/*
 * @(#)RequestImpl.java	1.61 01/12/03
 *
 * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Licensed Materials - Property of IBM
 * RMI-IIOP v1.0
 * Copyright IBM Corp. 1998 1999  All Rights Reserved
 *
 * US Government Users Restricted Rights - Use, duplication or
 * disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
 */

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


import org.omg.CORBA.Any;
import org.omg.CORBA.ARG_IN;
import org.omg.CORBA.ARG_OUT;
import org.omg.CORBA.ARG_INOUT;
import org.omg.CORBA.Context;
import org.omg.CORBA.ContextList;
import org.omg.CORBA.Environment;
import org.omg.CORBA.ExceptionList;
import org.omg.CORBA.NVList;
import org.omg.CORBA.NamedValue;
import org.omg.CORBA.Request;
import org.omg.CORBA.SystemException;
import org.omg.CORBA.TCKind;
import org.omg.CORBA.TypeCode;
import org.omg.CORBA.TypeCodePackage.BadKind;
import org.omg.CORBA.UnknownUserException;
import org.omg.CORBA.Bounds;
import org.omg.CORBA.UNKNOWN;
import org.omg.CORBA.INTERNAL;
import org.omg.CORBA.NO_IMPLEMENT;
import org.omg.CORBA.CompletionStatus;
import org.omg.CORBA.WrongTransaction;

import org.omg.CORBA.portable.*;

import com.sun.corba.se.internal.corba.ClientDelegate;
import com.sun.corba.se.internal.core.*;
import com.sun.corba.se.internal.iiop.messages.ReplyMessage;
import com.sun.corba.se.internal.orbutil.MinorCodes;



public class RequestImpl extends Request {

    ///////////////////////////////////////////////////////////////////////////
    // data members

    protected ObjectImpl		 _target	= null;
    protected String       		 _opName	= null;
    protected NVList 		 _arguments 	= null;
    protected ExceptionList		 _exceptions	= null;
    private NamedValue		 _result  	= null;
    protected Environment		 _env		= null;
    private Context		 _ctx		= null;
    private ContextList		 _ctxList	= null;
    protected ORB			 _orb		= null;

    // invocation-specific stuff

    protected boolean 		 _isOneWay	= false;
    private int[]			 _paramCodes	= null;
    private long[]		 _paramLongs	= null;
    private java.lang.Object[] 	 _paramObjects	= null;

    // support for deferred invocations. 
    // protected instead of private since it needs to be set by the
    // thread object doing the asynchronous invocation.
    protected boolean 		 gotResponse 	= false;

    ///////////////////////////////////////////////////////////////////////////
    // constructor

    protected RequestImpl (ORB orb,
			   org.omg.CORBA.Object targetObject,
			   Context ctx,
			   String operationName,
			   NVList argumentList,
			   NamedValue resultContainer,
			   ExceptionList exceptionList,
			   ContextList ctxList)
    {

        // initialize the orb
        _orb 	= orb;

        // initialize target, context and operation name
        _target     = (ObjectImpl) targetObject;
        _ctx	= ctx;
        _opName	= operationName;

        // initialize argument list if not passed in
        if (argumentList == null)
            _arguments = new NVListImpl(_orb);
        else
            _arguments = argumentList;

        // set result container. 
        _result = resultContainer;

        // initialize exception list if not passed in
        if (exceptionList == null)
            _exceptions = new ExceptionListImpl();
        else
            _exceptions = exceptionList;

        // initialize context list if not passed in
        if (ctxList == null)
            _ctxList = new ContextListImpl(_orb);
        else
            _ctxList = ctxList;

        // initialize environment 
        _env	= new EnvironmentImpl();

    }

    public org.omg.CORBA.Object target()
    {
        return _target;
    }

    public String operation() 
    {
        return _opName;
    }

    public NVList arguments() 
    {
        return _arguments;
    }
    
    public NamedValue result() 
    {
        return _result;
    }
    
    public Environment env() 
    {
        return _env;
    }
    
    public ExceptionList exceptions() 
    {
        return _exceptions;
    }
    
    public ContextList contexts() 
    {
        return _ctxList;
    }
    
    public synchronized Context ctx() 
    {
        if (_ctx == null)
            _ctx = new ContextImpl(_orb);
        return _ctx;
    }
    
    public synchronized void ctx(Context newCtx) 
    {
        _ctx = newCtx;
    }

    public synchronized Any add_in_arg()
    {
        return _arguments.add(org.omg.CORBA.ARG_IN.value).value();
    }

    public synchronized Any add_named_in_arg(String name)
    {
        return _arguments.add_item(name, org.omg.CORBA.ARG_IN.value).value();
    }

    public synchronized Any add_inout_arg()
    {
        return _arguments.add(org.omg.CORBA.ARG_INOUT.value).value();
    }

    public synchronized Any add_named_inout_arg(String name)
    {
        return _arguments.add_item(name, org.omg.CORBA.ARG_INOUT.value).value();
    }

    public synchronized Any add_out_arg()
    {
        return _arguments.add(org.omg.CORBA.ARG_OUT.value).value();
    }

    public synchronized Any add_named_out_arg(String name)
    {
        return _arguments.add_item(name, org.omg.CORBA.ARG_OUT.value).value();
    }

    public synchronized void set_return_type(TypeCode tc)
    {
        if (_result == null)
            _result = new NamedValueImpl(_orb);
        _result.value().type(tc);
    }

    public synchronized Any return_value()
    {
        if (_result == null)
            _result = new NamedValueImpl(_orb);
        return _result.value();
    }

    public synchronized void add_exception(TypeCode exceptionType)
    {
        _exceptions.add(exceptionType);
    }
    
    public synchronized void invoke()
    {
        doInvocation();
    }

    public synchronized void send_oneway()
    {
        _isOneWay = true;
        doInvocation();
    }
    
    public synchronized void send_deferred()
    {
        AsynchInvoke invokeObject = new AsynchInvoke((com.sun.corba.se.internal.corba.ORB) _orb, this, false);
        new Thread(invokeObject).start();
    }
    
    public synchronized boolean poll_response()
    {
        // this method has to be synchronized even though it seems
        // "readonly" since the thread object doing the asynchronous
        // invocation can potentially update this variable in parallel.
        // updates are currently simply synchronized againt the request
        // object. 
        return gotResponse;
    }
    
    public synchronized void get_response()
        throws org.omg.CORBA.WrongTransaction
    {
        while (gotResponse == false) {
            // release the lock. wait to be notified by the thread that is
            // doing the asynchronous invocation.
            try {
	        wait();
            } 
	    catch (InterruptedException e) {}
        }
    }
    
    ///////////////////////////////////////////////////////////////////////////
    // private helper methods

    /*
     * The doInvocation operation is where the real mechanics of
     * performing the request invocation is done.
     */

    protected void doInvocation() {
        // Get delegate

        // CHANGE(RAM J) (05/01/2000) changed ClientSubContract to ClientDelegate
        //ClientSubcontract delegate = (ClientSubcontract)_target._get_delegate();
	// Initiate Client Portable Interceptors.  Inform the PIORB that
	// this is a DII request so that it knows to ignore the second
	// inevitable call to initiateClientPIRequest in createRequest.
	// Also, save the RequestImpl object for later use.
	_orb.initiateClientPIRequest( true );
	_orb.setClientPIInfo( this );

        ClientDelegate delegate = (ClientDelegate)_target._get_delegate();
        ClientRequest req;
        Exception exception=null;
        ClientResponse resp=null;

	// Note: This try/catch was added for PI.  It is analogous to the
	// try / catch / finally found below for the line 
	// "resp = delegate.invoke(req);".  The finally is moved into the
	// catch body of SystemException since it should only be executed
	// if the request creation failed.
	try {
	    req = delegate.createRequest(_opName, _isOneWay);
	}
	catch( SystemException ex ) {
	    _env.exception(ex);
	    exception = ex;

	    // Note, this try block is copied directly from the one that
	    // appears later in this method.  Please synchronize changes.
	    // _REVISIT_ This code needs to be reviewed by Ram.
            try {

		_orb.sendCancelRequestIfFinalFragmentNotSent();

                // REVISIT: Talk to sanjeevk and verify if this signature of
                // is releaseReply is important to DII. Can we just use one
                // releaseReply in ClientDelegate, instead of two?

		// _REVISIT_ The call to cleanupClientPIRequest should
		// really should go in releaseReply().
		// However, because GenericPOAClientSC:305 v1.54 is 
		// explicitly calling releaseReply, this would cause 
		// cleanup to occur twice.  Ask Ram why releaseReply is 
		// being called instead of receivedReply.

		// Invoke Portable Interceptors cleanup.  This is done to
		// handle exceptions during stream marshalling.
		_orb.cleanupClientPIRequest();

                delegate.releaseReply(resp, _opName, exception);
            } catch ( WrongTransaction ex2 ) {
                // XXX return this for deferred sends
                throw new NO_IMPLEMENT(MinorCodes.SEND_DEFERRED_NOTIMPLEMENTED,
                               CompletionStatus.COMPLETED_MAYBE);
            } catch ( SystemException ex2 ) {
                // substitute this exception for original exception
                _env.exception(ex2);
                throw ex2;
            }

	    throw ex;
	}

        OutputStream os = (OutputStream)req;

        // Marshal args
        try {
            for (int i=0; i<_arguments.count() ; i++) {
	        NamedValue nv = _arguments.item(i);
                switch (nv.flags()) {
		        case ARG_IN.value:
                    nv.value().write_value(os);
                    break;
		        case ARG_OUT.value:
		            break;
		        case ARG_INOUT.value:
                    nv.value().write_value(os);
                    break;
	            }
            }
        } catch ( org.omg.CORBA.Bounds ex ) {
	        // Cannot happen since we only iterate till _arguments.count()
        }

        try { // this outer try block ensures we do a req.completeRequest.

            // Invoke
            try {
                resp = delegate.invoke(req);
            } catch (SystemException ex) {
		// Pass SystemException through Portable Interceptors'
		// ending points, and then
                // set the SystemException in the env and rethrow it.
                exception = _orb.invokeClientPIEndingPoint(
		    ReplyMessage.SYSTEM_EXCEPTION, ex );
		continueOrThrowSystemOrRemarshal( exception );

		// Note: We should never need to execute this line,
		// but we should assert in case exception was set to null
		// somehow.
		throw new INTERNAL(
		    "Assertion failed: exception should not be null ." );
            }

            if (_isOneWay) {
		// Invoke Portable Interceptors with receive_other
		exception = _orb.invokeClientPIEndingPoint(
		    ReplyMessage.NO_EXCEPTION, exception );
                continueOrThrowSystemOrRemarshal( exception );
                return;
	    }

            // Process reply
            InputStream is = (InputStream) resp;

            if (resp.isSystemException()) {

                // Unmarshal the SystemException, set it in the env and throw it.
                SystemException se = resp.getSystemException();

                boolean doRemarshal = false;

                // FIX(Ram J) (05/01/2000) added locatedIOR = ior
                // and retry the request from root ior,
                // if system exception is COMM_FAILURE.
                // WARNING: There is a risk of infinite loopback
                // if the requests on location forwarded ior result in
                // system exception (COMM_FAILURE)
                if (se instanceof org.omg.CORBA.COMM_FAILURE
                     && se.completed == CompletionStatus.COMPLETED_NO) {
                    if (delegate.locatedIOR != delegate.ior) {
                        delegate.locatedIOR = delegate.ior; // retry from root ior
                        doRemarshal = true;
                    }
                }

                if (se.minor == MinorCodes.CONN_CLOSE_REBIND &&
                    (se instanceof org.omg.CORBA.COMM_FAILURE)) {
                    
                    doRemarshal = true;
                }
                
                if (doRemarshal) {
                    // Invoke Portable Interceptors with receive_exception:
                    exception = _orb.invokeClientPIEndingPoint(
                        ReplyMessage.SYSTEM_EXCEPTION, se );

                    // If PI did not change the exception, handle
                    // COMM_FAILURE by recursively calling doInvocation.
                    // Otherwise, throw the exception PI wants thrown.
                    if( se == exception ) {
                        doInvocation();
                    }
                    else {
                        continueOrThrowSystemOrRemarshal( exception );
                        throw new INTERNAL(
                            "Assertion failed in RequestImpl. " +
                            "exception should not be null." );
                    }

                    return;
                }

		// Invoke Portable Interceptors with receive_exception.
                exception = _orb.invokeClientPIEndingPoint(
		    ReplyMessage.SYSTEM_EXCEPTION, se );
		continueOrThrowSystemOrRemarshal( exception );

		// Note: We should never need to execute this line, but
		// we should assert in case exception was set to null somehow
		throw new INTERNAL(
		    "Assertion failed in RequestImpl:  " +
		    "exception should not be null." );
            } else if (resp.isUserException()) {
                // Peek exception's repository id
                String exid = resp.peekUserExceptionId();

                try {
                    // Find the typecode for the exception
                    for (int i=0; i<_exceptions.count() ; i++) {
                        TypeCode tc = _exceptions.item(i);
                        if ( tc.id().equals(exid) ) {
                            // Since we dont have the actual user exception
                            // class, the spec says we have to create an
                            // UnknownUserException and put it in the
                            // environment.
                            Any eany = _orb.create_any();
                            eany.read_value(is, (TypeCode)tc);
                            exception = new UnknownUserException(eany);

                            // _REVISIT_ Understand why this is 
                            // UnknownUserException.

			    // Invoke Portable Interceptors with 
			    // receive_exception (user exception)
			    Exception newException = 
				_orb.invokeClientPIEndingPoint(
				ReplyMessage.USER_EXCEPTION, exception );
			    // Note that continueOrThrowSystemOrRemarshal
			    // will not call _env.exception in the case of
			    // a UserException.  We must do so explicitly here.
                            _env.exception(newException);
                            if( exception != newException ) {
				exception = newException;
				continueOrThrowSystemOrRemarshal( exception );
			    }

                            return;
                        }
                    }
                } catch (Exception b) {
                    // Only exceptions to be caught are Bounds and BadKind.
                    // Both cannot happen here.
                }

                // must be a truly unknown exception
                SystemException u = new UNKNOWN(MinorCodes.UNKNOWN_CORBA_EXC,
                                CompletionStatus.COMPLETED_MAYBE);

		// Invoke Portable Interceptors with receive_exception:
                exception = _orb.invokeClientPIEndingPoint(
		    ReplyMessage.SYSTEM_EXCEPTION, u );
		continueOrThrowSystemOrRemarshal( exception );

		// Note: We should never need to execute this line, but
		// we should assert in case exception was set to null somehow.
		throw new INTERNAL(
		    "Assertion failed in RequestImpl : " +
		    "exception should not be null." );
            } else if (resp.isLocationForward()) {
                // FIXED(Ram J) (05/01/2000) added setting delegate.locatedIOR
                //  and reinvoking.
                delegate.locatedIOR = resp.getForwardedIOR();

		// Invoke Portable Interceptors with receive_other:
		exception = _orb.invokeClientPIEndingPoint(
		    ReplyMessage.LOCATION_FORWARD, null );

                // If PI did not raise exception, invoke again.  Otherwise
		// throw the exception PI wants thrown.
		if( exception == null ) {
                    doInvocation(); // invoke again.
		}
		else {
		    continueOrThrowSystemOrRemarshal( exception );
		}

                return;
                /*
                // Location forwards are handled internally in the subcontract.
                throw new INTERNAL(MinorCodes.LOCATIONFORWARD_ERROR,
                           CompletionStatus.COMPLETED_NO);
                */
            } else if (resp.isDifferentAddrDispositionRequested()) {
                // set the desired target addressing disposition.
                delegate.addressingDisposition = resp.getAddrDisposition();

		// Invoke Portable Interceptors with receive_other:
		exception = _orb.invokeClientPIEndingPoint(
		    ReplyMessage.NEEDS_ADDRESSING_MODE, null);

                // If PI did not raise exception, invoke again.  Otherwise
		// throw the exception PI wants thrown.
		if( exception == null ) {
                    doInvocation(); // invoke again.
		}
		else {
		    continueOrThrowSystemOrRemarshal( exception );
		}

                return;                
            } else { // normal return
                // Unmarshal return args
                unmarshalParams(is);

		// Invoke Portable Interceptors with receive_reply:
		exception = _orb.invokeClientPIEndingPoint(
		    ReplyMessage.NO_EXCEPTION, null );

		// Remember: not thrown if exception is null.
		continueOrThrowSystemOrRemarshal( exception );
            }
        } finally {
	    // _REVISIT_ PI Note: Any exceptions in the following try 
	    // block happen after PI endpoint has already run so they are not
	    // reported to interceptors.

	    // Note that changes to this try block should be synchronized with
	    // the identical try block above.
            try {

		_orb.sendCancelRequestIfFinalFragmentNotSent();

                // REVISIT: Talk to sanjeevk and verify if this signature of
                // is releaseReply is important to DII. Can we just use one
                // releaseReply in ClientDelegate, instead of two?

		// _REVISIT_ See above revisit for same 
		// cleanupClientPIRequest call.

		// Invoke Portable Interceptors cleanup.  This is done to
		// handle exceptions during stream marshalling.  
		_orb.cleanupClientPIRequest();

                delegate.releaseReply(resp, _opName, exception);
            } catch ( WrongTransaction ex ) {
                // XXX return this for deferred sends
                throw new NO_IMPLEMENT(MinorCodes.SEND_DEFERRED_NOTIMPLEMENTED,
                               CompletionStatus.COMPLETED_MAYBE);
            } catch ( SystemException ex ) {
                // substitute this exception for original exception
                _env.exception(ex);
                throw ex;
            }
        }
    }

    // Filters the given exception into a SystemException and throws it.
    // If this is a RemarshalException, handle it by recursively calling
    // doInvocation().  This method assumes the given exception is a 
    // SystemException or a RemarhsalException.  
    //
    // If this is a SystemException, call _env.exception( exception ) so that
    // it is set in the environment.
    // 
    // This is a utility method for the above doInvocation() code which must 
    // do this numerous times.  If the exception is null, no exception is 
    // thrown.
    //
    // Note that this code is essentially the same as in ClientDelegate.java
    // or GenericPOAClientSC.java
    //
    private void continueOrThrowSystemOrRemarshal( Exception exception )
        throws SystemException
    {
        if( exception == null ) {
            // do nothing.
        }
        else if( exception instanceof RemarshalException ) {
	    // invoke again:
	    doInvocation();
        }
        else {
	    _env.exception( exception );
            throw (SystemException)exception;
        }
    }

    protected void unmarshalParams(InputStream is)
    {
        // First unmarshal the return value if it is not void
        if ( _result != null ) {
            Any returnAny = _result.value();
            TypeCode returnType = returnAny.type();
            if ( returnType.kind().value() != TCKind._tk_void )
                returnAny.read_value(is, returnType);
        }
        
        // Now unmarshal the out/inout args
        try {
            for ( int i=0; i<_arguments.count() ; i++) {
                NamedValue nv = _arguments.item(i);
                switch( nv.flags() ) {
		case ARG_IN.value:
		    break;
		case ARG_OUT.value:
		case ARG_INOUT.value:
		    Any any = nv.value();	
		    any.read_value(is, any.type());
		    break;
                }
            }
        } 
	catch ( org.omg.CORBA.Bounds ex ) {
	    // Cannot happen since we only iterate till _arguments.count()
        }
    }
}
