package jacorb.imr;

import jacorb.imr.RegistrationPackage.*;
import jacorb.imr.AdminPackage.*;
import jacorb.imr.ServerStartupDaemonPackage.*;
import java.io.*;
import jacorb.orb.*;
import jacorb.orb.giop.*;
import java.lang.*;
import jacorb.poa.util.*;
import org.omg.IIOP.*;
import org.omg.PortableServer.*;
import java.net.*;
import jacorb.util.Environment;

/**
 * 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 fro migrating and restarting
 * the POAS servers.
 *
 * @author Nicolas Noffke
 * 
 * $Id: ImplementationRepositoryImpl.java,v 1.21 2000/04/04 12:19:02 noffke Exp $
 */

public class ImplementationRepositoryImpl 
    extends ImplementationRepositoryPOA
{
    private static int m_default_port = 0;
    private static jacorb.orb.ORB m_orb;

    private File m_table_file;
    private ServerTable m_server_table;
    private File m_table_file_backup;
    private SocketListener m_listener;

    private int m_object_activation_retries = 5;
    private int m_object_activation_sleep = 50;

    /**
     * The constructor.
     * It builds up the server table and starts up the SocketListener thread.
     *
     * @param table_file the file containing the serialized server table. Also 
     * used for writing the table to on shutdown. If null, an empty table is created.
     * @param table_backup the file where backups are written to.
     * @param new_table set to true, if an empty server table should be created
     */
    public ImplementationRepositoryImpl(File table_file, File table_backup,
					boolean new_table) {
	m_table_file = table_file;
	m_table_file_backup = table_backup;

	//build up server table
	if (new_table)
	    m_server_table = new ServerTable();
	else{
	    try{
		ObjectInputStream _in = new ObjectInputStream(new FileInputStream(m_table_file));
		m_server_table = (ServerTable)_in.readObject();

		_in.close();
	    }catch (Exception _e){
		jacorb.util.Debug.output(2, _e);
		m_server_table = new ServerTable();
	    }
	}
	
	//read in properties from Environment.
	try{
	    String _tmp = Environment.getProperty("jacorb.imr.object_activation_retries");
	    m_object_activation_retries = Integer.parseInt(_tmp);
	}catch(Exception _e){}
    
	try{
	    String _tmp = Environment.getProperty("jacorb.imr.object_activation_sleep");
	    m_object_activation_sleep = Integer.parseInt(_tmp);
	}catch(Exception _e){}

    
	m_listener = new SocketListener();
    }

    // implementation of 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 jacorb.imr.UnknownServerName No server with name 
     * <code>server</code> has been registered.
     */
    public void set_server_down(String server) 
	throws UnknownServerName {
	jacorb.util.Debug.output(2,"ImR: server " + server + " is going down... ");

	ImRServerInfo _server = m_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 jacorb.imr.RegistrationPackage.IllegalPOAName the POAs name is not valid.
     * @exception jacorb.imr.RegistrationPackage.DuplicatePOAName an active POA with
     * <code>name</code> is currently registered.
     * @exception jacorb.imr.UnknownServerName The server has not been registered.
     */
    public void register_poa(String name, String server, String host, int port) 
	throws IllegalPOAName, DuplicatePOAName, UnknownServerName {
	
	jacorb.util.Debug.output(2,"ImR: registering poa " + name + " for server: " + server + " on " + host );
	
	ImRServerInfo _server = m_server_table.getServer(server);
	ImRPOAInfo _poa = m_server_table.getPOA(name);

	if (_poa == null){
	    //New POAInfo is to be created
	    _poa = new ImRPOAInfo(name, host, port, _server);
	    _server.addPOA(_poa);
	    m_server_table.putPOA(name, _poa);
	    jacorb.util.Debug.output(2,"ImR: new poa registered");

	}
	else {
	    //Existing POA is reactivated
	  if ((_poa.m_active) ||  (! server.equals(_poa.m_server.m_name)))
	    throw new DuplicatePOAName("POA " + name + " has already been registered " +
				       "for server " + _poa.m_server.m_name);
	    
	    _poa.reactivate(host, port);
	    jacorb.util.Debug.output(2,"ImR: register_poa, reactivated");
	}
    }
 

    /**
     * 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 jacorb.imr.RegistrationPackage.IllegalHostName <code>name</code> is not valid.
     * @exception jacorb.imr.RegistrationPackage.InvalidSSDRef It was impossible to connect 
     * to the daemon.
     */
    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){
	    jacorb.util.Debug.output(2, _e);
	    throw new InvalidSSDRef();
	}
	
	m_server_table.putHost(host.name, new ImRHostInfo(host));
    }
    

    /**
     * Get host and port (wrapped inside an ImRInfo object) of this repository.
     * @return the ImRInfo object of this repository.
     */
    public ImRInfo get_imr_info(){
	return new ImRInfo(m_listener.getAddress(), m_listener.getPort());
    }
    

    // implementation of 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.
     */
    public HostInfo[] list_hosts() {
	return m_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.
     */
    public ServerInfo[] list_servers() {
	return m_server_table.getServers();
    }

    /**
     * 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.
     */
    public ServerInfo get_server_info(String server)
	throws UnknownServerName{
	return m_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 jacorb.imr.AdminPackage.IllegalServerName the servers name is not valid.
     * @exception jacorb.imr.AdminPackage.DuplicateServerName a server with <code>name</code>
     * has already been registered.
     */
    public void register_server(String name, String command, String host) 
	throws IllegalServerName, DuplicateServerName {
	ImRServerInfo _server = new ImRServerInfo(name, host, command);
	m_server_table.putServer(name, _server);
	jacorb.util.Debug.output(2,"ImR: server " + name + " on " + host + " registered");
    }


    /**
     * 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 jacorb.imr.UnknownServerName a server with <code>name</code> has not been registered.
     */
    public void unregister_server(String name) throws UnknownServerName {
	ImRServerInfo _server = m_server_table.getServer(name);
	String[] _poas = _server.getPOANames();

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

	m_server_table.removeServer(name);
	jacorb.util.Debug.output(2,"ImR: server " + name + " unregistered");
    }

    /**
     * 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 jacorb.imr.AdminPackage.UnknownServerName a server with <code>name</code>
     * has not been registered.
     */
    public void edit_server(String name, String command, String host) 
	throws UnknownServerName {
	ImRServerInfo _server = m_server_table.getServer(name);

	_server.m_command = command;
	_server.m_host = host;

	jacorb.util.Debug.output(2,"ImR: server " + name + " edited");
    }


    /**
     * 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 jacorb.imr.UnknownServerName a server with <code>name</code> has not been registered.
     */
    public void hold_server(String name) throws UnknownServerName {
	ImRServerInfo _server = m_server_table.getServer(name);
	_server.m_holding = true;
    }

    /**
     * Release a server from state "holding".
     *
     * @param name the servers name.
     * @exception jacorb.imr.UnknownServerName a server with <code>name</code> has not been registered.
     */
    public void release_server(String name) throws UnknownServerName {
	ImRServerInfo _server = m_server_table.getServer(name);
	_server.release();
    }
    
    /**
     * Save the server table to a backup file.
     * @exception jacorb.imr.AdminPackage.FileOpFailed something went wrong.
     */
    public void save_server_table() throws FileOpFailed {
	save_server_table(m_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.
     */
    public void shutdown(boolean wait) {
	try{
	    save_server_table(m_table_file);
	}catch (Exception _e){
	    jacorb.util.Debug.output(2, _e);
	}

	m_listener.stopListening(wait);
    }

    /**
     * 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 no host with that name known.
     */
    public void unregister_host(String name)
	throws UnknownHostName{
	if (m_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.
     * @exception sth went wrong.
     */
    private void save_server_table(File save_to) throws FileOpFailed {
	try{
	    ObjectOutputStream _out = new ObjectOutputStream(new FileOutputStream(save_to));

	    m_server_table.m_table_lock.gainExclusiveLock();
	    _out.writeObject(m_server_table);
	    m_server_table.m_table_lock.releaseExclusiveLock();

	    _out.flush();
	    _out.close();
	}catch (Exception _e){
	    jacorb.util.Debug.output(2, _e);
	    throw new FileOpFailed();
	}
    }

    /**
     * Convenience method which returns a reference to the repository.
     *
     * @param orb the ORB is needed for string_to_object().
     * @param ior_url URL string referencing the IOR file of an arbitrary 
     * ImplementationRepository.
     * @return a reference to an implementation repository.
     */
    public static ImplementationRepository getImR(org.omg.CORBA.ORB orb,
						  String ior_url){
	try{
	    java.net.URL _imr_ref_url = new java.net.URL(ior_url);
	    
	    BufferedReader _in = new BufferedReader(new InputStreamReader(_imr_ref_url.openStream())); 
	    String _line = _in.readLine();
	    
	    while (_line.indexOf("IOR:") != 0) 
		_line = _in.readLine();
	    
	    _in.close();
	    
	    ImplementationRepository _imr = 
		ImplementationRepositoryHelper.narrow(orb.string_to_object(_line));
	    
	    return _imr;
	}
	catch (Exception _e){ 
	    jacorb.util.Debug.output(4,_e);	    
	}
	return null;    
    }

    
    /**
     * Convenience method which returns a reference to the repository.
     * Takes the url for the IOR file from properties.
     *
     * @param orb the ORB is needed for string_to_object().
     * @return a reference to an implementation repository.
     */
    public static ImplementationRepository getImR(org.omg.CORBA.ORB orb){
	String _url_str = null;
	try{
	    _url_str = Environment.getProperty("jacorb.ImplementationRepositoryURL");
	    if (_url_str == null)
		_url_str = "http://localhost/ImR_Ref";
	}
	catch (Exception _e)
	{ 
	    jacorb.util.Debug.output(4,_e);	    
	}
	return getImR(orb, _url_str);
    }

    /**
     * Prints the usage screen and exits.
     */
    public static void usage(){
	System.out.println("Usage: ImplementationRepositoryImpl [Parameter]");
	System.out.println("Parameter:");
	System.out.println("\t -p <port> Port to listen on for requests");
	System.out.println("\t -f <file> Read in server table from this file");
	System.out.println("\t -n Start with empty server table");
	System.out.println("\t -i <iorfile> Place IOR in this file");
	System.out.println("\t -b <backupfile> Put server table in this file");
	System.out.println("\t -h Print this help");
 
	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) {
	// evaluate args
	if (args.length > 8)
	    usage();

	String _table_file_str = null;
	boolean _new_table = false;
	String _ior_file_str = null;
	String _backup_file_str = null;

	try{
	    for (int i = 0; i < args.length ; i++){
		switch (args[i].charAt(1)){
		case 'h' : {
		    usage();
		}
		case 'p' : {
		    m_default_port = Integer.parseInt(args[++i]);
		    break;
		}
		case 'f' : {
		    if (_new_table)
			// -f and -n together not allowed
			usage();
		    _table_file_str = args[++i];
		    break;
		}
		case 'n' : {
		    if (_table_file_str != null)
			usage();
		    _new_table = true;
		    break;
		}
		case 'i' :{
		    _ior_file_str = args[++i];
		    break;
		}
		case 'b' :{
		    _backup_file_str = args[++i];
		    break;
		}
		default:
		    usage();
		}
	    }
	}catch (Exception _e){
	    _e.printStackTrace();
	    usage();
	}
	
	// table file not specified, try via property
	if (_table_file_str == null){
	    _table_file_str = Environment.getProperty("jacorb.imr.table_file");

	    if (_table_file_str == null){
		System.out.println("WARNING: No file for the server table specified!");
		System.out.println("Property jacorb.imr.table_file or use the -f switch");
		System.out.println("Will create \"table.dat\" in current directory, if necessary");
		_table_file_str = "table.dat";
	    }
	}
	
	File _table_file = new File(_table_file_str);
	
	// try to open table file
	if (! _new_table){
	    if (!_table_file.exists()){ 	
		System.out.println("ERROR: The table file does not exist!");
		System.out.println("Please check " + _table_file.getAbsolutePath());
		System.out.println("Property jacorb.imr.table_file or use the -n or -f switch");
		System.exit(-1);
	    }

	    if (_table_file.isDirectory()){
		System.out.println("ERROR: The table file is a directory!");
		System.out.println("Please check " + _table_file.getAbsolutePath());
		System.out.println("Property jacorb.imr.table_file or use the -n or -f switch");
		System.exit(-1);
	    }
	
	    if (! _table_file.canRead()){
		System.out.println("ERROR: The table file is not readable!");
		System.out.println("Please check " + _table_file.getAbsolutePath());
		System.exit(-1);
	    }

	    if (! _table_file.canWrite()){
		System.out.println("WARNING: The table file is not writable!");
		System.out.println("Please check " + _table_file.getAbsolutePath());
	    }
	}
	else
	    try{
		//testing the hard way, if the file can be created
		//with jdk1.2, we might try createNewFile()
		FileOutputStream _out = new FileOutputStream(_table_file);
		_out.close();
		_table_file.delete(); //don't leave empty files lying around
	    
	    }catch (Exception _e){
		System.out.println("WARNING: Unable to create table file!");
		System.out.println("Please check " + _table_file.getAbsolutePath());
	    }

	// no ior file specified, try via property
	if (_ior_file_str == null){
	    _ior_file_str = Environment.getProperty("jacorb.imr.ior_file");
	    if (_ior_file_str == null){
		System.out.println("ERROR: Please specify a file for the IOR string!");
		System.out.println("Property jacorb.imr.ior_file or use the -i switch");

		System.exit(-1);
	    }
	}

	//set up server table backup file
	if (_backup_file_str == null){
	    _backup_file_str = Environment.getProperty("jacorb.imr.backup_file");      
	    if (_backup_file_str == null){
		System.out.println("WARNING: No backup file specified!\n" +
				   "Will create \"backup.dat\" in current directory, if necessary");
		_backup_file_str = "backup.dat";
	    }
	}

	File _backup_file = new File(_backup_file_str);
	try{
	    if ( _backup_file.exists()){
		if (! _backup_file.canWrite()){
		    System.out.println("WARNING: The backup file exists, but is not writable!");
		    System.out.println("Please check " + _backup_file.getAbsolutePath());
		}
		else{
		    System.out.println("WARNING: The backup file already exists and might get overwritten!");
		    System.out.println("Please check " + _backup_file.getAbsolutePath());
		}
 	    }
	    else{
		//testing the hard way, if the file can be created
		//with jdk1.2, we might try createNewFile()
		FileOutputStream _out = new FileOutputStream(_backup_file);
		_out.close();
		_backup_file.delete(); //don't leave empty files lying around
	    }
	}catch (Exception _e){
	    System.out.println("WARNING: The backup file is not accessible!");
	    System.out.println("Please check " + _backup_file.getAbsolutePath());
	}

	m_orb = (jacorb.orb.ORB) jacorb.orb.ORB.init(args,null);

	//Write IOR to file
	try{	  
	    POA poa = POAHelper.narrow(m_orb.resolve_initial_references("RootPOA"));
	    poa.the_POAManager().activate();

	    ImplementationRepositoryImpl _imr = new ImplementationRepositoryImpl
	      (_table_file, _backup_file, _new_table);
	
	    PrintWriter _out = new PrintWriter
	      (new FileOutputStream(new File(_ior_file_str)));

	    _out.println(m_orb.object_to_string(poa.servant_to_reference(_imr)));
	    _out.flush();
	    _out.close();
	}catch (Exception _e){
	  jacorb.util.Debug.output(2, _e);
	    System.out.println("ERROR: Failed to write IOR to file.\nPlease check the path.");
	    System.out.println("Property jacorb.imr.ior_file or -i <file> switch");
	    System.exit(-1);
	}

	m_orb.run();
    }
    
    /**
     * 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 extends Thread {
	private java.net.ServerSocket m_server_socket;
	private int m_port = 0;
	private String m_address;
	private int m_timeout = 0;
	private boolean m_run = true;
	private boolean m_wait = false;

	/**
	 * The constructor. It sets up the ServerSocket and starts the thread.
	 */
	public SocketListener(){
	    try {
		m_server_socket = new java.net.ServerSocket(m_default_port);
		m_address = java.net.InetAddress.getLocalHost().toString();
		if (m_address.indexOf("/") > 0)
		    m_address = m_address.substring(m_address.indexOf("/") + 1);
		
		m_port = m_server_socket.getLocalPort();
		if( m_port <0)
		    m_port += 65536;

		jacorb.util.Debug.output(2,"ImR Listener at " + m_port + ", " + m_address );
	    } 
	    catch (Exception e){
		e.printStackTrace();
		jacorb.util.Debug.output(2, e);
		jacorb.util.Debug.output(1, "Listener: Couldn't init");
		jacorb.util.Debug.output(2, e);
		System.exit(1);
	    }
	    setDaemon(true);
	    start();
	}
	   
	/**
	 * Get the port this SocketListener is listening on.
	 *
	 * @return the port
	 */ 
	public int getPort()
	{
	    jacorb.util.Debug.output(2,"ImR Listener at " + m_port + ", " + m_address );
	    return m_port;
	}

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

	/**
	 * Set the connection timeout.
	 *
	 * @param timeout the timeout.
	 */
	public void setTimeout( int timeout )
	{
	    m_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.
	 */
	public void run() 
	{
	    Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
	    while (m_run){
		try{
		    RequestReceptor _rr = new RequestReceptor (m_server_socket.accept(), m_timeout);
		}catch (Exception _e){
		    // when finishing, we do a close() on m_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 (m_run)
			jacorb.util.Debug.output(2, _e);
		}
	    }
	    
	    // doing the actual shutdown of the implementation repository here
	    m_orb.shutdown(m_wait);
	    System.exit(0);	    
	}

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

	    try{
		m_server_socket.close();
	    }catch (Exception _e){
		jacorb.util.Debug.output(2, _e);
	    }	   
	}
    }


    /** 
     * Inner class RequestReceptor, instantiated only by SocketListener
     * receives messages. There is one object of this class per 
     * client connection.
     */

    private class RequestReceptor extends Thread {
	private Socket m_client_socket;
	private Connection m_connection;
	private int m_timeout;
	private ReplyOutputStream m_out;
	private RequestInputStream m_in;
	
	public RequestReceptor(java.net.Socket s, int timeout){
	    m_client_socket = s;
	    m_timeout = timeout;
	    InetAddress _remote_host = s.getInetAddress();
	    InetAddress _local_host = null;
	    try {
		_local_host = InetAddress.getLocalHost();
	    } 
	    catch (Exception _e){
		// debug:
		_e.printStackTrace();
	    }

	    start();
	}


	/**
	 *	receive and dispatch requests 
	 */

	public void run() {
	    /* set up a connection object */
	    
	    try {
		m_connection = new Connection(m_orb, m_client_socket, true);
		if (m_timeout != 0) {
		    try{
			m_connection.setTimeOut (m_timeout);
		    } 
		    catch(SocketException _s){
			_s.printStackTrace();
		    }
		}
	    }
	    catch(Exception _e)
	    {
		jacorb.util.Debug.output(0, _e); 
		jacorb.util.Debug.output(0,"Fatal error in session setup.");
		System.exit(1);
	    }

	    /* receive requests */

	    try {
		while (true){
		    byte [] _buf = m_connection.readBuffer();
		    
		    // debug:
		    // System.out.println("BasicAdapter: got Buffer");
		    
		    int _msg_type = _buf[7];
		    
		    switch (_msg_type){
		    case org.omg.GIOP.MsgType_1_0._Request:
			{
			    // debug:
			    // System.out.println("BasicAdapter: got request");
			    replyNewLocation(_buf);
			    break;
			    
			} 
		    case org.omg.GIOP.MsgType_1_0._CancelRequest:
			{
			    //	org.omg.GIOP.CancelRequestHeader cancel_req_hdr =
			    //	org.omg.GIOP.CancelRequestHeaderHelper.read( ois );			
			    break;
			}
		    case org.omg.GIOP.MsgType_1_0._LocateRequest:
			{
			    replyNewLocation(_buf);
			    break;
			}
		    default:
			{
			    jacorb.util.Debug.output(0,"SessionServer, message_type " + 
						     _msg_type + " not understood.");
			}
		    }
		} 
	    }

	    catch ( java.io.EOFException eof )
	    {
		jacorb.util.Debug.output(4,eof);
	        close();
	    } 
	    catch ( org.omg.CORBA.COMM_FAILURE cf )
	    {
		jacorb.util.Debug.output(1,cf);
		close();
	    } 
	    catch ( java.io.IOException i )
	    {
		jacorb.util.Debug.output(4,i);
		close();
	    }
	}	
	
	public void close(){
	    try{
		if (m_client_socket != null){
		    m_connection.releaseConnection();
		    m_client_socket.close();
		}
	    } 
	    catch (Exception e){
		jacorb.util.Debug.output(2,e);
		// ignore exceptions on closing sockets which would occur e.g.
		// when closing sockets without ever having opened one...
	    }
	}

	/**
	 * 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[] buffer){
	    m_in = new RequestInputStream(m_connection, buffer);
	    String _poa_name = POAUtil.extractPOAName(m_in.req_hdr.object_key);

	    // look up POA in table
	    ImRPOAInfo _poa = m_server_table.getPOA(_poa_name);
	    if (_poa == null){
		sendSysException(new org.omg.CORBA.OBJECT_NOT_EXIST("POA " + _poa_name + " unknown"));
		return;
	    }

	    // get server of POA
	    ImRServerInfo _server = _poa.m_server;
	    jacorb.util.Debug.output(3, "ImR: Looking up: " + _server.m_name);

	    // server might be holding
	    _server.awaitRelease();

	    if (! _server.m_active){
	      jacorb.util.Debug.output(3, "ImR: server " + _server.m_name + " is down");
		//server is down;
		if (_server.m_command.length() == 0){
		    //server can't be restartet, send exception
		    sendSysException(new org.omg.CORBA.TRANSIENT("Server " + _server.m_name + 
								 " can't be restarted because " +
								 "of missing startup command"));
		    return;
		}
		else{
		    // 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.
		    // m_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 = m_server_table.getHost(_server.m_host);
			    jacorb.util.Debug.output(3, "ImR: will restart " + _server.m_name);
			    _host.startServer(_server.m_command, m_orb);
			} catch (Exception _e){
			    jacorb.util.Debug.output(2, _e);
			    
				// sth wrong with daemon, remove from table
			    if (! ( _e instanceof ServerStartupFailed))
				m_server_table.removeHost(_server.m_host);
			    
			    sendSysException(new org.omg.CORBA.TRANSIENT("Server startup failed"));
			    return;
			}			
		    }
		    else
		      jacorb.util.Debug.output(3, "ImR: somebody else is restarting " + 
					       _server.m_name);
		      
		}
	    }
	    else
	      jacorb.util.Debug.output(3, "ImR: server " + _server.m_name + " is active");
	    
	    // POA might not be active
	    boolean _old_poa_state = _poa.m_active;

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

	    // profile body contains new host and port of POA
	    // Version is just a dummy
	    ProfileBody_1_0 _body = new ProfileBody_1_0(new Version((byte) 1, (byte) 0), 
							_poa.m_host,
							(short) _poa.m_port,
							m_in.req_hdr.object_key);    

	    m_out = new ReplyOutputStream(m_connection,
					  new org.omg.IOP.ServiceContext[0],
					  m_in.req_hdr.request_id,
					  org.omg.GIOP.ReplyStatusType.LOCATION_FORWARD);

	    // The typecode is for org.omg.CORBA.Object, but avoiding creation of new
	    // ObjectHolder Instance.
	    org.omg.IOP.IOR _ior = ParsedIOR.createIOR("org.omg/CORBA/Object", _body);

	    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 = m_orb.string_to_object((new ParsedIOR(_ior)).getIORString());

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

			// This will usually throw an OBJECT_NOT_EXIST
			if (!_object._non_existent()) // "CORBA ping"
			    break; 
		    }catch(Exception _e){
			jacorb.util.Debug.output(4, _e);
		    }
		}		
	    }

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

		jacorb.util.Debug.output(2,"ImR: Sending location forward for " + _server.m_name);

		m_connection.sendReply(m_out);
	    }catch (Exception _e){
		jacorb.util.Debug.output(2, _e);
		sendSysException(new org.omg.CORBA.UNKNOWN(_e.toString()));
	    }
	}
    
	/**
	 * Convenience method for sending a CORBA System Exception back to
	 * the client.
	 *
	 * @param the exception to send back.
	 */
	private void sendSysException(org.omg.CORBA.SystemException sys_ex){
	    m_out = new ReplyOutputStream(m_connection,
					  new org.omg.IOP.ServiceContext[0],
					  m_in.req_hdr.request_id,
					  org.omg.GIOP.ReplyStatusType.SYSTEM_EXCEPTION);
	    
	    SystemExceptionHelper.write(m_out, sys_ex);
	    m_out.close();
	    try{
		m_connection.sendReply(m_out);
	    }catch (Exception _e){
		jacorb.util.Debug.output(2, _e);
	    }
	}
    }

} // ImplementationRepositoryImpl


