/*
 * @(#)BootstrapServer.java	1.34 01/12/03
 *
 * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package com.sun.corba.se.internal.CosNaming;

import java.lang.Thread;

import java.util.Enumeration;
import java.util.Properties;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

import org.omg.CORBA.*;
import com.sun.corba.se.internal.iiop.*;
import com.sun.corba.se.internal.core.*;
import com.sun.corba.se.internal.orbutil.CorbaResourceUtil;

/**
 * Class BootstrapServer is the main loop for the bootstrap server
 * implementation, listening for new connections and spawning threads
 * to handle requests on each new connection.
 * It uses BootstrapRequestHandler objects to actually
 * serve requests on the connections. The supported services is a
 * special properties object that ensures all accesses are synchronized;
 * if a file is supplied then the properties are loaded from and stored to
 * that file. The properties object is shared among all threads.
 * @see BootstrapServiceProperties
 * @see BootstrapRequestHandler
 */
public class BootstrapServer
{
    /**
     * Constructs a new BootstrapServer object and the
     * BootstrapServiceProperties object shared among all threads.
     * @param port the port on which the server will listen.
     * @parma file a file containing the supported properties
     * @param svcs a Properties defining the set of supported services.
     */
    public BootstrapServer(com.sun.corba.se.internal.iiop.ORB orb, int port,
			   File file, Properties svcs)
    {
	listenerPort = port;
	this.orb = orb;

	// Create a properties object with the file and properties
	supportedServices =  new BootstrapServiceProperties(file,svcs);

	// Create the server SC
	serverSC = new BootstrapRequestHandler(orb,supportedServices);

	// Register it with the orb for big and little indians.. eh, endians.
	orb.getSubcontractRegistry().registerBootstrapServer(serverSC);
    }

    /**
     * Start the BootstrapServer. It creates a listener thread on
     * the specified listenerport which will serve requests.
     */
    public void start() {
	// Create a listener thread on the port - this will process requests
	orb.getServerGIOP().getBootstrapEndpoint(listenerPort);
    }

    /**
     * Adds a service to the supported set of services. Delegates to
     * the shared BootstrapServiceProperties object.
     * @param key the key under which the service is known.
     * @param val the stringified object reference for the service.
     * @param save a boolean which is true if the key,value pair should be saved.
     * @return any previously bound value under the key.
     */
    public String addService(String key, String val, boolean save) {
	// Don't hesitate - delegate.
	return supportedServices.put(key,val,save);
    }

    /**
     * Main startup routine in case the bootstrap server is run standalone.
     * It first determines the port on which to listen, checks that the
     * specified file is available, and then creates the BootstrapServer object
     * that will service the requests.
     * @param args the command-line arguments to the main program.
     */
    public static final void main(String[] args)
    {
	String propertiesFilename = null;

	// Get an orb object. Narrow to specific ORB so we can use Wait().
	Properties props = new Properties() ;
	props.put( "org.omg.CORBA.ORBClass", "com.sun.corba.se.internal.iiop.ORB" ) ;
	com.sun.corba.se.internal.iiop.ORB orb = (com.sun.corba.se.internal.iiop.ORB)
	    org.omg.CORBA.ORB.init(args,props);

	// Determine the initial bootstrap port to use
	int initialPort = 900; // by convention
	try {
	    // Try environment
	    String ips = System.getProperty("org.omg.CORBA.ORBInitialPort");
	    if (ips != null && ips.length() > 0)
		initialPort = java.lang.Integer.parseInt(ips);
	} catch (java.lang.NumberFormatException e) {
	    // do nothing
	}
	// Process arguments
	for (int i=0;i<args.length;i++) {
	    // Look for the filename
	    if (args[i].equals("-InitialServicesFile") && i < args.length -1) {
		propertiesFilename = args[i+1];
	    }
	    // Was the initial port specified? If so, override
	    if (args[i].equals("-ORBInitialPort") && i < args.length-1) {
		initialPort = java.lang.Integer.parseInt(args[i+1]);
	    }
	}

	if (propertiesFilename == null) {
	    System.out.println(CorbaResourceUtil.getText("bootstrap.usage", "BootstrapServer"));
	    return;
	}

	// Create a file
	File file = new File(propertiesFilename);

	// Verify that if it exists, it is readable
	if (file.exists() == true && file.canRead() == false) {
	    System.err.println(CorbaResourceUtil.getText("bootstrap.filenotreadable", file.getAbsolutePath()));
	    return;
	}

	// Success: start up
	System.out.println(CorbaResourceUtil.getText("bootstrap.success", Integer.toString(initialPort), file.getAbsolutePath()));

	// We are ready: start the server with the computed file and
	// with an empty set of properties: it will be read from the file.
	BootstrapServer server = new BootstrapServer(orb,
						     initialPort,
						     file,
						     new Properties());

	// Start it
	server.start();

	// Stick around
	java.lang.Object sync = new java.lang.Object();
	try {
	    synchronized (sync) { sync.wait();}
	} catch (Exception ex) {}

    }

    public final static boolean debug = false;
    private int listenerPort;
    private Thread listenerThread;
    private BootstrapServiceProperties supportedServices;
    private BootstrapRequestHandler serverSC;
    private com.sun.corba.se.internal.iiop.ORB orb;
}

/**
 * Class BootstrapServiceProperties implements a synchronized properties set
 * that automatically gets loaded from the supplied file if the modification
 * timestamp changes, or stored to the file when updated. Not providing a
 * file implies running in memory with no persistent storage.
 * It only implements get(), put() and keys(); all of
 * these methods are synchronized to ensure correct updates.
 */
final class BootstrapServiceProperties
{
    /**
     * Constructs a BootstrapServiceProperties object.
     * The file and properties object reference are stored; loading is
     * done on demand.
     * @param pFile a file object which is the file containing the Properties.
     */
    public BootstrapServiceProperties(File pFile, Properties props) {
	// Do not bother loading now; wait for first request
	propFile = pFile;
	savedProps = props;
	allProps = new Properties(savedProps);
    }

    /**
   * Returns the value associated with the key, if any.
   * First the check() method is called to make sure we have an up to
   * date copy, and then the getProperty() method is called on the
   * Properties object. This method is synchronized so access gets
   * serialized.
   * @param key a string which is the key to look up.
   * @return a string which is the value associated with the key, or null
   * if no such key exists.
   */
    public synchronized String get(String key) {
	// Check first, then call properties
	this.check();
	return allProps.getProperty(key);
    }

    /**
   * Stores a value associated with a key, and optionally writes the
   * key,value pair to a file.
   * This method is synchronized so access gets
   * serialized.
   * @param key a string which is the key to store the value under.
   * @param val a string which is the value to store.
   * @param save a boolean which is true if the key,value pair should be saved.
   * @return any previously bound value.
   */
    public synchronized String put(String key,String val, boolean save) {
	// If we're not supposed to save it, just put it in and be done
	if (!save)
	    return (String)allProps.put(key,val);
	// Check first, then call properties
	String s = (String)savedProps.put(key,val);
	if (propFile != null) {
	    try {
		// Store file
		FileOutputStream fileOS = new FileOutputStream(propFile);
		// Clear and reload
		savedProps.save(fileOS,null);
		fileOS.close();
		// Update timestamp
		fileModified = propFile.lastModified();
	    } catch (java.io.FileNotFoundException e) {
		System.err.println(CorbaResourceUtil.getText("bootstrap.filenotfound", propFile.getAbsolutePath()));
	    } catch (java.io.IOException e) {
		System.err.println(CorbaResourceUtil.getText("bootstrap.exception",propFile.getAbsolutePath(), e.toString()));
	    }
	}
	return s;
    }

    /**
   * Returns an array of strings containing the list of available keys.
   * First the check() method is called to make sure we have an up to
   * date copy, and then an enumeration is created to iterate through
   * the properties object, collecting all keys in a string array.
   * @return an array of strings containing the available keys in the
   * Properties object.
   */
    public synchronized String[] keys() {
	// Ensure up to date
	this.check();
	// Compute list of keys
	String[] services = null;
	int size = allProps.size() + savedProps.size();
	if (size > 0) {
	    services = new String[size];

	    // Obtain all the keys from the property object
	    Enumeration theKeys = allProps.propertyNames();
	    for (int index=0;theKeys.hasMoreElements();index++) {
		services[index] = (String)theKeys.nextElement();
	    }
	}
	return services;
    }

    /**
   * Checks the lastModified() timestamp of the file and optionally
   * re-reads the Properties object from the file if newer.
   */
protected void check() {
    // Is there a file to read?
    if (propFile == null)
	return;
    // Assume we already have the object lock
    long lastMod = propFile.lastModified();
    // Up to date?
    if (lastMod > fileModified) {
	try {
	    // No, load it again.
	    FileInputStream fileIS = new FileInputStream(propFile);
	    // Clear and reload
	    savedProps.clear();
	    savedProps.load(fileIS);
	    fileIS.close();
	    // Set new timestamp
	    fileModified = lastMod;
	} catch (java.io.FileNotFoundException e) {
	    System.err.println(CorbaResourceUtil.getText("bootstrap.filenotfound", propFile.getAbsolutePath()));
	} catch (java.io.IOException e) {
	    System.err.println(CorbaResourceUtil.getText("bootstrap.exception",propFile.getAbsolutePath(), e.toString()));
	}
    }
}

    private File propFile;
private long fileModified;
private Properties savedProps;
private Properties allProps;
}

/**
 * Class BootstrapRequestHandler handles the requests coming to the
 * BootstrapServer. It implements Server so that it can be registered
 * as a subcontract. It is passed a BootstrapServiceProperties object
 * which contains
 * the supported ids and their values for the bootstrap service. This
 * Properties object is only read from, never written to, and is shared
 * among all threads.
 * <p>
 * The BootstrapRequestHandler responds primarily to GIOP requests,
 * but LocateRequests are (reluctantly) handled for graceful interoperability.
 * The BootstrapRequestHandler handles one request at a time.
 */
final class BootstrapRequestHandler implements ServerSubcontract
{
    public static final int OBJECT_KEY_BAD_LEN = 10000;
    public static final int OPERATION_NOT_GET_OR_LIST = 10001;
    public static final int JAVA_RUNTIME_EXC_CAUGHT = 10002;
    public static final int JAVA_EXC_CAUGHT = 10003;

    private com.sun.corba.se.internal.iiop.ORB orb;
    private BootstrapServiceProperties props;
    private static final boolean debug = false;

    public BootstrapRequestHandler(com.sun.corba.se.internal.iiop.ORB orb,
				   BootstrapServiceProperties props) {
	this.orb = orb;
	this.props = props;
    }

    public void destroyObjref(java.lang.Object objref)
    {
    }

    /**
     * Dispatch is called by the ORB and will serve get(key) and list()
     * invocations on the initial object key.
     */
    public com.sun.corba.se.internal.core.ServerResponse dispatch(
					       com.sun.corba.se.internal.core.ServerRequest request)
    {
	com.sun.corba.se.internal.core.ServerResponse response = null;

	try {
	    MarshalInputStream is = (MarshalInputStream) request;
	    String method = request.getOperationName();
	    response = request.createResponse(null);
	    MarshalOutputStream os = (MarshalOutputStream) response;

            if (method.equals("get")) {

                // Get the name of the requested service
                String service_key = is.read_string();

                // Look it up
                String service_value = props.get(service_key);
                org.omg.CORBA.Object service_obj = null;
                if (service_value != null) {
                    try {
                      	service_obj = orb.string_to_object(service_value);
                    } catch (org.omg.CORBA.SystemException e) {}
                }

                if (debug)
		    System.out.println("BootstrapRequestHandler: get(" +
				       service_key + ") -> " + service_value);

                // Write reply value
                os.write_Object(service_obj);

            } else if (method.equals("list")) {

                // Get all known keys
                String keys[] = props.keys();
                int keys_len = 0;
            	if (keys == null) keys = new String[0];
                else keys_len = keys.length;

                if (debug)
		    System.out.println("BootstrapRequestHandler: list() -> " +
				       keys_len + " entries.");

                // Write length of sequence
                os.write_long(keys_len);

                // Write each string in the sequence
                for (int i=0;i<keys_len;i++) {
              	    os.write_string(keys[i]);
            	}
	    } else {

                // Bad operation
		if (debug) System.out.println(method);
                throw new BAD_OPERATION(OPERATION_NOT_GET_OR_LIST,
					CompletionStatus.COMPLETED_NO);
            }

	} catch (org.omg.CORBA.SystemException ex) {
            // Marshal the exception thrown
            if (debug) ex.printStackTrace();
	    response = request.createSystemExceptionResponse(ex, null);
	} catch (java.lang.RuntimeException ex) {
            // Unknown exception
            if (debug) ex.printStackTrace();
	    response = request.createSystemExceptionResponse(
							     new UNKNOWN(ex.toString(),
									 JAVA_RUNTIME_EXC_CAUGHT,
									 CompletionStatus.COMPLETED_NO),
							     null);
	} catch (java.lang.Exception ex) {
            // Unknown exception
            if (debug) ex.printStackTrace();
            response = request.createSystemExceptionResponse(
							     new UNKNOWN(ex.toString(),
									 JAVA_EXC_CAUGHT,
									 CompletionStatus.COMPLETED_NO),
							     null);
	}

	return response;
    }

    /**
     * Locates the object mentioned in the locate requests, and returns
     * object here iff the object is the initial object key. A SystemException
     * thrown if the object key is not the initial object key.
     */
    public com.sun.corba.se.internal.core.IOR locate(
            com.sun.corba.se.internal.ior.ObjectKey objectKey) {
	return null;
    }

    /**
     * Not implemented; throws org.omg.CORBA.NO_IMPLEMENT().
     */
public java.lang.Object createObjref( IOR ior )
{
    throw new NO_IMPLEMENT();
}

/**
 * Not implemented; throws org.omg.CORBA.NO_IMPLEMENT().
 */
public java.lang.Object createObjref(byte[] key,
				     java.lang.Object servant)
{
    throw new NO_IMPLEMENT();
}

/**
 * Not implemented; throws org.omg.CORBA.NO_IMPLEMENT().
 */
public byte[] getKey(org.omg.CORBA.Object objref)
{
    throw new NO_IMPLEMENT();
}

/**
 * Not implemented; throws org.omg.CORBA.NO_IMPLEMENT().
 */
public int getImplId(org.omg.CORBA.Object objref)
{
    throw new NO_IMPLEMENT();
}

/**
 * isServantSupported should return null
 */
public boolean isServantSupported()
{
    return false;
}

/**
 * Not implemented; throws org.omg.CORBA.NO_IMPLEMENT().
 */
public java.lang.Object getServant(IOR ior) {
    throw new NO_IMPLEMENT();
}

/**
 * Not implemented; throws org.omg.CORBA.NO_IMPLEMENT().
 */
public void setOrb(com.sun.corba.se.internal.core.ORB orb) {
    throw new NO_IMPLEMENT();
}

/**
 * Not implemented; throws org.omg.CORBA.NO_IMPLEMENT().
 */
public void setId(int scid) {
    throw new NO_IMPLEMENT();
}

/**
 * Not implemented; throws org.omg.CORBA.NO_IMPLEMENT().
 */
public int getId() {
    throw new NO_IMPLEMENT();
}

/**
 * Not implemented; throws org.omg.CORBA.NO_IMPLEMENT().
 */
public java.lang.Class getClientSubcontractClass() {
    throw new NO_IMPLEMENT();
}
}
