/*
 * @(#)InterceptorInvoker.java	1.28 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.Interceptors;

import org.omg.PortableInterceptor.LOCATION_FORWARD;
import org.omg.PortableInterceptor.SUCCESSFUL;
import org.omg.PortableInterceptor.SYSTEM_EXCEPTION;
import org.omg.PortableInterceptor.TRANSPORT_RETRY;
import org.omg.PortableInterceptor.USER_EXCEPTION;

import org.omg.PortableInterceptor.ClientRequestInfo;
import org.omg.PortableInterceptor.ClientRequestInterceptor;
import org.omg.PortableInterceptor.ForwardRequest;
import org.omg.PortableInterceptor.IORInterceptor;
import org.omg.PortableInterceptor.ServerRequestInfo;
import org.omg.PortableInterceptor.ServerRequestInterceptor;

import com.sun.corba.se.internal.corba.ClientDelegate;
import com.sun.corba.se.internal.core.ClientSubcontract;
import com.sun.corba.se.internal.core.IOR;
import com.sun.corba.se.internal.POA.POAImpl;
import com.sun.corba.se.internal.ior.IORTemplate;
             
import org.omg.CORBA.CompletionStatus;
import org.omg.CORBA.INTERNAL;
import org.omg.CORBA.ORB ;
import org.omg.CORBA.SystemException;
import org.omg.CORBA.portable.Delegate;
import org.omg.CORBA.portable.ObjectImpl;
        
/** 
 * Handles invocation of interceptors.  Has specific knowledge of how to
 * invoke IOR, ClientRequest, and ServerRequest interceptors.  
 * Makes use of the InterceptorList to retrieve the list of interceptors to 
 * be invoked.  Most methods in this class are package scope so that they
 * may only be called from the PIORB.
 */
public class InterceptorInvoker {

    // The ORB
    private ORB orb;
    
    // The list of interceptors to be invoked
    private InterceptorList interceptorList;

    // True if interceptors are to be invoked, or false if not
    // Note: This is a global enable/disable flag, whereas the enable flag
    // in the RequestInfoStack in PIORB is only for a particular Thread.
    private boolean enabled = false;

    // PICurrent variable.
    private PICurrent current;

    // NOTE: Be careful about adding additional attributes to this class.  
    // Multiple threads may be calling methods on this invoker at the same 
    // time.

    /**
     * Creates a new Interceptor Invoker.  Constructor is package scope so
     * only the ORB can create it.  The invoker is initially disabled, and
     * must be explicitly enabled using setEnabled().
     */
    InterceptorInvoker( ORB orb, InterceptorList interceptorList, 
                        PICurrent piCurrent ) 
    {
        this.orb = orb;
	this.interceptorList = interceptorList;
	this.enabled = false;
        this.current = piCurrent;
    }

    /**
     * Enables or disables the interceptor invoker
     */
    void setEnabled( boolean enabled ) {
	this.enabled = enabled;
    }
    
    /*
     **********************************************************************
     * IOR Interceptor invocation
     **********************************************************************/

    /**
     * Called when a new POA is created.
     *
     * @param poaImpl The POA associated with the IOR interceptor.
     */
    void invokeIORInterceptors( POAImpl poaImpl ) {
	// If invocation is not yet enabled, don't do anything.
	if( enabled ) {
	    // Create IORInfo object to pass to IORInterceptors:
	    IORInfoImpl info = new IORInfoImpl( orb, poaImpl );

	    // Call each IORInterceptor:
	    IORInterceptor[] iorInterceptors = 
                (IORInterceptor[])interceptorList.getInterceptors( 
                InterceptorList.INTERCEPTOR_TYPE_IOR );
	    int size = iorInterceptors.length;

	    // Implementation note:
	    // This loop counts backwards for greater efficiency.
	    // Benchmarks have shown that counting down is more efficient
	    // than counting up in Java for loops, as a compare to zero is
	    // faster than a subtract and compare to zero.  In this case,
	    // it doesn't really matter much, but it's simply a force of habit.

	    for( int i = (size - 1); i >= 0; i-- ) {
		IORInterceptor interceptor = iorInterceptors[i];
		try {
		    interceptor.establish_components( info );
	        }
		catch( Exception e ) {
		    // as per PI spec (orbos/99-12-02 sec 7.2.1), if
		    // establish_components throws an exception, ignore it.
		}
	    }
	}
    }

    /*
     **********************************************************************
     * Client Interceptor invocation
     **********************************************************************/

    /**
     * Invokes either send_request, or send_poll, depending on the value
     * of info.getStartingPointCall()
     */
    void invokeClientInterceptorStartingPoint( ClientRequestInfoImpl info ) {
	// If invocation is not yet enabled, don't do anything.
	if( enabled ) {
	    try {
		// Make a a fresh slot table available to TSC in case
		// interceptors need to make out calls.
		// Client's TSC is now RSC via RequestInfo.
		current.pushSlotTable( );
		info.setPICurrentPushed( true );
		info.setCurrentExecutionPoint( info.EXECUTION_POINT_STARTING );
		
		// Get all ClientRequestInterceptors:
		ClientRequestInterceptor[] clientInterceptors = 
		    (ClientRequestInterceptor[])interceptorList.
		    getInterceptors( InterceptorList.INTERCEPTOR_TYPE_CLIENT );
		int size = clientInterceptors.length;

		// We will assume that all interceptors returned successfully,
		// and adjust the flowStackIndex to the appropriate value if
		// we later discover otherwise.
		int flowStackIndex = size;
		boolean continueProcessing = true;
	
		// Determine whether we are calling send_request or send_poll:
		// (This is currently commented out because our ORB does not 
		// yet support the Messaging specification, so send_poll will
		// never occur.  Once we have implemented messaging, this may
		// be uncommented.)
		// int startingPointCall = info.getStartingPointCall();
		for( int i = 0; continueProcessing && (i < size); i++ ) {
		    try {
			clientInterceptors[i].send_request( info );
			
			// Again, it is not necessary for a switch here, since
			// there is only one starting point call type (see
			// above comment).
			
			//switch( startingPointCall ) {
			//case ClientRequestInfoImpl.CALL_SEND_REQUEST:
			    //clientInterceptors[i].send_request( info );
			    //break;
			//case ClientRequestInfoImpl.CALL_SEND_POLL:
			    //clientInterceptors[i].send_poll( info );
			    //break;
			//}

		    }
		    catch( ForwardRequest e ) {
			// as per PI spec (orbos/99-12-02 sec 5.2.1.), if
			// interception point throws a ForwardRequest, 
			// no other Interceptors' send_request operations are 
			// called.
			flowStackIndex = i;
			info.setForwardRequest( e );
			info.setEndingPointCall( 
			    ClientRequestInfoImpl.CALL_RECEIVE_OTHER );
			info.setReplyStatus( LOCATION_FORWARD.value );
			
			updateClientDelegateForwardRequest( info );
			
			// For some reason, using break here causes the VM on 
			// NT to lose track of the value of flowStackIndex 
			// after exiting the for loop.  I changed this to 
			// check a boolean value instead and it seems to work 
			// fine.
			continueProcessing = false;
		    }
		    catch( SystemException e ) {
			// as per PI spec (orbos/99-12-02 sec 5.2.1.), if
			// interception point throws a SystemException, 
			// no other Interceptors' send_request operations are 
			// called.
			flowStackIndex = i;
			info.setEndingPointCall( 
			    ClientRequestInfoImpl.CALL_RECEIVE_EXCEPTION );
			info.setReplyStatus( SYSTEM_EXCEPTION.value );
			info.setException( e );

			// For some reason, using break here causes the VM on 
			// NT to lose track of the value of flowStackIndex 
			// after exiting the for loop.  I changed this to 
			// check a boolean value instead and it seems to 
			// work fine.
			continueProcessing = false;
		    } 
		}
		
		// Remember where we left off in the flow stack:
		info.setFlowStackIndex( flowStackIndex );
	    }
	    finally {
		// Make the SlotTable fresh for the next interception point.
		current.resetSlotTable( );
	    }
        } // end enabled check
    }

    /**
     * Invokes either receive_reply, receive_exception, or receive_other,
     * depending on the value of info.getEndingPointCall()
     */
    void invokeClientInterceptorEndingPoint( ClientRequestInfoImpl info ) {
	// If invocation is not yet enabled, don't do anything.
	if( enabled ) {
	    try {
		// NOTE: It is assumed someplace else prepared a
		// fresh TSC slot table.

		info.setCurrentExecutionPoint( info.EXECUTION_POINT_ENDING );
		
		// Get all ClientRequestInterceptors:
		ClientRequestInterceptor[] clientInterceptors = 
		    (ClientRequestInterceptor[])interceptorList.
		    getInterceptors( InterceptorList.INTERCEPTOR_TYPE_CLIENT );
		int flowStackIndex = info.getFlowStackIndex();
		
		// Determine whether we are calling receive_reply, 
		// receive_exception, or receive_other:
		int endingPointCall = info.getEndingPointCall();

		// If we would be calling RECEIVE_REPLY, but this is a 
		// one-way call, override this and call receive_other:
		if( ( endingPointCall == 
		      ClientRequestInfoImpl.CALL_RECEIVE_REPLY ) &&
		    info.getIsOneWay() )
		{
		    endingPointCall = ClientRequestInfoImpl.CALL_RECEIVE_OTHER;
		    info.setEndingPointCall( endingPointCall );
		}
		
		// Only step through the interceptors whose starting points
		// have successfully returned.
		// Unlike the previous loop, this one counts backwards for a 
		// reason - we must execute these in the reverse order of the
		// starting points.
		for( int i = (flowStackIndex - 1); i >= 0; i-- ) {
		    
		    try {
			switch( endingPointCall ) {
			case ClientRequestInfoImpl.CALL_RECEIVE_REPLY:
			    clientInterceptors[i].receive_reply( info );
			    break;
			case ClientRequestInfoImpl.CALL_RECEIVE_EXCEPTION:
			    clientInterceptors[i].receive_exception( info );
			    break;
			case ClientRequestInfoImpl.CALL_RECEIVE_OTHER:
			    clientInterceptors[i].receive_other( info );
			    break;
			}
		    }
		    catch( ForwardRequest e ) {

			// as per PI spec (orbos/99-12-02 sec 5.2.1.), if
			// interception point throws a ForwardException, 
			// ending point call changes to receive_other.
			endingPointCall = 
			    ClientRequestInfoImpl.CALL_RECEIVE_OTHER;
			info.setEndingPointCall( endingPointCall );
			info.setReplyStatus( LOCATION_FORWARD.value );
			info.setForwardRequest( e );
			updateClientDelegateForwardRequest( info );
		    }
		    catch( SystemException e ) {

			// as per PI spec (orbos/99-12-02 sec 5.2.1.), if
			// interception point throws a SystemException, 
			// ending point call changes to receive_exception.
			endingPointCall = 
			    ClientRequestInfoImpl.CALL_RECEIVE_EXCEPTION;
			info.setEndingPointCall( endingPointCall );
			info.setReplyStatus( SYSTEM_EXCEPTION.value );
			info.setException( e );
		    } 
		}
	    }
	    finally {
		// See doc for setPICurrentPushed as to why this is necessary.
		// Check info for null in case errors happen before initiate.
		if (info != null && info.isPICurrentPushed()) {
		    current.popSlotTable( );
		    // After the pop, original client's TSC slot table
		    // remains avaiable via PICurrent.
		}
	    }
        } // end enabled check
    }

    /*
     **********************************************************************
     * Server Interceptor invocation
     **********************************************************************/

    /**
     * Invokes receive_request_service_context interception points.
     */
    void invokeServerInterceptorStartingPoint( ServerRequestInfoImpl info ) {
	// If invocation is not yet enabled, don't do anything.
	if( enabled ) {
	    try {
		// Make a fresh slot table for RSC.
		current.pushSlotTable();
		info.setSlotTable(current.getSlotTable());

		// Make a fresh slot table for TSC in case
		// interceptors need to make out calls.
		current.pushSlotTable( );

		info.setCurrentExecutionPoint( info.EXECUTION_POINT_STARTING );
		
		// Get all ServerRequestInterceptors:
		ServerRequestInterceptor[] serverInterceptors = 
		    (ServerRequestInterceptor[])interceptorList.
		    getInterceptors( InterceptorList.INTERCEPTOR_TYPE_SERVER );
		int size = serverInterceptors.length;

		// We will assume that all interceptors returned successfully,
		// and adjust the flowStackIndex to the appropriate value if
		// we later discover otherwise.
		int flowStackIndex = size;
		boolean continueProcessing = true;
		
		// Currently, there is only one server-side starting point
		// interceptor called receive_request_service_contexts.
		for( int i = 0; continueProcessing && (i < size); i++ ) {
		    
		    try {
			serverInterceptors[i].
			    receive_request_service_contexts( info );
		    }
		    catch( ForwardRequest e ) {
			// as per PI spec (orbos/99-12-02 sec 5.3.1.), if
			// interception point throws a ForwardRequest, 
			// no other Interceptors' starting points are 
			// called and send_other is called.
			flowStackIndex = i;
			info.setForwardRequest( e );
			info.setIntermediatePointCall(
			    ServerRequestInfoImpl.CALL_INTERMEDIATE_NONE );
			info.setEndingPointCall( 
			    ServerRequestInfoImpl.CALL_SEND_OTHER );
			info.setReplyStatus( LOCATION_FORWARD.value );

			// For some reason, using break here causes the VM on 
			// NT to lose track of the value of flowStackIndex 
			// after exiting the for loop.  I changed this to 
			// check a boolean value instead and it seems to work 
			// fine.
			continueProcessing = false;
		    }
		    catch( SystemException e ) {

			// as per PI spec (orbos/99-12-02 sec 5.3.1.), if
			// interception point throws a SystemException, 
			// no other Interceptors' starting points are 
			// called.
			flowStackIndex = i;
			info.setException( e );
			info.setIntermediatePointCall(
			    ServerRequestInfoImpl.CALL_INTERMEDIATE_NONE );
			info.setEndingPointCall( 
			    ServerRequestInfoImpl.CALL_SEND_EXCEPTION );
			info.setReplyStatus( SYSTEM_EXCEPTION.value );

			// For some reason, using break here causes the VM on 
			// NT to lose track of the value of flowStackIndex 
			// after exiting the for loop.  I changed this to 
			// check a boolean value instead and it seems to 
			// work fine.
			continueProcessing = false;
		    }
	    
		}
		
		// Remember where we left off in the flow stack:
		info.setFlowStackIndex( flowStackIndex );
	    }
	    finally {
		// The remaining points, ServantManager and Servant
		// all run in the same logical thread.
		current.popSlotTable( );
		// Now TSC and RSC are equivalent.
	    }
        } // end enabled check
    }

    /**
     * Invokes receive_request interception points
     */
    void invokeServerInterceptorIntermediatePoint( 
        ServerRequestInfoImpl info ) 
    {
        int intermediatePointCall = info.getIntermediatePointCall();
	// If invocation is not yet enabled, don't do anything.
	if( enabled && ( intermediatePointCall != 
                         ServerRequestInfoImpl.CALL_INTERMEDIATE_NONE ) )
        {
	    // NOTE: do not touch the slotStack.  The RSC and TSC are
	    // equivalent at this point.

            info.setCurrentExecutionPoint( info.EXECUTION_POINT_INTERMEDIATE );
            
            // Get all ServerRequestInterceptors:
            ServerRequestInterceptor[] serverInterceptors = 
                (ServerRequestInterceptor[])
                interceptorList.getInterceptors( 
                InterceptorList.INTERCEPTOR_TYPE_SERVER );
            int size = serverInterceptors.length;

            // Currently, there is only one server-side intermediate point
            // interceptor called receive_request.
            for( int i = 0; i < size; i++ ) {

                try {
                    serverInterceptors[i].receive_request( info );
                }
                catch( ForwardRequest e ) {

                    // as per PI spec (orbos/99-12-02 sec 5.3.1.), if
                    // interception point throws a ForwardRequest, 
                    // no other Interceptors' intermediate points are 
                    // called and send_other is called.
		    info.setForwardRequest( e );
                    info.setEndingPointCall( 
                        ServerRequestInfoImpl.CALL_SEND_OTHER );
                    info.setReplyStatus( LOCATION_FORWARD.value );
                    break;
                }
                catch( SystemException e ) {

                    // as per PI spec (orbos/99-12-02 sec 5.3.1.), if
                    // interception point throws a SystemException, 
                    // no other Interceptors' starting points are 
                    // called.
		    info.setException( e );
                    info.setEndingPointCall( 
                        ServerRequestInfoImpl.CALL_SEND_EXCEPTION );
                    info.setReplyStatus( SYSTEM_EXCEPTION.value );
                    break;
                }
            }
        } // end enabled check
    }

    /**
     * Invokes either send_reply, send_exception, or send_other,
     * depending on the value of info.getEndingPointCall()
     */
    void invokeServerInterceptorEndingPoint( ServerRequestInfoImpl info ) {
	// If invocation is not yet enabled, don't do anything.
	if( enabled ) {
	    try {
		// NOTE: do not touch the slotStack.  The RSC and TSC are
		// equivalent at this point.

		// REVISIT: This is moved out to PIORB until dispatch
		// path is rearchitected.  It must be there so that
		// it always gets executed so if an interceptor raises
		// an exception any service contexts added in earlier points
		// this point get put in the exception reply (via the SC Q).
		//info.setCurrentExecutionPoint( info.EXECUTION_POINT_ENDING );
		
		// Get all ServerRequestInterceptors:
		ServerRequestInterceptor[] serverInterceptors = 
		    (ServerRequestInterceptor[])interceptorList.
		    getInterceptors( InterceptorList.INTERCEPTOR_TYPE_SERVER );
		int flowStackIndex = info.getFlowStackIndex();
		
		// Determine whether we are calling 
		// send_exception, or send_other:
		int endingPointCall = info.getEndingPointCall();
		
		// Only step through the interceptors whose starting points
		// have successfully returned.
		for( int i = (flowStackIndex - 1); i >= 0; i-- ) {
		    try {
			switch( endingPointCall ) {
			case ServerRequestInfoImpl.CALL_SEND_REPLY:
			    serverInterceptors[i].send_reply( info );
			    break;
			case ServerRequestInfoImpl.CALL_SEND_EXCEPTION:
			    serverInterceptors[i].send_exception( info );
			    break;
			case ServerRequestInfoImpl.CALL_SEND_OTHER:
			    serverInterceptors[i].send_other( info );
			    break;
			}
		    }
		    catch( ForwardRequest e ) {
			// as per PI spec (orbos/99-12-02 sec 5.3.1.), if
			// interception point throws a ForwardException, 
			// ending point call changes to receive_other.
			endingPointCall = 
			    ServerRequestInfoImpl.CALL_SEND_OTHER;
			info.setEndingPointCall( endingPointCall );
			info.setForwardRequest( e );
			info.setReplyStatus( LOCATION_FORWARD.value );
			info.setForwardRequestRaisedInEnding();
		    }
		    catch( SystemException e ) {
			// as per PI spec (orbos/99-12-02 sec 5.3.1.), if
			// interception point throws a SystemException, 
			// ending point call changes to send_exception.
			endingPointCall = 
			    ServerRequestInfoImpl.CALL_SEND_EXCEPTION;
			info.setEndingPointCall( endingPointCall );
			info.setException( e );
			info.setReplyStatus( SYSTEM_EXCEPTION.value );
		    }
		}
		
		// Remember that all interceptors' starting and ending points
		// have already been executed so we need not do anything.
		info.setAlreadyExecuted( true );
	    }
            finally {
		// Get rid of the Server side RSC.
		current.popSlotTable();
	    }
        } // end enabled check
    }
    
    /*
     **********************************************************************
     * Private utility methods
     **********************************************************************/
    
    /**
     * Update the client delegate in the event of a ForwardRequest, given the 
     * information in the passed-in info object.
     */
    private void updateClientDelegateForwardRequest( 
        ClientRequestInfoImpl info ) 
    {
        ForwardRequest forwardRequest = info.getForwardRequestException();
        
        // ForwardRequest may be null if the forwarded IOR is set internal
        // to the ClientDelegate rather than explicitly through Portable
        // Interceptors.  In this case, we need not update the client 
        // delegate ForwardRequest object.
        if( forwardRequest != null ) {
            ClientDelegate clientDelegate = info.getClientDelegate();
            org.omg.CORBA.Object object = forwardRequest.forward;

            // Convert the forward object into an IOR:
            if( object instanceof ObjectImpl ) {
                Delegate delegate = ((ObjectImpl)object)._get_delegate();
                if( delegate == null ) {
                    throw new INTERNAL( 
                        "While converting Forward object into IOR, object " +
			"has no delegate.",
			MinorCodes.OBJECT_HAS_NO_DELEGATE,
			CompletionStatus.COMPLETED_NO );
                }
                else if( delegate instanceof ClientSubcontract ) {
                    IOR ior = ((ClientSubcontract)delegate).marshal();
                    clientDelegate.setLocatedIOR( ior );
                }
                else {
                    throw new INTERNAL(
                        "While converting Forward object into IOR, delegate " +
                        "is not instanceof ClientSubcontract",
			MinorCodes.DELEGATE_NOT_CLIENTSUB,
			CompletionStatus.COMPLETED_NO );
                }
            }
            else {
                throw new INTERNAL( 
                    "While converting Forward object into IOR, object is not "+
                    "instanceof ObjectImpl",
		    MinorCodes.OBJECT_NOT_OBJECTIMPL,
		    CompletionStatus.COMPLETED_NO );
            }
        }
    }
    
}
