/*
 *        JacORB - a free Java ORB
 *
 *   Copyright (C) 1997-98  Gerald Brose.
 *
 *   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 jacorb.orb;

/**
 * 
 * Class BasicAdapter, used by the POA.
 *
 * @author Gerald Brose, FU Berlin
 * @version $Id: BasicAdapter.java,v 1.18 2000/03/03 15:13:15 brose Exp $
 */

import java.io.*;
import java.net.*;
import java.util.*;
import org.omg.PortableServer.POA;
import org.omg.PortableServer.POAHelper;

public class BasicAdapter
{
    /** the number of outstanding replies. */
    private  int pendingReplies = 0;

    private  jacorb.orb.ORB orb; 
    private  POA rootPOA; 
    private  Listener listener;
    private  Listener sslListener; // bnv


    /**
     * The new BasicAdapter will create a Listener and, if the environment
     * supports it also
     */

    public BasicAdapter(jacorb.orb.ORB orb, POA rootPOA)
    {
	this.orb = orb;
	this.rootPOA = rootPOA;

	if ( jacorb.util.Environment.supportSSL()) 
	{
	    sslListener =
		new Listener ( orb,
			       rootPOA,
			       jacorb.util.Environment.getProperty("OASSLPort"),
//				bnv: if you don't use Credentials to setup your default SSL contexts
//				defaultSSLSocketFactory = jacorb.security.ssl.SSLSetup.getDefaultSSLServerSocketFactory ();
//				orb reference needed to access securityCurrent and then own_credentials
			       jacorb.security.ssl.SSLSetup.getDefaultSSLServerSocketFactory ( orb )
			       );
	    sslListener.is_ssl = true;
	    jacorb.util.Debug.output( 1, "SSL Listener on port = " + sslListener.port );
	}

	/*
	 * if SSL is not required, we create an plain socket listener as well,
	 * if it required, we do not offer any other port
	 */

       	if( jacorb.util.Environment.enforceSSL() )
	{
	    /* gb: sanity check: requiring SSL requires supporting it */
	    if( !jacorb.util.Environment.supportSSL ())
	    {
		throw new java.lang.Error("SSL required but not supported, cannot continue!");
	    }
	}

	listener = new Listener(orb, rootPOA);
    }

    public void replyPending()
    {
	pendingReplies++;
    }

    public int getPort()
    {
	return listener.getPort();
    }

    public int getSSLPort()
    {
	return sslListener.getPort();
    }

    public String getAddress()
    {
	return listener.getAddress();
    }

    public void setTimeout( int timeout )
    {
	listener.setTimeout( timeout );
    }
    
    /**
     * to be called from the POA, code duplicated for performance reasons to avoid
     * synchronization in the private version of this method.
     */

    public synchronized void deliverRequest( jacorb.orb.dsi.ServerRequest request, 
					     org.omg.PortableServer.POA poa )
    {
	jacorb.poa.POA tmp_poa = (jacorb.poa.POA)poa;
	String scopes[] = request.remainingPOAName();

	try
	{
	    for( int i=0; i < scopes.length-1; i++)
	    {
		if( scopes[i].equals(""))
		{
		    request.setRemainingPOAName(null);
		    break;
		}
		try
		{
		    tmp_poa = tmp_poa._getChildPOA( scopes[i] );
		}
		catch ( jacorb.poa.except.ParentIsHolding p )
		{
		    /* if one of the POAs is in holding state, we simply deliver 
		       deliver the request to this POA. It will forward the request
		       to its child POAs if necessary when changing back to active
		       For the POA to be able to forward this request to its child POAa,
		       we need to supply the remaining part of the child's POA name */

		    String [] rest_of_name = new String[scopes.length - i];
		    for( int j = 0; j < i; j++ )
			rest_of_name[j] = scopes[j+i];
		    request.setRemainingPOAName(rest_of_name);
		    break;
		}		
	    }	      

	    if( tmp_poa == null )
	    {
		throw new Error("request POA null!");
	    }
	    else
	    {
		/* hand over to the POA */
		((jacorb.poa.POA)tmp_poa)._invoke( request );
	    }
	      
	}
	catch( org.omg.PortableServer.POAPackage.WrongAdapter wa )
	{
	    // unknown oid (not previously generated)
	    request.setSystemException( new org.omg.CORBA.OBJECT_NOT_EXIST("unknown oid") );
	    request.reply();
	}
	catch( org.omg.CORBA.SystemException one )
	{
	    request.setSystemException( one );
	    request.reply();
	}
	catch( Throwable th )
	{
	    request.setSystemException( new org.omg.CORBA.UNKNOWN( th.toString()) );
	    request.reply();
	    th.printStackTrace(); // TODO
	}			
    }

    /** 
     * to be called from the POA
     */

    public synchronized void return_result(jacorb.orb.dsi.ServerRequest request)
    {
	pendingReplies--;
	request.reply();
    }



    /**
     * Inner class Listener, responsible for accepting connection requests
     */

    static class Listener
	extends Thread
    {
	java.net.ServerSocket serverSocket;
	int port = 0;
	String address;
	int timeout = 0;
	jacorb.orb.ORB orb;
	POA rootPOA;
	private boolean is_ssl = false;
	
	public Listener(jacorb.orb.ORB orb, POA poa)
	{
	    this.orb = orb;
	    rootPOA = poa;
	    try
	    {
		String oa_port = jacorb.util.Environment.getProperty("OAPort");
		String ip_addr = jacorb.util.Environment.getProperty("OAIAddr");

		if( ip_addr == null)
		{
		    if( oa_port != null )
			serverSocket = new java.net.ServerSocket( Integer.parseInt( oa_port) );
		    else
			serverSocket = new java.net.ServerSocket( 0 );
		    address = java.net.InetAddress.getLocalHost().toString();
		    if(  address.indexOf("/") > 0 )
			address = address.substring(address.indexOf("/")+1);
		}
		else
		{
		    java.net.InetAddress[] addrs = java.net.InetAddress.getAllByName( 
							   java.net.InetAddress.getLocalHost().getHostName());
		    java.net.InetAddress target_addr = null;
		    for( int i = 0; i < addrs.length; i++ )
		    {
			if( addrs[i].toString().endsWith( ip_addr )  )
			{
			    target_addr = addrs[i];
			    break;
			}
		    }
		    if( target_addr == null )
		    {
			System.err.println("[ Listener: Couldn't initialize, illegal ip addr " + ip_addr +" ]");
			System.exit(1);
		    }
		    if( oa_port != null )
			serverSocket = new java.net.ServerSocket( Integer.parseInt( oa_port), 20, target_addr );
		    else
			serverSocket = new java.net.ServerSocket( 0, 20, target_addr );
		    address = ip_addr;
		}
		port = serverSocket.getLocalPort();
	    } 
	    catch (Exception e) 
	    {
		jacorb.util.Debug.output(1,e);
		System.err.println("[ Listener: Couldn't initialize ]");
		System.exit(1);
	    }

	    this.setName("JacORB Listener Thread on port " + port );
	    this.setDaemon(true);
	    this.start();
	}

	/**
	 * Author bnv
	 * By now this method is called with the SSL port and socket factory
	 */
	public Listener( jacorb.orb.ORB orb,
			 POA poa,
			 String oa_port,
			 jacorb.util.net.ServerSocketFactory factory )
	// remark: 
	// this constructor has been made fully general and could be used with any socket factory.
	{
	    this.orb = orb;
	    rootPOA = poa;

	    if( factory == null )
		throw new java.lang.Error("No socket factory available!");

	    try
	    {
		String ip_addr = jacorb.util.Environment.getProperty("OAIAddr");

		if( ip_addr == null)
		{
		    if( oa_port != null )
			serverSocket = factory.createServerSocket ( Integer.parseInt( oa_port));
		    else
			serverSocket = factory.createServerSocket ( 0 );

		    address = java.net.InetAddress.getLocalHost().toString();
		    if(  address.indexOf("/") > 0 )
			address = address.substring(address.indexOf("/")+1);
		}
		else
		{
		    java.net.InetAddress[] addrs = java.net.InetAddress.getAllByName( 
							   java.net.InetAddress.getLocalHost().getHostName());
		    java.net.InetAddress target_addr = null;
		    for( int i = 0; i < addrs.length; i++ )
		    {
			if( addrs[i].toString().endsWith( ip_addr )  )
			{
			    target_addr = addrs[i];
			    break;
			}
		    }
		    if( target_addr == null )
		    {
			System.err.println("[ Listener: Couldn't initialize, illegal ip addr " + ip_addr +" ]");
			System.exit(1);
		    }
		    if( oa_port != null )
			serverSocket = factory.createServerSocket ( Integer.parseInt( oa_port), 20, target_addr );
		    else
			serverSocket = factory.createServerSocket ( 0, 20, target_addr );
		    address = ip_addr;
		}
		port = serverSocket.getLocalPort();
	    } 
	    catch (Exception e) 
	    {
		jacorb.util.Debug.output(1,e);
		System.err.println("[ Listener: Couldn't initialize ]");
		System.exit(1);
	    }

	    this.setName("JacORB Listener Thread on port " + port );
	    setDaemon(true);
	    start();
	}

	public int getPort()
	{
	    return port;
	}

	public String getAddress()
	{
	    return address;
	}

	public void setTimeout( int timeout )
	{
	    this.timeout = timeout;
	}
    
	       
	public void run() 
	{
	    Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
	    while( true )
	    {
		try
		{
		    RequestReceptor rr = 
			new RequestReceptor( orb, rootPOA, serverSocket.accept(), timeout);

		    if( is_ssl )
			jacorb.util.Debug.output(1,"SSL Listener completed session request...");
		} 
		catch (Exception e)
		{
		    e.printStackTrace();
		    System.err.println("Fatal Error:  cannot accept on socket");
		    System.exit(1);
		}
	    }
	}		
    }

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

    static class RequestReceptor
	extends Thread
    {
	private java.net.Socket clientSocket;
	private Connection connection;
	private int timeout;
	private jacorb.orb.ORB orb;
	private POA rootPOA;
	private boolean done = false;

	private boolean is_ssl; // bnv

	public RequestReceptor(jacorb.orb.ORB orb, 
			       POA rootPOA, 
			       java.net.Socket s, 
			       int timeout )
	{
	    this.orb = orb;
	    this.rootPOA = rootPOA;	  
	    clientSocket = s;
	    is_ssl = jacorb.security.ssl.SSLSetup.isSSL( s ); // bnv
	    this.timeout = timeout;
	    InetAddress remote_host = s.getInetAddress();
	    InetAddress local_host = null;
	    try
	    {
		local_host = InetAddress.getLocalHost();
	    } 
	    catch ( UnknownHostException uhe )
	    {
		// debug:
		uhe.printStackTrace();
	    }

	    this.setName("JacORB Request Receptor Thread on " + local_host );
	    this.start();
	}


	/**
	 *	receive and dispatch requests 
	 */

	public void run() 
	{
	    /* set up a connection object */	    
	    try
	    {
		connection = new Connection(orb, clientSocket, true );
		if( timeout != 0 )
		{
		    try
		    {
			connection.setTimeOut( timeout );
		    } 
		    catch( SocketException s )
		    {
			s.printStackTrace();
		    }
		}
	    }
	    catch(java.io.IOException ioex)
	    {
		jacorb.util.Debug.output(2, ioex); 
		jacorb.util.Debug.output(0,"Error in " + (is_ssl? "SSL ":"") + "session setup.");
		return;
	    }

	    /* receive requests */
	    try
	    {		
		while( !done ) 
		{
		    byte [] buf = connection.readBuffer();
		    
		    // debug:
		    // System.out.println("BasicAdapter: got Buffer");
		    
		    /* let the message-level interceptors do their job */

		    int msg_type = buf[7];
		    //if( Environment.serverInterceptMessages())
		    //buf = ((jacorb.orb.ORB)orb).server_messageIntercept_pre( buf );
		    
		    switch( msg_type )
		    {
		    case org.omg.GIOP.MsgType_1_0._Request:
			{
  			    // bnv: default SSL security policy
			    if( jacorb.util.Environment.enforceSSL() && !connection.isSSL()) 
			    {
				jacorb.orb.dsi.ServerRequest request = 
					new jacorb.orb.dsi.ServerRequest( orb, buf, connection );
				request.setSystemException (
					new org.omg.CORBA.NO_PERMISSION ( 3,  // SERVER_POLICY
						org.omg.CORBA.CompletionStatus.COMPLETED_NO
									  )
					);
				request.reply();
			    } 
			    else 
			    {
				// bnv: as before
				jacorb.orb.dsi.ServerRequest request = 
					new jacorb.orb.dsi.ServerRequest(orb, buf, connection );
				orb.getBasicAdapter().replyPending();
				
				// devik: look for codeset context if not negotiated yet
				if(!connection.isTCSNegotiated())
				{
					// look for codeset service context
					connection.setServerCodeSet(request.getServiceContext());
				}
				
				deliverRequest( request );
			    }
			    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:
			{
			    jacorb.orb.giop.LocateRequest request = 
					new jacorb.orb.giop.LocateRequest(buf, connection );
			    deliverRequest( request );
			    break;
			}
		    default:
			{
			    jacorb.util.Debug.output(0,"SessionServer, message_type " + 
					       msg_type + " not understood.");
			}
		    }
		} 
	    }
	//      catch ( java.io.InterruptedIOException eof )
//  	    {
//  		jacorb.util.Debug.output(2,"RequestReceptor: Connection timed out");

//  		while( pendingReplies > 0 )
//  		    try{ sleep( 5 ); } catch ( Exception e ){}
//  		connection.sendCloseConnection();
//  	    }
	    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( clientSocket != null )
		{
		    connection.closeConnection();
		    clientSocket.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...
	    }
	    done = true;
	}


	/* private code */

	private void deliverRequest( jacorb.orb.dsi.ServerRequest request )
	{
	    jacorb.poa.POA tmp_poa = (jacorb.poa.POA)rootPOA;
	
	    try
	    {
		//		String obj_key = new String(request.objectKey());
		String poa_name = jacorb.poa.util.POAUtil.extractPOAName(request.objectKey());

		/** strip scoped poa name (first part of the object key before "::",
		 *  will be empty for the root poa
		 */
		/*
		  if( !(obj_key.startsWith(jacorb.poa.POAConstants.OBJECT_KEY_SEPARATOR+
		  jacorb.poa.POAConstants.OBJECT_KEY_SEPARATOR)))
		  {
		  poa_name = obj_key.substring(0,
		  obj_key.indexOf(jacorb.poa.POAConstants.OBJECT_KEY_SEPARATOR+
		  jacorb.poa.POAConstants.OBJECT_KEY_SEPARATOR) );
		  }
		*/
		java.util.StringTokenizer strtok = 
		    new java.util.StringTokenizer(poa_name, jacorb.poa.POAConstants.OBJECT_KEY_SEPARATOR );

		String scopes[]  = new String[strtok.countTokens()];

		for( int i = 0; strtok.hasMoreTokens(); scopes[i++] = strtok.nextToken() );

		for( int i = 0; i < scopes.length; i++)
		{
		    if( scopes[i].equals(""))
			break;

		    /* the following is a call to a method in the private
		       interface between the ORB and the POA. It does the
		       necessary synchronization between incoming,
		       potentially concurrent requests to activate a POA
		       using its adapter activator. This call will block
		       until the correct POA is activated and ready to
		       service requests. Thus, concurrent calls
		       originating from a single, multi-threaded client
		       will be serialized because the thread that accepts
		       incoming requests from the client process is
		       blocked. Concurrent calls from other destinations
		       are not serialized unless they involve activating
		       the same adapter.  */

		    try
		    {
			tmp_poa = tmp_poa._getChildPOA( scopes[i] );
		    }
		    catch ( jacorb.poa.except.ParentIsHolding p )
		    {
			/* if one of the POAs is in holding state, we simply deliver 
			   deliver the request to this POA. It will forward the request
			   to its child POAs if necessary when changing back to active
			   For the POA to be able to forward this request to its child POAa,
			   we need to supply the remaining part of the child's POA name */

			String [] rest_of_name = new String[scopes.length - i];
			for( int j = 0; j < i; j++ )
			    rest_of_name[j] = scopes[j+i];
			request.setRemainingPOAName(rest_of_name);
			break;
		    }		
		}
	      

		if( tmp_poa == null )
		{
		    throw new Error("request POA null!");
		}
		else
		{
		    /* hand over to the POA */
		    ((jacorb.poa.POA)tmp_poa)._invoke( request );
		}
	      
	    }
	    //	    catch( org.omg.PortableServer.POAPackage.AdapterNonExistent ane )
	    //{
	    //request.setSystemException( new org.omg.CORBA.OBJECT_NOT_EXIST("POA: AdapterNonExistent"));
	    //request.reply();
	    //}
	    catch( org.omg.PortableServer.POAPackage.WrongAdapter wa )
	    {
		// unknown oid (not previously generated)
		request.setSystemException( new org.omg.CORBA.OBJECT_NOT_EXIST("unknown oid") );
		request.reply();
	    }
	    catch( org.omg.CORBA.SystemException one )
	    {
		request.setSystemException( one );
		request.reply();
	    }
	    catch( Throwable th )
	    {
		request.setSystemException( new org.omg.CORBA.UNKNOWN( th.toString()) );
		request.reply();
		th.printStackTrace(); // TODO
	    }			
	}

    }


}
