/*
 * @(#)ServerTableEntry.java	1.23 01/12/03
 *
 * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * @(#)ServerTableEntry.java 1.1 97/11/30
 *
 * Copyright 1993-1997 Sun Microsystems, Inc. 901 San Antonio Road,
 * Palo Alto, California, 94303, U.S.A.  All Rights Reserved.
 *
 * This software is the confidential and proprietary information of Sun
 * Microsystems, Inc. ("Confidential Information").  You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into
 * with Sun.
 *
 * CopyrightVersion 1.2
 *
 */
package com.sun.corba.se.internal.Activation;

/**
 *
 * @version     1.3, 97/10/19
 * @author      Anita Jindal
 * @since       JDK1.2
 */

import org.omg.CORBA.INTERNAL;
import org.omg.CORBA.CompletionStatus;
 
import com.sun.corba.se.ActivationIDL.Server;
import com.sun.corba.se.ActivationIDL.EndPointInfo;
import com.sun.corba.se.ActivationIDL.ORBAlreadyRegistered;
import com.sun.corba.se.ActivationIDL.ORBPortInfo;
import com.sun.corba.se.ActivationIDL.InvalidORBid;
import com.sun.corba.se.ActivationIDL.ServerHeldDown;
import com.sun.corba.se.ActivationIDL.RepositoryPackage.ServerDef;
import com.sun.corba.se.ActivationIDL.IIOP_CLEAR_TEXT;
import com.sun.corba.se.internal.orbutil.ORBConstants;

import java.io.File;
import java.util.HashMap;
import java.util.Iterator;
import java.util.NoSuchElementException;

public class ServerTableEntry 
{

    private final static int DE_ACTIVATED = 0;
    private final static int ACTIVATING   = 1;
    private final static int ACTIVATED    = 2;
    private final static int RUNNING      = 3;
    private final static int HELD_DOWN    = 4;


    private String printState()
    {
	String str = "UNKNOWN";

	switch (state) {
	case (DE_ACTIVATED) : str = "DE_ACTIVATED"; break;
	case (ACTIVATING  ) : str = "ACTIVATING  "; break;
	case (ACTIVATED   ) : str = "ACTIVATED   "; break;
	case (RUNNING     ) : str = "RUNNING     "; break;
	case (HELD_DOWN   ) : str = "HELD_DOWN   "; break;
	default: break;
	}

	return str;
    }

    private final static long waitTime    = 2000;
    private static final int ActivationRetryMax = 5;

    // state of each entry
    private int state;
    private int serverId;
    private HashMap orbAndPortInfo;
    private Server serverObj;
    private ServerDef serverDef;
    private Process process;
    private int activateRetryCount=0;
    private String activationCmd;
    private String orbIdFile;


    public String toString()
    {
	return "ServerTableEntry[" + "state=" + printState() +
	    " serverId=" + serverId +
	    " activateRetryCount=" + activateRetryCount + "]" ;
    }

    // get the string needed to make the activation command
    private static String javaHome, classPath, fileSep, pathSep;

    static {
	javaHome  = System.getProperty("java.home");
	classPath = System.getProperty("java.class.path");
	fileSep   = System.getProperty("file.separator");
	pathSep   = System.getProperty("path.separator");
    }

    ServerTableEntry(int serverId, ServerDef serverDef, int initialPort,
		     String dbDirName, boolean verify, boolean debug )
    {
        this.serverId = serverId;
        this.serverDef = serverDef;
	this.debug = debug ;
	// create a HashMap with capacity 255
	// Since all methods are synchronized, we don't need any
	// additional synchronization mechanisms
	orbAndPortInfo = new HashMap(255);

	activateRetryCount = 0;
        state = ACTIVATING;

	// compute the activation command
	activationCmd = 
	
	    // add path to the java vm
	    javaHome + fileSep + "bin" + fileSep + "java " +

	    // add any arguments to the server Java VM
	    serverDef.serverVmArgs + " " + 

	    // add ORB properties
	    "-Dioser=" + System.getProperty( "ioser" ) + " " +
	    "-D" + ORBConstants.INITIAL_PORT_PROPERTY	+ "=" + initialPort + " " +
	    "-D" + ORBConstants.DB_DIR_PROPERTY		+ "=" + dbDirName + " " +
	    "-D" + ORBConstants.ACTIVATED_PROPERTY	+ "=true " + 
	    "-D" + ORBConstants.SERVER_ID_PROPERTY	+ "=" + serverId + " " +
	    "-D" + ORBConstants.SERVER_NAME_PROPERTY	+ "=" + serverDef.serverName + " " + 
	    // we need to pass in the verify flag, so that the server is not
	    // launched, when we try to validate its definition during registration
	    // into the RepositoryImpl

	    (verify ? "-D" + ORBConstants.SERVER_DEF_VERIFY_PROPERTY + "=true ": "") + 

	    // add classpath to the server
	    "-classpath " + classPath + 
	    (serverDef.serverClassPath.equals("") == true ? "" : pathSep) + 
	    serverDef.serverClassPath + 

	    // add server class name and arguments
	    " com.sun.corba.se.internal.Activation.ServerMain " + serverDef.serverArgs
	    
	    // Add the debug flag, if any
	    + (debug ? " -debug" : "") ;

	if (debug) System.out.println( 
				      "ServerTableEntry constructed with activation command " + 
				      activationCmd);
    }

    /** 
     * Verify whether the server definition is valid.
     */
    public int verify()
    {
	try {

	    if (debug)
	        System.out.println("Server being verified w/" + activationCmd);
	
            process = Runtime.getRuntime().exec(activationCmd);
	    int result = process.waitFor();
	    if (debug)
		printDebug( "verify", "returns " + ServerMain.printResult( result ) ) ;
	    return result ;
	} catch (Exception e) {
	    if (debug)
		printDebug( "verify", "returns unknown error because of exception " + 
			    e ) ;
	    return ServerMain.UNKNOWN_ERROR;
	}
    }

    private void printDebug(String method, String msg)
    {
	System.out.println("ServerTableEntry: method  =" + method);
	System.out.println("ServerTableEntry: server  =" + serverId);
	System.out.println("ServerTableEntry: state   =" + printState());
	System.out.println("ServerTableEntry: message =" + msg);
	System.out.println();
    }
	
    synchronized void activate() throws org.omg.CORBA.SystemException
    {
        state = ACTIVATED;

        try {
            if (debug)
		printDebug("activate", "activating server");
            process = Runtime.getRuntime().exec(activationCmd);
        } catch (Exception e) { 
            deActivate();
            if (debug)
		printDebug("activate", "throwing premature process exit");
            throw (new INTERNAL(MinorCodes.UNABLE_TO_START_PROCESS, 
				CompletionStatus.COMPLETED_NO));
        }
    }

    synchronized void register(Server server)
    {
        if (state == ACTIVATED) {

            serverObj = server;

            //state = RUNNING;
            //notifyAll();

            if (debug) 
		printDebug("register", "process registered back");

        } else {

            if (debug) 
		printDebug("register", "throwing premature process exit");
 
            throw (new INTERNAL(MinorCodes.SERVER_NOT_EXPECTED_TO_REGISTER, 
				CompletionStatus.COMPLETED_NO));
        }
    }

    synchronized void registerPorts( String orbId, EndPointInfo [] endpointList)
	throws ORBAlreadyRegistered
    {

	// find if the ORB is already registered, then throw an exception
	if (orbAndPortInfo.containsKey(orbId)) {
	    throw new ORBAlreadyRegistered(orbId);
	}

	// store all listener ports and their types 
	int numListenerPorts = endpointList.length;
	EndPointInfo [] serverListenerPorts = new EndPointInfo[numListenerPorts];

	for (int i = 0; i < numListenerPorts; i++) {
	    serverListenerPorts[i] = new EndPointInfo (endpointList[i].endpointType, endpointList[i].port);
	if (debug)
	    System.out.println("registering type: " + serverListenerPorts[i].endpointType  +  "  port  " + serverListenerPorts[i].port);
	}

	// put this set of listener ports in the HashMap associated
	// with the orbId
	orbAndPortInfo.put(orbId, serverListenerPorts);
        if (state == ACTIVATED) {
            state = RUNNING;
            notifyAll();
        }
        // _REVISIT_, If the state is not equal to ACTIVATED then it is a bug
        // need to log that error, once the Logging framework is in place
        // for rip-int.
        if (debug) 
	    printDebug("registerPorts", "process registered Ports");
    }
    
    synchronized void install()
    {
	if (state == RUNNING)
	    serverObj.install() ;
	else
	    throw new INTERNAL( MinorCodes.SERVER_NOT_RUNNING,
				CompletionStatus.COMPLETED_NO ) ;
    }

    synchronized void uninstall()
    {
	if (state == RUNNING) {

            deActivate();

 	    try {
	        if (serverObj != null) {
	            serverObj.shutdown(); // shutdown the server
	            serverObj.uninstall() ; // call the uninstall

                }
	        if (process != null) {
                    process.destroy();
	        }
	    } catch (Exception ex) {
	        // what kind of exception should be thrown
	    }
	} else {
	    throw new INTERNAL( MinorCodes.SERVER_NOT_RUNNING,
				CompletionStatus.COMPLETED_NO ) ;
	}
    }

    synchronized void holdDown() 
    {
        state = HELD_DOWN;

	if (debug) 
	    printDebug( "holdDown", "server held down" ) ;

        notifyAll();
    }

    synchronized void deActivate() 
    {
        state = DE_ACTIVATED;

	if (debug)
	    printDebug( "deActivate", "server deactivated" ) ;

        notifyAll();
    }

    synchronized void checkProcessHealth( ) {
        // If the State in the ServerTableEntry is RUNNING and the
        // Process was shut down abnormally, The method will change the
        // server state as De-Activated.
        if( state == RUNNING ) {
            try {
                int exitVal = process.exitValue();
            } catch (IllegalThreadStateException e1) {
                return;
            }
            synchronized ( this ) {
                // Clear the PortInformation as it is old 
                orbAndPortInfo.clear(); 
                // Move the state to De-Activated, So that the next
                // call to this server will re-activate.
                deActivate();
            }
        }
    }

    synchronized boolean isValid() 
    {
        if ((state == ACTIVATING) || (state == HELD_DOWN)) {
	    if (debug)
		printDebug( "isValid", "returns true" ) ;

            return true;
        }	

        try {
            int exitVal = process.exitValue();
        } catch (IllegalThreadStateException e1) {
            return true;
        }  
     
        if (state == ACTIVATED) {
            if (activateRetryCount < ActivationRetryMax) {
                if (debug)
		    printDebug("isValid", "reactivating server");
                activateRetryCount++;
                activate(); 
                return true;
            }
         
            if (debug)
		printDebug("isValid", "holding server down");

            holdDown();
            return true;
        }
 
        deActivate();
        return false;
    }

    synchronized ORBPortInfo[] lookup(String endpointType) throws ServerHeldDown 
    {
        while ((state == ACTIVATING) || (state == ACTIVATED)) {
            try {
                wait(waitTime);
                if (!isValid()) break;
            } catch(Exception e) {}
        }
	ORBPortInfo[] orbAndPortList = null;
     
        if (state == RUNNING) {
	    orbAndPortList = new ORBPortInfo[orbAndPortInfo.size()];
	    Iterator setORBids = orbAndPortInfo.keySet().iterator();

	    try {
		int numElements = 0;
		int i;
	        int port;
	        while (setORBids.hasNext()) {
		    String orbId = (String) setORBids.next();
		    // get an entry corresponding to orbId
		    EndPointInfo [] serverListenerPorts = (EndPointInfo []) orbAndPortInfo.get(orbId);
	            port = -1;
	            // return the port corresponding to the endpointType
	            for (i = 0; i < serverListenerPorts.length; i++) {
		        if (debug)
		            System.out.println("lookup num-ports " + serverListenerPorts.length + "   " + 
	                        serverListenerPorts[i].endpointType + "   " + 
	                        serverListenerPorts[i].port );
	                if ((serverListenerPorts[i].endpointType).equals(endpointType)) {
	                    port = serverListenerPorts[i].port;
			    break;
		        }
	            }
	            orbAndPortList[numElements] = new ORBPortInfo(orbId, port);
		    numElements++;
		}
	    } catch (NoSuchElementException e) {
	        // have everything in the table
	    }
	    return orbAndPortList;
        }
     
        if (debug) 
	    printDebug("lookup", "throwing server held down error");
 
        throw new ServerHeldDown( serverId ) ;
    }

    synchronized EndPointInfo[] lookupForORB(String orbId) 
	throws ServerHeldDown, InvalidORBid
    {
        while ((state == ACTIVATING) || (state == ACTIVATED)) {
            try {
                wait(waitTime);
                if (!isValid()) break;
            } catch(Exception e) {}
        }
	EndPointInfo[] portList = null;
     
        if (state == RUNNING) {

	    try {

		// get an entry corresponding to orbId
	        EndPointInfo [] serverListenerPorts = (EndPointInfo []) orbAndPortInfo.get(orbId);

	        portList = new EndPointInfo[serverListenerPorts.length];
	        // return the port corresponding to the endpointType
	        for (int i = 0; i < serverListenerPorts.length; i++) {
		   if (debug)
		      System.out.println("lookup num-ports " + serverListenerPorts.length + "   " 
	                     + serverListenerPorts[i].endpointType + "   " + 
	                     serverListenerPorts[i].port );
	           portList[i] = new EndPointInfo(serverListenerPorts[i].endpointType, serverListenerPorts[i].port);
	        }
	    } catch (NoSuchElementException e) {
	        // no element in HashMap corresponding to ORBid found
	        throw new InvalidORBid();
	    }
	    return portList;
        }
     
        if (debug) 
	    printDebug("lookup", "throwing server held down error");
 
        throw new ServerHeldDown( serverId ) ;
    }

    synchronized String[] getORBList()
    {
	String [] orbList = new String[orbAndPortInfo.size()];
	Iterator setORBids = orbAndPortInfo.keySet().iterator();

	try {
	    int numElements = 0;
	    while (setORBids.hasNext()) {
		String orbId = (String) setORBids.next();
		orbList[numElements++] = orbId ;
	    }
	} catch (NoSuchElementException e) {
	    // have everything in the table
	}
	return orbList;
    }
 
    int getServerId()
    {
	return serverId;
    }
    
    boolean isActive()
    {
        return (state == RUNNING) || (state == ACTIVATED);
    }

    synchronized void destroy()
    {

	deActivate();

	try {
	    if (serverObj != null) 
		serverObj.shutdown();

	    if (debug)
		printDebug( "destroy", "server shutdown successfully" ) ;
	} catch (Exception ex) {
	    if (debug)
		printDebug( "destroy", 
			    "server shutdown threw exception" + ex ) ;
	    // ex.printStackTrace();
	}

	try {
	    if (process != null) 
		process.destroy();

	    if (debug)
		printDebug( "destroy", "process destroyed successfully" ) ;
	} catch (Exception ex) {
	    if (debug)
		printDebug( "destroy", 
			    "process destroy threw exception" + ex ) ;

	    // ex.printStackTrace();
	}
    }

    private boolean debug = false;
}
