/*
 *        JacORB - a free Java ORB
 *
 *   Copyright (C) 1997-2014 Gerald Brose / The JacORB Team.
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Library General Public
 *   License as published by the Free Software Foundation; either
 *   version 2 of the License, or (at your option) any later version.
 *
 *   This library is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *   Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this library; if not, write to the Free
 *   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package org.jacorb.imr;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import org.jacorb.config.Configuration;
import org.jacorb.config.ConfigurationException;
import org.jacorb.imr.AdminPackage.DuplicateServerName;
import org.jacorb.imr.AdminPackage.FileOpFailed;
import org.jacorb.imr.AdminPackage.IllegalServerName;
import org.jacorb.imr.AdminPackage.UnknownHostName;
import org.jacorb.imr.RegistrationPackage.DuplicatePOAName;
import org.jacorb.imr.RegistrationPackage.IllegalHostName;
import org.jacorb.imr.RegistrationPackage.IllegalPOAName;
import org.jacorb.imr.RegistrationPackage.InvalidSSDRef;
import org.jacorb.orb.LocateReplyReceiver;
import org.jacorb.orb.ORB;
import org.jacorb.orb.ParsedIOR;
import org.jacorb.orb.SystemExceptionHelper;
import org.jacorb.orb.giop.ClientConnection;
import org.jacorb.orb.giop.ClientConnectionManager;
import org.jacorb.orb.giop.GIOPConnection;
import org.jacorb.orb.giop.LocateReplyInputStream;
import org.jacorb.orb.giop.LocateRequestInputStream;
import org.jacorb.orb.giop.LocateRequestOutputStream;
import org.jacorb.orb.giop.MessageReceptorPool;
import org.jacorb.orb.giop.NoBiDirServerReplyListener;
import org.jacorb.orb.giop.ReplyListener;
import org.jacorb.orb.giop.ReplyOutputStream;
import org.jacorb.orb.giop.RequestInputStream;
import org.jacorb.orb.giop.RequestListener;
import org.jacorb.orb.giop.ServerGIOPConnection;
import org.jacorb.orb.giop.TransportManager;
import org.jacorb.orb.iiop.IIOPAddress;
import org.jacorb.orb.iiop.IIOPProfile;
import org.jacorb.orb.iiop.ServerIIOPConnection;
import org.jacorb.orb.listener.NullTCPConnectionListener;
import org.jacorb.poa.util.POAUtil;
import org.jacorb.util.ObjectUtil;
import org.omg.CORBA.SystemException;
import org.omg.GIOP.LocateStatusType_1_2;
import org.omg.PortableServer.IdAssignmentPolicyValue;
import org.omg.PortableServer.LifespanPolicyValue;
import org.omg.PortableServer.POA;
import org.omg.PortableServer.POAHelper;
import org.slf4j.Logger;

/**
 * This is the main class of the JacORB implementation repository.
 * It keeps track of the registered POAs with lifespan policy
 * PERSISTENT and provides a way for migrating and restarting
 * the POAS servers.
 *
 * @author Nicolas Noffke
 *
 */

public class ImplementationRepositoryImpl
    extends ImplementationRepositoryPOA
{
    /**
     * <code>orb</code> is the ORB instance for the IMR.
     */
    private org.omg.CORBA.ORB orb;

    private org.jacorb.config.Configuration configuration = null;

    /** the specific logger for this component */
    private Logger logger = null;

    private String iorFile = null;

    /**
     * the file containing the serialized server table. Also
     * used for writing the table to on shutdown.
     */
    private File table_file;
    private ServerTable server_table;
    private File table_file_backup;
    private SocketListener listener;
    private Thread listenerThread;

    private int object_activation_retries = 5;
    private int object_activation_sleep = 50;

    private boolean allow_auto_register = false;
    private boolean check_object_liveness = false;

    private long poaActivationTimeout = 120000; //2 min

    private WriteThread wt;
    private boolean updatePending;
    private ResourceLock server_table_ser_lock;
    private Shutdown shutdownThread;


    /**
     * The constructor.
     * It builds up the server table and starts up the SocketListener thread.
     */
    public ImplementationRepositoryImpl(org.omg.CORBA.ORB orb)
    {
        this.orb = orb;

        this.server_table_ser_lock = new ResourceLock();

        shutdownThread = new Shutdown ();
    }

    public void configure(Configuration myConfiguration)
        throws ConfigurationException
    {
        configuration = myConfiguration;

        wt = new WriteThread ();
        wt.setName ("IMR Write Thread");

        logger = configuration.getLogger("org.jacorb.imr");

        String defaultTableFile = "table.dat";
        String tableFileStr = configuration.getAttribute("jacorb.imr.table_file",
                                                         defaultTableFile);

        //NOTE: deliberate use of ref equivalence check here. I need to find
        //out if the default case has taken place, in which case, i assume
        //that the default string ref is just passed through.
        if (tableFileStr == defaultTableFile)
        {
            if (this.logger.isWarnEnabled())
            {
                this.logger.warn("No file for the server table specified! Please configure the property jacorb.imr.table_file!");
                this.logger.warn("Will create \"table.dat\" in current directory, if necessary");
            }
        }

        table_file = new File(tableFileStr);
        boolean _new_table = false;

        // try to open table file
        if (! table_file.exists ())
        {
            _new_table = true;
            if (this.logger.isInfoEnabled())
            {
                this.logger.info("Table file " + tableFileStr +
                                 " does not exist - autocreating it.");
            }

            try
            {
                table_file.createNewFile ();
            }
            catch (IOException ex)
            {
                throw new ConfigurationException("Failed to create table file", ex);
            }
        }
        else
        {
            if (table_file.isDirectory ())
            {
                throw new ConfigurationException("The table file is a directory! Please check " + table_file.getAbsolutePath());
            }

            if (! table_file.canRead())
            {
                throw new ConfigurationException("The table file is not readable! Please check " + table_file.getAbsolutePath());
            }

            if (! table_file.canWrite())
            {
                throw new ConfigurationException("The table file is not writable! Please check " + table_file.getAbsolutePath());
            }
        }

        try
        {
            if (_new_table)
            {
                this.server_table = new ServerTable();
                save_server_table(table_file);
            }
            else
            {
                try
                {
                    ObjectInputStream _in =
                        new ObjectInputStream(new FileInputStream(table_file));
                    server_table = (ServerTable)_in.readObject();
                    _in.close();
                }
                catch (Exception ex)
                {
                    logger.warn("Failed to read ServerTable -- creating an empty one");

                    server_table = new ServerTable();
                    save_server_table(table_file);
                }
            }
        }
        catch (FileOpFailed ex)
        {
            this.logger.error("Failed to read ServerTable", ex);
        }


        //should be set. if not, throw
        this.iorFile = configuration.getAttribute("jacorb.imr.ior_file");

        String _backup_file_str =
            configuration.getAttribute("jacorb.imr.backup_file", "");

        //set up server table backup file
        if (_backup_file_str.length() == 0)
        {
            this.logger.warn("No backup file specified!. No backup file will be created");
        }

        if (_backup_file_str.length() > 0)
        {
            table_file_backup = new File(_backup_file_str);

            // try to open backup file
            if ( ! table_file_backup.exists ())
            {
                _new_table = true;

                if (this.logger.isInfoEnabled())
                {
                    this.logger.info("Backup file " + _backup_file_str +
                                     " does not exist - autocreating it.");
                }

                try
                {
                    table_file_backup.createNewFile();
                }
                catch (IOException ex)
                {
                    throw new ConfigurationException("Failed to create backup file",
                                                     ex);
                }
            }
            else
            {
                if (table_file_backup.isDirectory ())
                {
                    throw new ConfigurationException("The backup file is a directory! Please check " + table_file_backup.getAbsolutePath());
                }

                if (! table_file_backup.canRead())
                {
                    throw new ConfigurationException("The backup file is not readable! Please check " + table_file_backup.getAbsolutePath());
                }

                if (! table_file_backup.canWrite())
                {
                    throw new ConfigurationException("The backup file is not writable! Please check " + table_file_backup.getAbsolutePath());
                }
            }
        }

        this.object_activation_retries =
            configuration.getAttributeAsInteger("jacorb.imr.object_activation_retries",
                                            5);

        this.object_activation_sleep =
            configuration.getAttributeAsInteger("jacorb.imr.object_activation_sleep",
                                            50);

        this.allow_auto_register =
            configuration.getAttributeAsBoolean("jacorb.imr.allow_auto_register",
                                                false);
        this.check_object_liveness =
            configuration.getAttributeAsBoolean("jacorb.imr.check_object_liveness",
                                                false);

        this.poaActivationTimeout =
            configuration.getAttributeAsInteger( "jacorb.imr.timeout", 120000);

        this.listener = new SocketListener();
        this.listener.configure(configuration);

        this.listenerThread = new Thread(listener);
        this.listenerThread.setPriority(Thread.MAX_PRIORITY);
        this.listenerThread.start();

        this.wt.setDaemon (true);
        this.wt.start ();
    }

    public String getIORFile()
    {
        return this.iorFile;
    }

    // implementation of org.jacorb.imr.RegistrationOperations interface

    /**
     * This method sets a server down, i.e. not.active. If a request for
     * that server is encountered, the server is tried to be restarted.
     *
     * @param server the servers name.
     * @exception org.jacorb.imr.UnknownServerName No server with name
     * <code>server</code> has been registered.
     */
    @Override
    public void set_server_down(String server)
        throws UnknownServerName
    {
        if (this.logger.isDebugEnabled())
        {
            this.logger.debug("ImR: server " + server + " is going down... ");
        }

        ImRServerInfo _server = server_table.getServer(server);
        _server.setDown();
    }


    /**
     * This method registers a POA. It has actually two functions:
     * <ul>
     * <li> Register a POA that has not yet been registered. It is the added to the
     * server table. </li>
     * <li> Reactivating a POA that is not active, but has already an entry
     * in the server table</li> </ul>
     * The reason for using only one method for those two tasks is that it is
     * much more difficult for the ORB, which does the registering, to distinguish
     * between an newly created POA and a restarted one.
     *
     * @param name the POAs name.
     * @param server the logical server name of the server the running in.
     * @param host the POAs host.
     * @param port the POas port.
     * @exception org.jacorb.imr.RegistrationPackage.IllegalPOAName the POAs name is not valid.
     * @exception org.jacorb.imr.RegistrationPackage.DuplicatePOAName an active POA with
     * <code>name</code> is currently registered.
     * @exception org.jacorb.imr.UnknownServerName The server has not been registered.
     */
    @Override
    public void register_poa(String name, String server, String host, int port)
        throws IllegalPOAName, DuplicatePOAName, UnknownServerName
    {
        ImRServerInfo             _server      = null;
        ImRPOAInfo                _poa         = null;
        boolean                   remap        = false;

        updatePending = true;

        if (this.logger.isDebugEnabled())
        {
            this.logger.debug("ImR: registering poa " + name + " for server: " +
                              server + " on " + host + ":" + port);
        }

        if( allow_auto_register &&
            ! server_table.hasServer( server ))
        {
            try
            {
                register_server( server, "", "" );
            }
            catch( IllegalServerName isn )
            {
                //ignore
            }
            catch( DuplicateServerName dsn )
            {
                //ignore
            }
        }

        _server = server_table.getServer(server);
        _poa = server_table.getPOA(name);

        if (_poa == null)
        {
            //New POAInfo is to be created
            _poa = new ImRPOAInfo(name, host, port, _server,
                                  poaActivationTimeout);

            server_table_ser_lock.gainExclusiveLock();
            _server.addPOA(_poa);
            server_table_ser_lock.releaseExclusiveLock();

            server_table.putPOA(name, _poa);

            this.logger.debug("ImR: new poa registered");
        }
        else
        {
            // Existing POA is reactivated

            // Need to check whether the old server is alive. if it is then
            // throw an exception otherwise we can remap the currently
            // registered name.
            if ((_poa.active) ||  (! server.equals(_poa.server.name)))
            {
                byte[] first = _poa.name.getBytes ();
                byte[] id = new byte [ first.length + 1];
                System.arraycopy (first, 0, id, 0, first.length);
                id[first.length] = org.jacorb.poa.POAConstants.OBJECT_KEY_SEP_BYTE;

                // If host and port are the same then it must be a replacement as
                // we could not have got to here if the original was running - we
                // would have got a socket exception.
                if (_poa.host.equals (host) && _poa.port == port)
                {
                    remap = true;
                }
                else
                {
                    // Otherwise try a ping

                    // The ImplRepo may not get chance to update its
                    // internal state due to server non-graceful
                    // shutdown. If the address is actively using by
                    // another server, the new endpoint(host, port)
                    // will replace the old endpoint.
                    boolean enp_reused = server_table.poa_enp_reused (_poa.name, _poa.host, _poa.port);

                    // We can not determine if the address is really
                    // using based on the server table since it may
                    // not be updated. We still need ping the server.
                    remap = ! (checkServerActive (_poa.host, _poa.port, id, enp_reused ));
                }

                if (remap == false)
                {
                    throw new DuplicatePOAName
                    (
                        "POA " + name +
                        " has already been registered " +
                        "for server " + _poa.server.name
                    );
                }
                this.logger.debug("ImR: Remapping server/port");
            }

            _poa.reactivate(host, port);
            this.logger.debug("ImR: register_poa, reactivated");
        }

        synchronized (wt)
        {
            wt.notify ();
        }
    }


    /**
     * Register a new host with a server startup daemon.
     * @param host a HostInfo object containing the hosts name and a reference to its
     * ServerStartupDaemon object.
     *
     * @exception org.jacorb.imr.RegistrationPackage.IllegalHostName <code>name</code> is not valid.
     * @exception org.jacorb.imr.RegistrationPackage.InvalidSSDRef It was impossible to connect
     * to the daemon.
     */
    @Override
    public void register_host(HostInfo host)
        throws IllegalHostName, InvalidSSDRef
    {

        if (host.name == null || host.name.length() == 0)
            throw new IllegalHostName(host.name);

        try
        {
            host.ssd_ref.get_system_load();
        }
        catch (Exception e)
        {
            this.logger.error("Exception while getting system load", e);
            throw new InvalidSSDRef();
        }
        updatePending = true;

        server_table.putHost(host.name, new ImRHostInfo(host));

        synchronized (wt)
        {
            wt.notify ();
        }
    }


    /**
     * Get host and port (wrapped inside an ImRInfo object) of this repository.
     * @return the ImRInfo object of this repository.
     */

    @Override
    public ImRInfo get_imr_info()
    {
        return new ImRInfo(listener.getAddress(), listener.getPort());
    }


    // implementation of org.jacorb.imr.AdminOperations interface

    /**
     * List all hosts currently registered with this repository.
     * It is not guaranteed that the references inside the HostInfo
     * objects are still valid.
     *
     * @return an array containing all known hosts.
     */
    @Override
    public HostInfo[] list_hosts()
    {
        return server_table.getHosts();
    }

    /**
     * List all registered server. The ServerInfo objects contain also
     * a list of the associated POAs.
     *
     * @return an array containing all registered servers.
     */
    @Override
    public ServerInfo[] list_servers()
    {
        ServerInfo [] servers;

        if (check_object_liveness)
        {
            this.logger.debug("ImR: Checking servers");

            servers = server_table.getServers();

            for (int k=0; k<servers.length; k++)
            {
                if (servers[k].active && servers[k].poas.length > 0)
                {
                    byte[] first = servers[k].poas[0].name.getBytes ();
                    byte[] id = new byte [ first.length + 1];
                    System.arraycopy (first, 0, id, 0, first.length);
                    id[first.length] = org.jacorb.poa.POAConstants.OBJECT_KEY_SEP_BYTE;

                    if ( ! checkServerActive
                         (servers[k].poas[0].host, servers[k].poas[0].port, id, false))
                    {
                        try
                        {
                            if (this.logger.isDebugEnabled())
                            {
                                this.logger.debug("ImR: Setting server " +
                                                  servers[k].name + " down");
                            }

                            // Server is not active so set it down
                            server_table.getServer(servers[k].name).setDown();

                            // Save retrieving the list again.
                            servers[k].active = false;
                        }
                        catch (UnknownServerName e)
                        {
                            if (this.logger.isErrorEnabled())
                            {
                                this.logger.error("ImR: Internal error - unknown server " + servers[k].name, e);
                            }
                        }
                    }
                }
            }
        }
        else
        {
            servers = server_table.getServers();
        }

        return servers;
    }

    /**
     * Get the ServerInfo object of a specific server.
     *
     * @param server the servers name.
     * @return the ServerInfo object of the server with name <code>server</code>
     * @exception UnknownServerName the server <code>server</code> has not been registered.
     */
    @Override
    public ServerInfo get_server_info(String server)
        throws UnknownServerName
    {
        return server_table.getServer(server).toServerInfo();
    }

    /**
     * Register a logical server. The logical server corresponds to a process
     * which has a number of POAs.
     *
     * @param name the servers name.
     * @param command the startup command for this server if it should be restarted
     * on demand. Has to be empty (NOT null) if the server should not be restarted.
     * @param host the host on which the server should be restarted. Should not
     * be null, but is ignored if no startup command is specified.
     *
     * @exception org.jacorb.imr.AdminPackage.IllegalServerName the servers name is not valid.
     * @exception org.jacorb.imr.AdminPackage.DuplicateServerName a server with <code>name</code>
     * has already been registered.
     */
    @Override
    public void register_server(String name, String command, String host)
        throws IllegalServerName, DuplicateServerName
    {
        updatePending = true;

        ImRServerInfo _server = new ImRServerInfo(name, host, command);
        server_table.putServer(name, _server);

        if (this.logger.isDebugEnabled())
        {
            this.logger.debug("ImR: server " + name + " on " +
                              host + " registered");
        }

        synchronized (wt)
        {
            wt.notify ();
        }
    }


    /**
     * Remove a logical server from the server table. If a server is removed, all of its POAs
     * are removed as well.
     *
     * @param name the servers name.
     * @exception org.jacorb.imr.UnknownServerName a server with <code>name</code> has not been registered.
     */
    @Override
    public void unregister_server(String name) throws UnknownServerName
    {
        updatePending = true;

        ImRServerInfo _server = server_table.getServer(name);
        String[] _poas = _server.getPOANames();

        // remove POAs
        for (int _i = 0; _i < _poas.length; _i++)
            server_table.removePOA(_poas[_i]);

        server_table.removeServer(name);

        if (this.logger.isDebugEnabled())
        {
            this.logger.debug("ImR: server " + name + " unregistered");
        }

        synchronized (wt)
        {
            wt.notify ();
        }
    }

    /**
     * Updates the server with a new command and host. For migrating purposes.
     *
     * @param name the servers name.
     * @param command the new startup command for this server.
     * @param host the new host.
     * @exception UnknownServerName a server with <code>name</code>
     * has not been registered.
     */
    @Override
    public void edit_server(String name, String command, String host)
        throws UnknownServerName
    {
        updatePending = true;

        ImRServerInfo _server = server_table.getServer(name);

        _server.command = command;
        _server.host = host;

        if (this.logger.isDebugEnabled())
        {
            this.logger.debug("ImR: server " + name + " edited");
        }

        synchronized (wt)
        {
            wt.notify ();
        }
    }


    /**
     * Hold a server. This causes all requests for this server to be delayed
     * until it is released. Holding a server is useful for migrating or
     * maintaining it. There is not timeout set, so requests might be delayed
     * indefinetly (or, at least, until the communication layer protests;-).
     *
     * @param name the servers name.
     * @exception org.jacorb.imr.UnknownServerName a server with <code>name</code> has not been registered.
     */
    @Override
    public void hold_server(String name)
        throws UnknownServerName
    {
        ImRServerInfo _server = server_table.getServer(name);
        _server.holding = true;
    }

    /**
     * Release a server from state "holding".
     *
     * @param name the servers name.
     * @exception org.jacorb.imr.UnknownServerName a server with <code>name</code> has not been registered.
     */
    @Override
    public void release_server(String name)
        throws UnknownServerName
    {
        ImRServerInfo _server = server_table.getServer(name);
        _server.release();
    }

    /**
     * Start a server.
     *
     * @param name the servers name.
     * @exception org.jacorb.imr.UnknownServerName a server with <code>name</code>
     * has not been registered.
     */
    @Override
    public void start_server(String name)
        throws UnknownServerName, ServerStartupFailed
    {
        restartServer(server_table.getServer(name));
    }

    /**
     * Save the server table to a backup file.
     * @exception org.jacorb.imr.AdminPackage.FileOpFailed something went wrong.
     */
    @Override
    public void save_server_table()
        throws FileOpFailed
    {
        if (table_file_backup != null)
        {
            save_server_table(table_file_backup);
        }
    }

    /**
     * Shut the repository down orderly, i.e. with saving of the server table.
     * The actual shutdown is done in the SocketListener thread because, if
     * done from here, the orb wont shut don correctly because this connection
     * is still active. (See end of SocketListener.run())
     *
     * @param wait wait_for_completion (from ORB.shutdown()). If false, then the ORB
     * is forced down, ignoring any open connection.
     */
    @Override
    public void shutdown(boolean wait)
    {
        synchronized (wt)
        {
            wt.shutdown ();
            wt.notify ();
        }
        if (listener != null)
        {
            listener.stopListening (wait);
            try
            {
                synchronized (listener)
                {
                    // Wait at most 5 seconds for the listener to shutdown.
                    listenerThread.join(5000);
                }
            }
            catch (InterruptedException e)
            {
                this.logger.warn("shutdown wait was interrupted", e);
            }
        }
        try
        {
            save_server_table ();
        }
        catch (FileOpFailed f)
        {
            this.logger.error("ImR: Failed to save backup table.", f);
        }
        this.logger.debug("ImR: Finished shutting down");
    }

    /**
     * Remove a host from the servertable. Hosts are removed
     * automatically on server startup, if they can't be accessed.
     *
     * @param name the hosts name.
     * @exception UnknownHostName no host with that name known.
     */
    @Override
    public void unregister_host(String name)
        throws UnknownHostName{
        if (server_table.removeHost(name) == null)
            throw new UnknownHostName(name);
    }

    /**
     * Convenience method which does the actual serialization.
     *
     * @param save_to the file where to write to.
     */
    private void save_server_table(File save_to) throws FileOpFailed {
        try{
            ObjectOutputStream _out = new ObjectOutputStream(new FileOutputStream(save_to));

            server_table.table_lock.gainExclusiveLock();
            _out.writeObject(server_table);
            server_table.table_lock.releaseExclusiveLock();

            _out.flush();
            _out.close();
        }
        catch (Exception e)
        {
            this.logger.error("Exception while saving server table", e);
            throw new FileOpFailed();
        }
        updatePending = false;
    }


    /**
     * Prints the usage screen and exits.
     */
    public static void usage ()
    {
        System.out.println("Usage: The following properties are useful in conjunction with the \nImplementationRepository:");
        System.out.println("\t \"jacorb.imr.endpoint_host\" Address to listen on for requests");
        System.out.println("\t \"jacorb.imr.endpoint_port_number\" Port to listen on for requests");
        System.out.println("\t \"jacorb.imr.table_file\" The file to store the server table into");
        System.out.println("\t \"jacorb.imr.backup_file\" The file to store the server table backup into");
        System.out.println("\t \"jacorb.imr.ior_file\" The file to store the ImRs IOR into");
        System.out.println("\t \"jacorb.imr.allow_auto_register\" if set to \"on\", servers that don't \n\talready have an entry on their first call to the imr, will get \n\tautomatically registered. Otherwise, an UnknownServer exception \n\tis thrown.");
        System.exit(0);
    }

    /**
     * The main method. "Parses" the arguments and sets the corresponding
     * attributes up, creates a new ImplementationRepositoryImpl instance and
     * runs the ORB.
     */
    public static void main(String[] args) throws Exception
    {
        // translate any properties set on the commandline but after the
        // class name to a properties
        java.util.Properties argProps = ObjectUtil.argsToProps( args );
        argProps.setProperty("jacorb.implname", "the_ImR");
        argProps.setProperty("jacorb.use_imr", "off");
        argProps.setProperty("jacorb.use_tao_imr", "off");

        boolean printIOR = false;

        for (int i = 0; i < args.length; i++)
        {
            if ("-printIOR".equals(args[i]))
            {
                printIOR = true;
            }
        }

        //Write IOR to file
        org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init( args, argProps );

        ImplementationRepositoryImpl _imr =
                new ImplementationRepositoryImpl(orb);
        _imr.configure(((org.jacorb.orb.ORB) orb).getConfiguration());

        POA root_poa = POAHelper.narrow(orb.resolve_initial_references("RootPOA"));
        root_poa.the_POAManager().activate();

        org.omg.CORBA.Policy[] policies = new org.omg.CORBA.Policy[2];

        policies[0] =
                root_poa.create_lifespan_policy(LifespanPolicyValue.PERSISTENT);
        policies[1] =
                root_poa.create_id_assignment_policy(IdAssignmentPolicyValue.USER_ID);

        POA imr_poa = root_poa.create_POA( "ImRPOA",
                root_poa.the_POAManager(),
                policies );

        for (int i=0; i<policies.length; i++)
        {
            policies[i].destroy();
        }

        byte[] id = "ImR".getBytes();


        imr_poa.activate_object_with_id( id, _imr );

        PrintWriter _out = new PrintWriter
                (new FileOutputStream(new File(_imr.getIORFile())));

        final org.omg.CORBA.Object imrReference = imr_poa.servant_to_reference(_imr);
        _out.println(orb.object_to_string(imrReference));
        _out.flush();
        _out.close();

        if (printIOR)
        {
            System.out.println ("SERVER IOR: " + orb.object_to_string(imrReference));
            System.out.flush();
        }

        _imr.shutdownThread.setDaemon (true);
        _imr.shutdownThread.setName ("Shutdown Thread");

        Runtime.getRuntime().addShutdownHook(_imr.shutdownThread);

        orb.run();
    }

    private void restartServer(ImRServerInfo server)
        throws ServerStartupFailed
    {
        // server might be holding
        server.awaitRelease();

        if(! server.active )
        {
            if (this.logger.isDebugEnabled())
            {
                this.logger.debug("ImR: server " + server.name + " is down");
            }

            if (server.command.length() == 0)
            {
                //server can't be restarted, send exception
                throw new ServerStartupFailed("Server " + server.name +
                                              " can't be restarted because" +
                                              " of missing startup command");
            }

            // we have to synchronize here to avoid a server to be
            // restarted multiple times by requests that are
            // received in the gap between the first try to
            // restart and the reactivation of the POAs.
            // restarting is set back to false when the first POA
            // is reactivated and the server goes back to active
            // (see ImRPOAInfo.reactivate()).
            if (server.shouldBeRestarted())
            {
                try
                {
                    // If there is no SSD for the host, we get an
                    // NullPointerException.  In a further
                    // version, we might choose another random
                    // SSD.
                    ImRHostInfo _host = server_table.getHost(server.host);

                    if( _host == null )
                    {
                        throw new ServerStartupFailed( "Unknown host: >>" +
                                server.host + "<<" );
                    }

                    if (this.logger.isDebugEnabled())
                    {
                        this.logger.debug("ImR: will restart " + server.name);
                    }

                    _host.startServer(server.command, orb);
                }
                catch (ServerStartupFailed ssf)
                {
                    server.setNotRestarting();

                    throw ssf;
                }
                catch (Exception e)
                {
                    server.setNotRestarting();

                    this.logger.error("Exception while restarting server", e);

                    // sth wrong with daemon, remove from table
                    server_table.removeHost(server.host);

                    throw new ServerStartupFailed("Failed to connect to host!");
                }
            }
            else
            {
                if (this.logger.isDebugEnabled())
                {
                    this.logger.debug("ImR: somebody else is restarting " +
                            server.name);
                }
            }
        }
        else
        {
            if (this.logger.isDebugEnabled())
            {
                this.logger.debug("ImR: server " + server.name + " is active");
            }
        }
    }


    /**
     * Inner class SocketListener, responsible for accepting
     * connection requests.  *Very* close to inner class Listener in
     * orb/BasicAdapter.java.
     * <br> When a connection is accepted a
     * new RequestReceptor thread is started.
     */
    private class SocketListener
        implements Runnable
    {
        private ServerSocket server_socket;
        private int port = 0;
        private String address;
        private int timeout = 0;
        private boolean run = true;
        private boolean wait = false;

        private MessageReceptorPool receptor_pool = null;
        private RequestListener request_listener = null;
        private ReplyListener reply_listener = null;

        private TransportManager transport_manager = null;

        /**
         * The constructor. It sets up the ServerSocket and starts the thread.
         */
        public SocketListener()
        {
            request_listener = new ImRRequestListener();
            reply_listener = new NoBiDirServerReplyListener();
        }

        public void configure(Configuration myConfiguration)
            throws ConfigurationException
        {
            // Moved from the constructor to facilitate logging.
            receptor_pool = new MessageReceptorPool("server", "ImplementationRepository", myConfiguration);

            try
            {
                int endpoint_port =
                    configuration.getAttributeAsInteger(
                        "jacorb.imr.endpoint_port_number",0);

                String endpoint_host =
                    configuration.getAttribute("jacorb.imr.endpoint_host", "");

                if (endpoint_host.length() > 0)
                {
                    server_socket =
                        new ServerSocket( endpoint_port,
                                          50, //default backlog, see jdk doc
                                          InetAddress.getByName(endpoint_host));
                }
                else
                {
                    //no explicit address given, listen anywhere
                    server_socket =
                        new ServerSocket(endpoint_port);
                }

                if( endpoint_host.length() > 0 )
                {
                    address = endpoint_host;
                }
                else
                {
                    address = IIOPAddress.getLocalHostAddress(logger);
                }

                if( address.indexOf("/") >= 0 )
                {
                    address = address.substring(address.indexOf("/") + 1);
                }

                port = server_socket.getLocalPort();

                if (logger.isDebugEnabled())
                {
                    logger.debug("ImR Listener at " + port + ", " + address);
                }
            }
            catch (Exception e)
            {
                throw new ConfigurationException("Listener: Couldn't init", e);
            }

            this.transport_manager = new TransportManager();
            this.transport_manager.configure(configuration);
        }


        /**
         * Get the port this SocketListener is listening on.
         *
         * @return the port
         */
        public int getPort()
        {
            return port;
        }

        /**
         * The internet address of the Socket this thread is listening on.
         *
         * @return the address of the socket.
         */
        public String getAddress()
        {
            return address;
        }

        /**
         * Set the connection timeout.
         *
         * @param timeout the timeout.
         */
        public void setTimeout( int timeout )
        {
            this.timeout = timeout;
        }

        /**
         * The threads main event loop. Listenes on the socket
         * and starts new RequestReceptor threads on accepting.
         * <br> On termination does the actual shutdown of the
         * repository.
         */
        @Override
        public void run()
        {
            while( run )
            {
                try
                {
                    Socket socket = server_socket.accept();
                    socket.setSoTimeout( timeout );

                    org.jacorb.orb.iiop.ServerIIOPConnection transport =
                        new ServerIIOPConnection (socket,
                                                  false,
                                                  new NullTCPConnectionListener() ); // TODO // no SSL
                    transport.configure(configuration);

                    GIOPConnection connection =
                    new ServerGIOPConnection( transport.get_server_profile(),
                                              transport,
                                              request_listener,
                                              reply_listener,
                                              null,
                                              null);
                    connection.configure(configuration);
                    receptor_pool.connectionCreated( connection );
                }
                catch (Exception _e)
                {
                    // when finishing, we do a close() on
                    // server_socket from "outside" and that causes an
                    // exception here. But since we wanted it this
                    // way, we don't display the Exception to avoid
                    // confusing users.
                    if (run)
                    {
                        logger.debug("Internal Exception, can be ignored", _e);
                    }
                }
            }

            // doing the actual shutdown of the implementation
            // repository here
            orb.shutdown(wait);
        }

        /**
         * Causes the event loop to terminate by closing the ServerSocket.
         *
         * @param wait for ORB.shutdown().
         */
        public void stopListening(boolean wait)
        {
            run = false;
            this.wait = wait;

            try
            {
                server_socket.close();
            }
            catch (Exception _e)
            {
                logger.error("Exception while closing server socket", _e);
            }
        }
    }


    private boolean checkServerActive(String host, int port, byte []object_key, boolean enp_reused)
    {
        ClientConnectionManager   cm           = null;
        IIOPAddress               address      = null;
        ClientConnection          connection   = null;
        LocateRequestOutputStream lros         = null;
        LocateReplyReceiver       receiver     = null;
        LocateReplyInputStream    lris         = null;
        boolean                   result       = false;

        cm = ((org.jacorb.orb.ORB)orb).getClientConnectionManager ();
        try
        {
            address = new IIOPAddress (host, port);
            address.configure(configuration);

            IIOPProfile iiopProfile = new IIOPProfile(address, object_key, ((org.jacorb.orb.ORB)orb).getGIOPMinorVersion());
            iiopProfile.configure(configuration);

            connection = cm.getConnection(iiopProfile);
        }
        catch(ConfigurationException e)
        {
            logger.error("Failed to configure", e);
        }

        if (this.logger.isDebugEnabled())
        {
            this.logger.debug("Pinging " + host + " / " + port);
        }

        try
        {
            lros = new LocateRequestOutputStream ( orb, object_key, connection.getId(), 2);
            receiver = new LocateReplyReceiver();
            receiver.configure (configuration);

            connection.sendRequest(
                lros,
                receiver,
                lros.getRequestId (),
                true ); // response expected

            lris = receiver.getReply();

            switch (lris.rep_hdr.locate_status.value ())
            {
                case LocateStatusType_1_2._UNKNOWN_OBJECT:
                case LocateStatusType_1_2._LOC_SYSTEM_EXCEPTION:
                {

                    SystemException se = SystemExceptionHelper.read( lris );
                    if (enp_reused && (se instanceof org.omg.CORBA.OBJECT_NOT_EXIST))
                    {
                       // Endpoint is reused by another server and the new server
                       // returned OBJECT_NOT_EXIST, so give up using this endpoint.
                       // The return value can be interpreted as not available endpoint.
                       result = false;
                    }
                    else
                    {
                       result = true;
                    }
                    break;
                }
                case LocateStatusType_1_2._OBJECT_HERE:
                case LocateStatusType_1_2._OBJECT_FORWARD:
                case LocateStatusType_1_2._OBJECT_FORWARD_PERM:
                case LocateStatusType_1_2._LOC_NEEDS_ADDRESSING_MODE:
                default:
                {
                    result = true;
                    break;
                }
            }
        }
        catch (Throwable ex)
        {
            this.logger.debug("Exception while checking server active", ex);

            result = false;
        }
        finally
        {
            cm.releaseConnection (connection);
        }
        return result;
    }


    /**
     * Inner class ImRRequestListener. Receives messages.
     */
    private class ImRRequestListener
        implements RequestListener
    {
        public ImRRequestListener()
        {
        }

        /**
         * receive and dispatch requests
         *
         * @param request a <code>byte[]</code> value
         * @param connection a <code>GIOPConnection</code> value
         */
        @Override
        public void requestReceived( byte[] request,
                                     GIOPConnection connection )
        {
            logger.debug("requestReceived");
            connection.incPendingMessages();

            final RequestInputStream in = new RequestInputStream( orb, connection, request );

            try
            {
            replyNewLocation( ((org.jacorb.orb.ORB)orb).mapObjectKey(
                                    ParsedIOR.extractObjectKey(in.req_hdr.target, (org.jacorb.orb.ORB)orb)),
                              in.req_hdr.request_id,
                              in.getGIOPMinor(),
                              connection,
                              false);
            }
            finally
            {
                in.close();
            }
        }

        @Override
        public void locateRequestReceived( byte[] request,
                                           GIOPConnection connection )
        {
            connection.incPendingMessages();

            final LocateRequestInputStream in = new LocateRequestInputStream( orb, request );

            try
            {
                replyNewLocation(ParsedIOR.extractObjectKey(in.req_hdr.target, (org.jacorb.orb.ORB) orb),
                        in.req_hdr.request_id,
                        in.getGIOPMinor(),
                        connection,
                        true);
            }
            finally
            {
                in.close();
            }
        }

        @Override
        public void cancelRequestReceived( byte[] request,
                                           GIOPConnection connection )
        {
            //ignore
        }

        public void fragmentReceived( byte[] fragment,
                                      GIOPConnection connection )
        {
            //ignore
        }

        public void connectionClosed()
        {
        }

        /**
         * The actual core method of the implementation repository.
         * Causes servers to start, looks up new POA locations in
         * the server table.
         */
        private void replyNewLocation( byte[] object_key,
                                       int request_id,
                                       int giop_minor,
                                       GIOPConnection connection,
                                       boolean isLocateRequest)
        {
            String _poa_name =
            POAUtil.extractImplName( object_key ) + '/' +
            POAUtil.extractPOAName( object_key );

            // look up POA in table
            ImRPOAInfo _poa = server_table.getPOA( _poa_name );
            if (_poa == null)
            {
                sendSysException(
                    new org.omg.CORBA.TRANSIENT( "POA " +
                                                _poa_name +
                                                " unknown" ),
                    connection,
                    request_id,
                    giop_minor );
                return;
            }

            // get server of POA
            ImRServerInfo _server = _poa.server;

            if (logger.isDebugEnabled())
            {
                logger.debug("ImR: Looking up: " + _server.name);
            }

            // There is only point pinging the remote object if server
            // is active and either the QoS to ping returned objects
            // is true or the ServerStartUpDaemon is active and there
            // is a command to run - if not, even if the server isn't
            // actually active, we can't restart it so just allow this
            // to fall through and throw the TRANSIENT below.
            boolean ssd_valid =
            (
                (_server.command.length() != 0) &&
                (server_table.getHost(_server.host) != null)
            );

            if (_server.active && (check_object_liveness || ssd_valid))
            {
                // At this point the server *might* be running - we
                // just want to verify it.
                if (! checkServerActive (_poa.host, _poa.port, object_key, false))
                {
                    // Server is not active so set it down
                    _server.setDown ();
                }
            }

            try
            {
                restartServer( _server );
            }
            catch( ServerStartupFailed ssf )
            {
                if (logger.isInfoEnabled())
                {
                    logger.info("Object (" + _server.name + ") on "
                                      + _poa.host + '/' + _poa.port +
                                      " not reachable");
                }

                sendSysException( new org.omg.CORBA.TRANSIENT(ssf.reason),
                                  connection,
                                  request_id,
                                  giop_minor );
                return;
            }

            // POA might not be active
            boolean _old_poa_state = _poa.active;

            // wait for POA to be reregistered.
            if( ! _poa.awaitActivation() )
            {
                // timeout reached
                sendSysException( new org.omg.CORBA.TRANSIENT("Timeout exceeded"),
                                  connection,
                                  request_id,
                                  giop_minor );
                return;
            }

            ReplyOutputStream out =
                new ReplyOutputStream( orb,
                                       request_id,
                                       org.omg.GIOP.ReplyStatusType_1_2.LOCATION_FORWARD,
                                       giop_minor,
                                       isLocateRequest,
                                       logger);

            // The typecode is for org.omg.CORBA.Object, but avoiding
            // creation of new ObjectHolder Instance.
            IIOPAddress addr = new IIOPAddress (_poa.host,(short)_poa.port);
            org.omg.IOP.IOR _ior = null;
            try
            {
                addr.configure(configuration);
                IIOPProfile p = new IIOPProfile (addr,object_key,giop_minor);
                p.configure(configuration);
                _ior = ParsedIOR.createObjectIOR ((ORB)orb, p);
            }
            catch(ConfigurationException e)
            {
                logger.error("Error while configuring address/profile", e);
            }

            if( !_old_poa_state )
            {
                // if POA has been reactivated, we have to wait for
                // the requested object to become ready again. This is
                // for avoiding clients to get confused by
                // OBJECT_NOT_EXIST exceptions which they might get
                // when trying to contact the server too early.

                org.omg.CORBA.Object _object =
                    orb.string_to_object(
                        (new ParsedIOR( (org.jacorb.orb.ORB) orb, _ior)).getIORString());

                // Sort of busy waiting here, no other way possible
                for( int _i = 0; _i < object_activation_retries; _i++ )
                {
                    try
                    {
                        Thread.sleep( object_activation_sleep );

                        // This will usually throw an OBJECT_NOT_EXIST
                        if( ! _object._non_existent() ) // "CORBA ping"
                        {
                            break;
                        }
                    }
                    catch(Exception _e)
                    {
                        logger.info("Exception while waiting for object", _e);
                    }
                }
            }

            try
            {
                // write new location to stream
                out.write_IOR(_ior);

                if (logger.isDebugEnabled())
                {
                    logger.debug("ImR: Sending location forward for " +
                                      _server.name);
                }

                connection.sendReply( out );
            }
            catch( IOException _e )
            {
                logger.error("Exception while writing new location", _e);

                sendSysException( new org.omg.CORBA.UNKNOWN(_e.toString()),
                                  connection,
                                  request_id,
                                  giop_minor );
            }
        }

        /**
         * Convenience method for sending a CORBA System Exception back to
         * the client.
         *
         * @param sys_ex the exception to send back.
         */
        private void sendSysException( org.omg.CORBA.SystemException sys_ex,
                                       GIOPConnection connection,
                                       int request_id,
                                       int giop_minor )
        {
            ReplyOutputStream out =
                new ReplyOutputStream( orb,
                                       request_id,
                                       org.omg.GIOP.ReplyStatusType_1_2.SYSTEM_EXCEPTION,
                                       giop_minor,
                                       false,
                                       logger);

            SystemExceptionHelper.write( out, sys_ex );

            try
            {
                connection.sendReply( out );
            }
            catch( IOException _e )
            {
                logger.error("Exception while sending SystemException to client", _e);
            }
        }
    }


    /**
     * <code>WriteThread</code> runs as a background thread which will write the
     * server table out whenever any modifications are made.
     */
    private class WriteThread extends Thread
    {
        boolean done;

        public WriteThread ()
        {
        }

        /**
         * <code>run</code> continiously loops until the shutdown is called.
         */
        @Override
        public void run ()
        {
            while (true)
            {
                try
                {
                    server_table_ser_lock.gainExclusiveLock();
                    save_server_table (table_file);
                }
                catch (FileOpFailed ex)
                {
                    logger.error("Exception while saving server table", ex);
                }
                finally
                {
                    server_table_ser_lock.releaseExclusiveLock();
                }

                if (done)
                {
                    break;
                }

                // If by the time we have written the server table another request has arrived
                // which requires an update don't bother entering the wait state.
                if ( ! updatePending)
                {
                    try
                    {
                        synchronized (this)
                        {
                            this.wait ();
                        }
                    }
                    catch (InterruptedException ex) {}

                    logger.debug("ImR: IMR write thread waking up to save server table... ");
                }
            }
        }

        /**
         * <code>shutdown</code> toggles the thread to shut itself down.
         */
        public void shutdown ()
        {
            done = true;
        }
    }


    /**
     * <code>Shutdown</code> is a thread that is run the Java 1.3 (and greater)
     * virtual machine upon receiving a Ctrl-C or kill -INT.
     */
    private class Shutdown extends Thread
    {
        @Override
        public synchronized void run ()
        {
            logger.debug("ImR: Shutting down");
            shutdown(true);
        }
    }

} // ImplementationRepositoryImpl
