/*
 * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 * Licensed Materials - Property of IBM
 * RMI-IIOP v1.0
 * Copyright IBM Corp. 1998 1999  All Rights Reserved
 *
 */

package com.sun.corba.se.impl.protocol;

import java.io.IOException;
import java.util.Iterator;
import java.rmi.RemoteException;

import javax.rmi.CORBA.Util;
import javax.rmi.CORBA.Tie;

import org.omg.CORBA.COMM_FAILURE;
import org.omg.CORBA.INTERNAL;
import org.omg.CORBA.SystemException;
import org.omg.CORBA.Request;
import org.omg.CORBA.NamedValue;
import org.omg.CORBA.NVList;
import org.omg.CORBA.Context;
import org.omg.CORBA.ContextList;
import org.omg.CORBA.ExceptionList;
import org.omg.CORBA.TypeCode;
import org.omg.CORBA.portable.RemarshalException;
import org.omg.CORBA_2_3.portable.InputStream;
import org.omg.CORBA_2_3.portable.OutputStream;
import org.omg.CORBA.portable.Delegate;
import org.omg.CORBA.portable.ServantObject;
import org.omg.CORBA.portable.ApplicationException;
import org.omg.CORBA.portable.UnknownException;
import org.omg.IOP.ExceptionDetailMessage;
import org.omg.IOP.TAG_CODE_SETS;

import com.sun.org.omg.SendingContext.CodeBase;

import com.sun.corba.se.pept.broker.Broker;
import com.sun.corba.se.pept.encoding.InputObject;
import com.sun.corba.se.pept.encoding.OutputObject;
import com.sun.corba.se.pept.protocol.ClientRequestDispatcher;
import com.sun.corba.se.pept.protocol.MessageMediator;
import com.sun.corba.se.pept.transport.Connection;
import com.sun.corba.se.pept.transport.OutboundConnectionCache;
import com.sun.corba.se.pept.transport.ContactInfo;

import com.sun.corba.se.spi.ior.IOR;
import com.sun.corba.se.spi.ior.iiop.GIOPVersion;
import com.sun.corba.se.spi.ior.iiop.IIOPProfileTemplate;
import com.sun.corba.se.spi.ior.iiop.CodeSetsComponent;
import com.sun.corba.se.spi.oa.OAInvocationInfo;
import com.sun.corba.se.spi.oa.ObjectAdapterFactory;
import com.sun.corba.se.spi.orb.ORB;
import com.sun.corba.se.spi.orb.ORBVersion;
import com.sun.corba.se.spi.orb.ORBVersionFactory;
import com.sun.corba.se.spi.protocol.CorbaMessageMediator;
import com.sun.corba.se.spi.protocol.RequestDispatcherRegistry;
import com.sun.corba.se.spi.transport.CorbaContactInfo ;
import com.sun.corba.se.spi.transport.CorbaContactInfoList ;
import com.sun.corba.se.spi.transport.CorbaContactInfoListIterator ;
import com.sun.corba.se.spi.transport.CorbaConnection;
import com.sun.corba.se.spi.logging.CORBALogDomains;

import com.sun.corba.se.spi.servicecontext.MaxStreamFormatVersionServiceContext;
import com.sun.corba.se.spi.servicecontext.ServiceContext;
import com.sun.corba.se.spi.servicecontext.ServiceContexts;
import com.sun.corba.se.spi.servicecontext.UEInfoServiceContext;
import com.sun.corba.se.spi.servicecontext.CodeSetServiceContext;
import com.sun.corba.se.spi.servicecontext.SendingContextServiceContext;
import com.sun.corba.se.spi.servicecontext.ORBVersionServiceContext;
import com.sun.corba.se.spi.servicecontext.MaxStreamFormatVersionServiceContext;
import com.sun.corba.se.spi.servicecontext.UnknownServiceContext;

import com.sun.corba.se.impl.encoding.CDRInputObject;
import com.sun.corba.se.impl.encoding.CodeSetComponentInfo;
import com.sun.corba.se.impl.encoding.CodeSetConversion;
import com.sun.corba.se.impl.encoding.EncapsInputStream;
import com.sun.corba.se.impl.encoding.MarshalOutputStream;
import com.sun.corba.se.impl.encoding.MarshalInputStream;
import com.sun.corba.se.impl.logging.ORBUtilSystemException;
import com.sun.corba.se.impl.orbutil.ORBUtility;
import com.sun.corba.se.impl.orbutil.ORBConstants;
import com.sun.corba.se.impl.protocol.giopmsgheaders.ReplyMessage;
import com.sun.corba.se.impl.protocol.giopmsgheaders.KeyAddr;
import com.sun.corba.se.impl.protocol.giopmsgheaders.ProfileAddr;
import com.sun.corba.se.impl.protocol.giopmsgheaders.ReferenceAddr;
import com.sun.corba.se.impl.transport.CorbaContactInfoListIteratorImpl;
import com.sun.corba.se.impl.util.JDKBridge;

import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentHashMap;
import sun.corba.EncapsInputStreamFactory;

/**
 * ClientDelegate is the RMI client-side subcontract or representation
 * It implements RMI delegate as well as our internal ClientRequestDispatcher
 * interface.
 */
public class CorbaClientRequestDispatcherImpl
    implements
        ClientRequestDispatcher
{
    private ConcurrentMap<ContactInfo, Object> locks =
            new ConcurrentHashMap<ContactInfo, Object>();

    public OutputObject beginRequest(Object self, String opName,
                                     boolean isOneWay, ContactInfo contactInfo)
    {
      ORB orb = null;
      try {
        CorbaContactInfo corbaContactInfo = (CorbaContactInfo) contactInfo;
        orb =  (ORB)contactInfo.getBroker();

        if (orb.subcontractDebugFlag) {
            dprint(".beginRequest->: op/" + opName);
        }

        //
        // Portable Interceptor initialization.
        //

        orb.getPIHandler().initiateClientPIRequest( false );

        //
        // Connection.
        //

        CorbaConnection connection = null;

        // This locking is done so that multiple connections are not created
        // for the same endpoint
        // 7046238 - Synchronization on a single monitor for contactInfo parameters
        // with identical hashCode(), so we lock on same monitor for equal parameters
        // (which can refer to equal (in terms of equals()) but not the same objects)

        Object lock = locks.get(contactInfo);

        if (lock == null) {
            Object newLock = new Object();
            lock = locks.putIfAbsent(contactInfo, newLock);
            if (lock == null) {
                lock = newLock;
            }
        }

        synchronized (lock) {
            if (contactInfo.isConnectionBased()) {
                if (contactInfo.shouldCacheConnection()) {
                    connection = (CorbaConnection)
                        orb.getTransportManager()
                        .getOutboundConnectionCache(contactInfo).get(contactInfo);
                }
                if (connection != null) {
                    if (orb.subcontractDebugFlag) {
                        dprint(".beginRequest: op/" + opName
                               + ": Using cached connection: " + connection);
                    }
                } else {
                    try {
                        connection = (CorbaConnection)
                            contactInfo.createConnection();
                        if (orb.subcontractDebugFlag) {
                            dprint(".beginRequest: op/" + opName
                                   + ": Using created connection: " + connection);
                        }
                    } catch (RuntimeException e) {
                        if (orb.subcontractDebugFlag) {
                            dprint(".beginRequest: op/" + opName
                                   + ": failed to create connection: " + e);
                        }
                        // REVISIT: this part similar to marshalingComplete below.
                        boolean retry = getContactInfoListIterator(orb)
                                           .reportException(contactInfo, e);
                        // REVISIT:
                        // this part similar to Remarshal in this method below
                        if (retry) {
                            if(getContactInfoListIterator(orb).hasNext()) {
                                contactInfo = (ContactInfo)
                                   getContactInfoListIterator(orb).next();
                                unregisterWaiter(orb);
                                return beginRequest(self, opName,
                                                    isOneWay, contactInfo);
                            } else {
                                throw e;
                            }
                        } else {
                            throw e;
                        }
                    }
                    if (connection.shouldRegisterReadEvent()) {
                        // REVISIT: cast
                        orb.getTransportManager().getSelector(0)
                            .registerForEvent(connection.getEventHandler());
                        connection.setState("ESTABLISHED");
                    }
                    // Do not do connection reclaim here since the connections
                    // are marked in use by registerWaiter() call and since this
                    // call happens later do it after that.
                    if (contactInfo.shouldCacheConnection()) {
                        OutboundConnectionCache connectionCache =
                         orb.getTransportManager()
                            .getOutboundConnectionCache(contactInfo);
                        connectionCache.stampTime(connection);
                        connectionCache.put(contactInfo, connection);
    //              connectionCache.reclaim();
                    }
                }
            }
        }

        CorbaMessageMediator messageMediator = (CorbaMessageMediator)
            contactInfo.createMessageMediator(
                orb, contactInfo, connection, opName, isOneWay);
        if (orb.subcontractDebugFlag) {
            dprint(".beginRequest: " + opAndId(messageMediator)
                   + ": created message mediator: " +  messageMediator);
        }

        // NOTE: Thread data so we can get the mediator in release reply
        // in order to remove the waiter in CorbaConnection.
        // We cannot depend on obtaining information in releaseReply
        // via its InputStream argument since, on certain errors
        // (e.g., client marshaling errors), the stream may be null.
        // Likewise for releaseReply "self".
        // NOTE: This must be done before initializing the message since
        // that may start sending fragments which may end up in "early"
        // replies or client marshaling exceptions.

        orb.getInvocationInfo().setMessageMediator(messageMediator);

        if (connection != null && connection.getCodeSetContext() == null) {
            performCodeSetNegotiation(messageMediator);
        }

        addServiceContexts(messageMediator);

        OutputObject outputObject =
            contactInfo.createOutputObject(messageMediator);
        if (orb.subcontractDebugFlag) {
            dprint(".beginRequest: " + opAndId(messageMediator)
                   + ": created output object: " + outputObject);
        }


        // NOTE: Not necessary for oneways, but useful for debugging.
        // This must be done BEFORE message initialization since fragments
        // may be sent at that time.
        registerWaiter(messageMediator);

        // Do connection reclaim now
        synchronized (lock) {
            if (contactInfo.isConnectionBased()) {
                if (contactInfo.shouldCacheConnection()) {
                    OutboundConnectionCache connectionCache =
                             orb.getTransportManager()
                                .getOutboundConnectionCache(contactInfo);
                    connectionCache.reclaim();
                }
            }
        }

        orb.getPIHandler().setClientPIInfo(messageMediator);
        try {
            // This MUST come before message is initialized so
            // service contexts may be added by PI because
            // initial fragments may be sent during message initialization.
            orb.getPIHandler().invokeClientPIStartingPoint();
        } catch( RemarshalException e ) {
            if (orb.subcontractDebugFlag) {
                dprint(".beginRequest: " + opAndId(messageMediator)
                       + ": Remarshal");
            }

            // NOTE: We get here because an interceptor raised ForwardRequest
            // and updated the IOR/Iterator.  Since we have a fresh iterator
            // hasNext should succeed.

            // REVISIT: We should feed ALL interceptor exceptions to
            // iterator.reportException so it can determine if it wants
            // to retry.  Right now, SystemExceptions will flow to the
            // client code.

            // REVISIT:
            // This assumes that interceptors update
            // ContactInfoList outside of subcontract.
            // Want to move that update to here.
            if (getContactInfoListIterator(orb).hasNext()) {
                contactInfo = (ContactInfo)getContactInfoListIterator(orb).next();
                if (orb.subcontractDebugFlag) {
                    dprint( "RemarshalException: hasNext true\ncontact info " + contactInfo );
                }

                // Fix for 6763340: Complete the first attempt before starting another.
                orb.getPIHandler().makeCompletedClientRequest(
                    ReplyMessage.LOCATION_FORWARD, null ) ;
                unregisterWaiter(orb);
                orb.getPIHandler().cleanupClientPIRequest() ;

                return beginRequest(self, opName, isOneWay, contactInfo);
            } else {
                if (orb.subcontractDebugFlag) {
                    dprint( "RemarshalException: hasNext false" );
                }
                ORBUtilSystemException wrapper =
                    ORBUtilSystemException.get(orb,
                                               CORBALogDomains.RPC_PROTOCOL);
                throw wrapper.remarshalWithNowhereToGo();
            }
        }

        messageMediator.initializeMessage();
        if (orb.subcontractDebugFlag) {
            dprint(".beginRequest: " + opAndId(messageMediator)
                   + ": initialized message");
        }

        return outputObject;

      } finally {
        if (orb.subcontractDebugFlag) {
            dprint(".beginRequest<-: op/" + opName);
        }
      }
    }

    public InputObject marshalingComplete(java.lang.Object self,
                                          OutputObject outputObject)
        throws
            ApplicationException,
            org.omg.CORBA.portable.RemarshalException
    {
        ORB orb = null;
        CorbaMessageMediator messageMediator = null;
        try {
            messageMediator = (CorbaMessageMediator)
                outputObject.getMessageMediator();

            orb = (ORB) messageMediator.getBroker();

            if (orb.subcontractDebugFlag) {
                dprint(".marshalingComplete->: " + opAndId(messageMediator));
            }

            InputObject inputObject =
                marshalingComplete1(orb, messageMediator);

            return processResponse(orb, messageMediator, inputObject);

        } finally {
            if (orb.subcontractDebugFlag) {
                dprint(".marshalingComplete<-: " + opAndId(messageMediator));
            }
        }
    }

    public InputObject marshalingComplete1(
            ORB orb, CorbaMessageMediator messageMediator)
        throws
            ApplicationException,
            org.omg.CORBA.portable.RemarshalException
    {
        try {
            messageMediator.finishSendingRequest();

            if (orb.subcontractDebugFlag) {
                dprint(".marshalingComplete: " + opAndId(messageMediator)
                       + ": finished sending request");
            }

            return messageMediator.waitForResponse();

        } catch (RuntimeException e) {

            if (orb.subcontractDebugFlag) {
                dprint(".marshalingComplete: " + opAndId(messageMediator)
                       + ": exception: " + e.toString());
            }

            boolean retry  =
                getContactInfoListIterator(orb)
                    .reportException(messageMediator.getContactInfo(), e);

            //Bug 6382377: must not lose exception in PI

            // Must run interceptor end point before retrying.
            Exception newException =
                    orb.getPIHandler().invokeClientPIEndingPoint(
                    ReplyMessage.SYSTEM_EXCEPTION, e);

            if (retry) {
                if (newException == e) {
                    continueOrThrowSystemOrRemarshal(messageMediator,
                                                     new RemarshalException());
                } else {
                    continueOrThrowSystemOrRemarshal(messageMediator,
                                                     newException);
                }
            } else {
                if (newException instanceof RuntimeException){
                    throw (RuntimeException)newException;
                }
                else if (newException instanceof RemarshalException)
                {
                    throw (RemarshalException)newException;
                }

                // NOTE: Interceptor ending point will run in releaseReply.
                throw e;
            }
            return null; // for compiler
        }
    }

    protected InputObject processResponse(ORB orb,
                                          CorbaMessageMediator messageMediator,
                                          InputObject inputObject)
        throws
            ApplicationException,
            org.omg.CORBA.portable.RemarshalException
    {
        ORBUtilSystemException wrapper =
            ORBUtilSystemException.get( orb,
                CORBALogDomains.RPC_PROTOCOL ) ;

        if (orb.subcontractDebugFlag) {
            dprint(".processResponse: " + opAndId(messageMediator)
                   + ": response received");
        }

        // We know for sure now that we've sent a message.
        // So OK to not send initial again.
        if (messageMediator.getConnection() != null) {
            ((CorbaConnection)messageMediator.getConnection())
                .setPostInitialContexts();
        }

        // NOTE: not necessary to set MessageMediator for PI.
        // It already has it.

        // Process the response.

        Exception exception = null;

        if (messageMediator.isOneWay()) {
            getContactInfoListIterator(orb)
                .reportSuccess(messageMediator.getContactInfo());
            // Invoke Portable Interceptors with receive_other
            exception = orb.getPIHandler().invokeClientPIEndingPoint(
                ReplyMessage.NO_EXCEPTION, exception );
            continueOrThrowSystemOrRemarshal(messageMediator, exception);
            return null;
        }

        consumeServiceContexts(orb, messageMediator);

        // Now that we have the service contexts processed and the
        // correct ORBVersion set, we must finish initializing the stream.
        // REVISIT - need interface for this operation.
        ((CDRInputObject)inputObject).performORBVersionSpecificInit();

        if (messageMediator.isSystemExceptionReply()) {

            SystemException se = messageMediator.getSystemExceptionReply();

            if (orb.subcontractDebugFlag) {
                dprint(".processResponse: " + opAndId(messageMediator)
                       + ": received system exception: " + se);
            }

            boolean doRemarshal =
                getContactInfoListIterator(orb)
                    .reportException(messageMediator.getContactInfo(), se);

            if (doRemarshal) {

                // Invoke Portable Interceptors with receive_exception:
                exception = orb.getPIHandler().invokeClientPIEndingPoint(
                    ReplyMessage.SYSTEM_EXCEPTION, se );

                // If PI did not change the exception, throw a
                // Remarshal.
                if( se == exception ) {
                    // exception = null is to maintain symmetry with
                    // GenericPOAClientSC.
                    exception = null;
                    continueOrThrowSystemOrRemarshal(messageMediator,
                                                     new RemarshalException());
                    throw wrapper.statementNotReachable1() ;
                } else {
                    //  Otherwise, throw the exception PI wants thrown.
                    continueOrThrowSystemOrRemarshal(messageMediator,
                                                     exception);
                    throw wrapper.statementNotReachable2() ;
                }
            }

            // No retry, so see if was unknown.

            ServiceContexts contexts =
                messageMediator.getReplyServiceContexts();
            if (contexts != null) {
                UEInfoServiceContext usc =
                    (UEInfoServiceContext)
                    contexts.get(UEInfoServiceContext.SERVICE_CONTEXT_ID);

                if (usc != null) {
                    Throwable unknown = usc.getUE() ;
                    UnknownException ue = new UnknownException(unknown);

                    // Invoke Portable Interceptors with receive_exception:
                    exception = orb.getPIHandler().invokeClientPIEndingPoint(
                        ReplyMessage.SYSTEM_EXCEPTION, ue );

                    continueOrThrowSystemOrRemarshal(messageMediator, exception);
                    throw wrapper.statementNotReachable3() ;
                }
            }

            // It was not a comm failure nor unknown.
            // This is the general case.

            // Invoke Portable Interceptors with receive_exception:
            exception = orb.getPIHandler().invokeClientPIEndingPoint(
                ReplyMessage.SYSTEM_EXCEPTION, se );

            continueOrThrowSystemOrRemarshal(messageMediator, exception);

            // Note: We should never need to execute this line, but
            // we should assert in case exception is null somehow.
            throw wrapper.statementNotReachable4() ;
        } else if (messageMediator.isUserExceptionReply()) {

            if (orb.subcontractDebugFlag) {
                dprint(".processResponse: " + opAndId(messageMediator)
                       + ": received user exception");
            }

            getContactInfoListIterator(orb)
                .reportSuccess(messageMediator.getContactInfo());

            String exceptionRepoId = peekUserExceptionId(inputObject);
            Exception newException = null;

            if (messageMediator.isDIIRequest()) {
                exception = messageMediator.unmarshalDIIUserException(
                                exceptionRepoId, (InputStream)inputObject);
                newException = orb.getPIHandler().invokeClientPIEndingPoint(
                                   ReplyMessage.USER_EXCEPTION, exception );
                messageMediator.setDIIException(newException);

            } else {
                ApplicationException appException =
                    new ApplicationException(
                        exceptionRepoId,
                        (org.omg.CORBA.portable.InputStream)inputObject);
                exception = appException;
                newException = orb.getPIHandler().invokeClientPIEndingPoint(
                                   ReplyMessage.USER_EXCEPTION, appException );
            }

            if (newException != exception) {
                continueOrThrowSystemOrRemarshal(messageMediator,newException);
            }

            if (newException instanceof ApplicationException) {
                throw (ApplicationException)newException;
            }
            // For DII:
            // This return will be ignored - already unmarshaled above.
            return inputObject;

        } else if (messageMediator.isLocationForwardReply()) {

            if (orb.subcontractDebugFlag) {
                dprint(".processResponse: " + opAndId(messageMediator)
                       + ": received location forward");
            }

            // NOTE: Expects iterator to update target IOR
            getContactInfoListIterator(orb).reportRedirect(
                (CorbaContactInfo)messageMediator.getContactInfo(),
                messageMediator.getForwardedIOR());

            // Invoke Portable Interceptors with receive_other:
            Exception newException = orb.getPIHandler().invokeClientPIEndingPoint(
                ReplyMessage.LOCATION_FORWARD, null );

            if( !(newException instanceof RemarshalException) ) {
                exception = newException;
            }

            // If PI did not change exception, throw Remarshal, else
            // throw the exception PI wants thrown.
            // KMC: GenericPOAClientSC did not check exception != null
            if( exception != null ) {
                continueOrThrowSystemOrRemarshal(messageMediator, exception);
            }
            continueOrThrowSystemOrRemarshal(messageMediator,
                                             new RemarshalException());
            throw wrapper.statementNotReachable5() ;

        } else if (messageMediator.isDifferentAddrDispositionRequestedReply()){

            if (orb.subcontractDebugFlag) {
                dprint(".processResponse: " + opAndId(messageMediator)
                       + ": received different addressing dispostion request");
            }

            // Set the desired target addressing disposition.
            getContactInfoListIterator(orb).reportAddrDispositionRetry(
                (CorbaContactInfo)messageMediator.getContactInfo(),
                messageMediator.getAddrDispositionReply());

            // Invoke Portable Interceptors with receive_other:
            Exception newException = orb.getPIHandler().invokeClientPIEndingPoint(
                ReplyMessage.NEEDS_ADDRESSING_MODE, null);

            // For consistency with corresponding code in GenericPOAClientSC:
            if( !(newException instanceof RemarshalException) ) {
                exception = newException;
            }

            // If PI did not change exception, throw Remarshal, else
            // throw the exception PI wants thrown.
            // KMC: GenericPOAClientSC did not include exception != null check
            if( exception != null ) {
                continueOrThrowSystemOrRemarshal(messageMediator, exception);
            }
            continueOrThrowSystemOrRemarshal(messageMediator,
                                             new RemarshalException());
            throw wrapper.statementNotReachable6() ;
        } else /* normal response */ {

            if (orb.subcontractDebugFlag) {
                dprint(".processResponse: " + opAndId(messageMediator)
                       + ": received normal response");
            }

            getContactInfoListIterator(orb)
                .reportSuccess(messageMediator.getContactInfo());

            messageMediator.handleDIIReply((InputStream)inputObject);

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

            // Remember: not thrown if exception is null.
            continueOrThrowSystemOrRemarshal(messageMediator, exception);

            return inputObject;
        }
    }

    // Filters the given exception into a SystemException or a
    // RemarshalException and throws it.  Assumes the given exception is
    // of one of these two types.  This is a utility method for
    // the above invoke code which must do this numerous times.
    // If the exception is null, no exception is thrown.
    //
    // Note that this code is duplicated in GenericPOAClientSC.java
    protected void continueOrThrowSystemOrRemarshal(
        CorbaMessageMediator messageMediator, Exception exception)
        throws
            SystemException, RemarshalException
    {

        ORB orb = (ORB) messageMediator.getBroker();

        if( exception == null ) {

            // do nothing.

        } else if( exception instanceof RemarshalException ) {

            // REVISIT - unify with PI handling
            orb.getInvocationInfo().setIsRetryInvocation(true);

            // NOTE - We must unregister the waiter NOW for this request
            // since the retry will result in a new request id.  Therefore
            // the old request id would be lost and we would have a memory
            // leak in the responseWaitingRoom.
            unregisterWaiter(orb);

            if (orb.subcontractDebugFlag) {
                dprint(".continueOrThrowSystemOrRemarshal: "
                       + opAndId(messageMediator)
                       + ": throwing Remarshal");
            }

            throw (RemarshalException)exception;

        } else {

            if (orb.subcontractDebugFlag) {
                dprint(".continueOrThrowSystemOrRemarshal: "
                       + opAndId(messageMediator)
                       + ": throwing sex:"
                       + exception);
            }

            throw (SystemException)exception;
        }
    }

    protected CorbaContactInfoListIterator  getContactInfoListIterator(ORB orb)
    {
        return (CorbaContactInfoListIterator)
            ((CorbaInvocationInfo)orb.getInvocationInfo())
                .getContactInfoListIterator();
    }

    protected void registerWaiter(CorbaMessageMediator messageMediator)
    {
        if (messageMediator.getConnection() != null) {
            messageMediator.getConnection().registerWaiter(messageMediator);
        }
    }

    protected void unregisterWaiter(ORB orb)
    {
        MessageMediator messageMediator =
            orb.getInvocationInfo().getMessageMediator();
        if (messageMediator!=null && messageMediator.getConnection() != null) {
            // REVISIT:
            // The messageMediator may be null if COMM_FAILURE before
            // it is created.
            messageMediator.getConnection().unregisterWaiter(messageMediator);
        }
    }

    protected void addServiceContexts(CorbaMessageMediator messageMediator)
    {
        ORB orb = (ORB)messageMediator.getBroker();
        CorbaConnection c = (CorbaConnection) messageMediator.getConnection();
        GIOPVersion giopVersion = messageMediator.getGIOPVersion();

        ServiceContexts contexts = messageMediator.getRequestServiceContexts();

        addCodeSetServiceContext(c, contexts, giopVersion);

        // Add the RMI-IIOP max stream format version
        // service context to every request.  Once we have GIOP 1.3,
        // we could skip it since we now support version 2, but
        // probably safer to always send it.
        contexts.put(MaxStreamFormatVersionServiceContext.singleton);

        // ORBVersion servicecontext needs to be sent
        ORBVersionServiceContext ovsc = new ORBVersionServiceContext(
                        ORBVersionFactory.getORBVersion() ) ;
        contexts.put( ovsc ) ;

        // NOTE : We only want to send the runtime context the first time
        if ((c != null) && !c.isPostInitialContexts()) {
            // Do not do c.setPostInitialContexts() here.
            // If a client interceptor send_request does a ForwardRequest
            // which ends up using the same connection then the service
            // context would not be sent.
            SendingContextServiceContext scsc =
                new SendingContextServiceContext( orb.getFVDCodeBaseIOR() ) ; //d11638
            contexts.put( scsc ) ;
        }
    }

    protected void consumeServiceContexts(ORB orb,
                                        CorbaMessageMediator messageMediator)
    {
        ServiceContexts ctxts = messageMediator.getReplyServiceContexts();
        ServiceContext sc ;
        ORBUtilSystemException wrapper = ORBUtilSystemException.get( orb,
                CORBALogDomains.RPC_PROTOCOL ) ;

        if (ctxts == null) {
            return; // no service context available, return gracefully.
        }

        sc = ctxts.get( SendingContextServiceContext.SERVICE_CONTEXT_ID ) ;

        if (sc != null) {
            SendingContextServiceContext scsc =
                (SendingContextServiceContext)sc ;
            IOR ior = scsc.getIOR() ;

            try {
                // set the codebase returned by the server
                if (messageMediator.getConnection() != null) {
                    ((CorbaConnection)messageMediator.getConnection()).setCodeBaseIOR(ior);
                }
            } catch (ThreadDeath td) {
                throw td ;
            } catch (Throwable t) {
                throw wrapper.badStringifiedIor( t ) ;
            }
        }

        // see if the version subcontract is present, if yes, then set
        // the ORBversion
        sc = ctxts.get( ORBVersionServiceContext.SERVICE_CONTEXT_ID ) ;

        if (sc != null) {
            ORBVersionServiceContext ovsc =
               (ORBVersionServiceContext) sc;

            ORBVersion version = ovsc.getVersion();
            orb.setORBVersion( version ) ;
        }

        getExceptionDetailMessage(messageMediator, wrapper);
    }

    protected void getExceptionDetailMessage(
        CorbaMessageMediator  messageMediator,
        ORBUtilSystemException wrapper)
    {
        ServiceContext sc = messageMediator.getReplyServiceContexts()
            .get(ExceptionDetailMessage.value);
        if (sc == null)
            return ;

        if (! (sc instanceof UnknownServiceContext)) {
            throw wrapper.badExceptionDetailMessageServiceContextType();
        }
        byte[] data = ((UnknownServiceContext)sc).getData();
        EncapsInputStream in =
                EncapsInputStreamFactory.newEncapsInputStream((ORB)messageMediator.getBroker(),
                                      data, data.length);
        in.consumeEndian();

        String msg =
              "----------BEGIN server-side stack trace----------\n"
            + in.read_wstring() + "\n"
            + "----------END server-side stack trace----------";

        messageMediator.setReplyExceptionDetailMessage(msg);
    }

    public void endRequest(Broker broker, Object self, InputObject inputObject)
    {
        ORB orb = (ORB)broker ;

        try {
            if (orb.subcontractDebugFlag) {
                dprint(".endRequest->");
            }

            // Note: the inputObject may be null if an error occurs
            //       in request or before _invoke returns.
            // Note: self may be null also (e.g., compiler generates null in stub).

            MessageMediator messageMediator =
                orb.getInvocationInfo().getMessageMediator();
            if (messageMediator != null)
            {
                if (messageMediator.getConnection() != null)
                {
                    ((CorbaMessageMediator)messageMediator)
                              .sendCancelRequestIfFinalFragmentNotSent();
                }

                // Release any outstanding NIO ByteBuffers to the ByteBufferPool

                InputObject inputObj = messageMediator.getInputObject();
                if (inputObj != null) {
                    inputObj.close();
                }

                OutputObject outputObj = messageMediator.getOutputObject();
                if (outputObj != null) {
                    outputObj.close();
                }

            }

            // XREVISIT NOTE - Assumes unregistering the waiter for
            // location forwards has already happened somewhere else.
            // The code below is only going to unregister the final successful
            // request.

            // NOTE: In the case of a recursive stack of endRequests in a
            // finally block (because of Remarshal) only the first call to
            // unregisterWaiter will remove the waiter.  The rest will be
            // noops.
            unregisterWaiter(orb);

            // Invoke Portable Interceptors cleanup.  This is done to handle
            // exceptions during stream marshaling.  More generally, exceptions
            // that occur in the ORB after send_request (which includes
            // after returning from _request) before _invoke:
            orb.getPIHandler().cleanupClientPIRequest();

            // REVISIT: Early replies?
        } catch (IOException ex) {
            // See CDRInput/OutputObject.close() for more info.
            // This won't result in a Corba error if an IOException happens.
            if (orb.subcontractDebugFlag)
            {
                dprint(".endRequest: ignoring IOException - " + ex.toString());
            }
        } finally {
            if (orb.subcontractDebugFlag) {
                dprint(".endRequest<-");
            }
        }
    }


    protected void performCodeSetNegotiation(CorbaMessageMediator messageMediator)
    {
        CorbaConnection conn =
            (CorbaConnection) messageMediator.getConnection();
        IOR ior =
            ((CorbaContactInfo)messageMediator.getContactInfo())
            .getEffectiveTargetIOR();
        GIOPVersion giopVersion = messageMediator.getGIOPVersion();

        // XXX This seems to be a broken double checked locking idiom: FIX IT!

        // conn.getCodeSetContext() is null when no other requests have
        // been made on this connection to trigger code set negotation.
        if (conn != null &&
            conn.getCodeSetContext() == null &&
            !giopVersion.equals(GIOPVersion.V1_0)) {

            synchronized(conn) {
                // Double checking.  Don't let any other
                // threads use this connection until the
                // code sets are straight.
                if (conn.getCodeSetContext() != null)
                    return;

                // This only looks at the first code set component.  If
                // there can be multiple locations with multiple code sets,
                // this requires more work.
                IIOPProfileTemplate temp =
                    (IIOPProfileTemplate)ior.getProfile().
                    getTaggedProfileTemplate();
                Iterator iter = temp.iteratorById(TAG_CODE_SETS.value);
                if (!iter.hasNext()) {
                    // Didn't have a code set component.  The default will
                    // be to use ISO8859-1 for char data and throw an
                    // exception if wchar data is used.
                    return;
                }

                // Get the native and conversion code sets the
                // server specified in its IOR
                CodeSetComponentInfo serverCodeSets
                    = ((CodeSetsComponent)iter.next()).getCodeSetComponentInfo();

                // Perform the negotiation between this ORB's code sets and
                // the ones from the IOR
                CodeSetComponentInfo.CodeSetContext result
                    = CodeSetConversion.impl().negotiate(
                          conn.getBroker().getORBData().getCodeSetComponentInfo(),
                          serverCodeSets);

                conn.setCodeSetContext(result);
            }
        }
    }

    protected void addCodeSetServiceContext(CorbaConnection conn,
                                          ServiceContexts ctxs,
                                          GIOPVersion giopVersion) {

        // REVISIT.  OMG issue 3318 concerning sending the code set
        // service context more than once was deemed too much for the
        // RTF.  Here's our strategy for the moment:
        //
        // Send it on every request (necessary in cases of fragmentation
        // with multithreaded clients or when the first thing on a
        // connection is a LocateRequest).  Provide an ORB property
        // to disable multiple sends.
        //
        // Note that the connection is null in the local case and no
        // service context is included.  We use the ORB provided
        // encapsulation streams.
        //
        // Also, there will be no negotiation or service context
        // in GIOP 1.0.  ISO8859-1 is used for char/string, and
        // wchar/wstring are illegal.
        //
        if (giopVersion.equals(GIOPVersion.V1_0) || conn == null)
            return;

        CodeSetComponentInfo.CodeSetContext codeSetCtx = null;

        if (conn.getBroker().getORBData().alwaysSendCodeSetServiceContext() ||
            !conn.isPostInitialContexts()) {

            // Get the negotiated code sets (if any) out of the connection
            codeSetCtx = conn.getCodeSetContext();
        }

        // Either we shouldn't send the code set service context, or
        // for some reason, the connection doesn't have its code sets.
        // Perhaps the server didn't include them in the IOR.  Uses
        // ISO8859-1 for char and makes wchar/wstring illegal.
        if (codeSetCtx == null)
            return;

        CodeSetServiceContext cssc = new CodeSetServiceContext(codeSetCtx);
        ctxs.put(cssc);
    }

    protected String peekUserExceptionId(InputObject inputObject)
    {
        CDRInputObject cdrInputObject = (CDRInputObject) inputObject;
        // REVISIT - need interface for mark/reset
        cdrInputObject.mark(Integer.MAX_VALUE);
        String result = cdrInputObject.read_string();
        cdrInputObject.reset();
        return result;
    }

    protected void dprint(String msg)
    {
        ORBUtility.dprint("CorbaClientRequestDispatcherImpl", msg);
    }

    protected String opAndId(CorbaMessageMediator mediator)
    {
        return ORBUtility.operationNameAndRequestId(mediator);
    }
}

// End of file.