/*
 * Copyright 2001-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.axis.client ;

import javax.xml.namespace.QName;
import javax.xml.rpc.handler.HandlerChain;

import org.apache.axis.AxisEngine;
import org.apache.axis.AxisFault;
import org.apache.axis.Constants;
import org.apache.axis.EngineConfiguration;
import org.apache.axis.Handler;
import org.apache.axis.MessageContext;
import org.apache.axis.components.logger.LogFactory;
import org.apache.axis.configuration.EngineConfigurationFactoryFinder;
import org.apache.axis.handlers.HandlerInfoChainFactory;
import org.apache.axis.handlers.soap.MustUnderstandChecker;
import org.apache.axis.handlers.soap.SOAPService;
import org.apache.axis.utils.Messages;
import org.apache.commons.logging.Log;

/**
 * Provides the equivalent of an "Axis engine" on the client side.
 * Subclasses hardcode initialization & setup logic for particular
 * client-side transports.
 *
 * @author Rob Jellinghaus (robj@unrealities.com)
 * @author Doug Davis (dug@us.ibm.com)
 * @author Glen Daniels (gdaniels@allaire.com)
 */
public class AxisClient extends AxisEngine {
    protected static Log log =
            LogFactory.getLog(AxisClient.class.getName());

    MustUnderstandChecker checker = new MustUnderstandChecker(null);

    public AxisClient(EngineConfiguration config) {
        super(config);
    }

    public AxisClient() {
        this(EngineConfigurationFactoryFinder.newFactory().
                getClientEngineConfig());
    }

    /**
     * @return this instance, as this is the client engine
     */
    public AxisEngine getClientEngine () {
        return this;
    }

    /**
     * Main routine of the AXIS engine.  In short we locate the appropriate
     * handler for the desired service and invoke() it.
     *
     * @param msgContext the <code>MessageContext</code> to invoke relative
     *                   to
     * @throws AxisFault if anything goes wrong during invocation
     */
    public void invoke(MessageContext msgContext) throws AxisFault {
        if (log.isDebugEnabled()) {
            log.debug("Enter: AxisClient::invoke");
        }
        String hName = null;
        Handler h = null;
        HandlerChain handlerImpl = null;
        
        // save previous context
        MessageContext previousContext = getCurrentMessageContext();
        try {
            // set active context
            setCurrentMessageContext(msgContext);
            hName = msgContext.getStrProp(MessageContext.ENGINE_HANDLER);
            if (log.isDebugEnabled()) {
                log.debug("EngineHandler: " + hName);
            }
            if (hName != null) {
                h = getHandler(hName);
                if (h != null)
                    h.invoke(msgContext);
                else
                    throw new AxisFault("Client.error",
                            Messages.getMessage("noHandler00",
                                    hName),
                            null, null);
            } else {
                /* Now we do the 'real' work.  The flow is basically:         */
                /*                                                            */
                /*   Service Specific Request Chain                           */
                /*   Global Request Chain                                     */
                /*   Transport Request Chain - must have a send at the end    */
                /*   Transport Response Chain                                 */
                /*   Global Response Chain                                    */
                /*   Service Specific Response Chain                          */
                /*   Protocol Specific-Handler/Checker                        */
                /**************************************************************/
                SOAPService service = null;
                msgContext.setPastPivot(false);

                /* Process the Service Specific Request Chain */
                /**********************************************/
                service = msgContext.getService();
                if (service != null) {
                    h = service.getRequestHandler();
                    if (h != null)
                        h.invoke(msgContext);
                }

                /* Process the Global Request Chain */
                /**********************************/
                if ((h = getGlobalRequest()) != null)
                    h.invoke(msgContext);

                /* Process the JAX-RPC Handlers  - handleRequest.
                 * Make sure to set the pastPivot to true if this returns a
                 * false. In that case we do not invoke the transport request
                 * chain. Also note that if a a false was returned from the
                 * JAX-RPC handler chain, then the chain still holds the index
                 * of the handler that returned false. So when we invoke the
                 * handleResponse method of the chain, it will correctly call
                 * the handleResponse from that specific handler instance. So
                 * do not destroy the chain at this point - the chain will be
                 * destroyed in the finally block.
                 */
                handlerImpl = getJAXRPChandlerChain(msgContext);
                if (handlerImpl != null) {
                    try {
                        if (!handlerImpl.handleRequest(msgContext)) {
                            msgContext.setPastPivot(true);
                        }
                    } catch (RuntimeException re) {
                        handlerImpl.destroy();  // WS4EE 1.1 6.2.2.1 Handler Life Cycle. "RuntimeException" --> destroy handler
                        throw re;
                    }
                }

                /** Process the Transport Specific stuff
                 *
                 * NOTE: Somewhere in here there is a handler which actually
                 * sends the message and receives a response.  Generally
                 * this is the pivot point in the Transport chain. But invoke
                 * this only if pivot point has not been set to false. This
                 * can be set to false if any of the JAX-RPC handler's
                 * handleRequest returned false.
                 */
                if (!msgContext.getPastPivot()) {
                    hName = msgContext.getTransportName();
                    if (hName != null && (h = getTransport(hName)) != null) {
                        try {
                            h.invoke(msgContext);
                        } catch (AxisFault e) {
                            throw e;
                        }
                    } else {
                        throw new AxisFault(Messages.getMessage("noTransport00",
                                hName));
                    }
                }
                
                msgContext.setPastPivot(true);
                if (!msgContext.isPropertyTrue(Call.ONE_WAY)) {
                    if ((handlerImpl != null) &&
                            !msgContext.isPropertyTrue(Call.ONE_WAY)) {
                        try {
                            handlerImpl.handleResponse(msgContext);                            
                        } catch (RuntimeException ex) {
                            handlerImpl.destroy();  // WS4EE 1.1 6.2.2.1 Handler Life Cycle. "RuntimeException" --> destroy handler
                            throw ex;    
                        }                        
                    }

                    /* Process the Global Response Chain */
                    /***********************************/
                    if ((h = getGlobalResponse()) != null) {
                        h.invoke(msgContext);
                    }
                    
                    /* Process the Service-Specific Response Chain */
                    /***********************************************/
                    if (service != null) {
                        h = service.getResponseHandler();
                        if (h != null) {
                            h.invoke(msgContext);
                        }
                    }

                    // Do SOAP Semantics checks here - this needs to be a call
                    // to a pluggable object/handler/something
                    if (msgContext.isPropertyTrue(Call.CHECK_MUST_UNDERSTAND,
                            true)) {
                        checker.invoke(msgContext);
                    }
                }
            }
        } catch (Exception e) {
            // Should we even bother catching it ?
            if (e instanceof AxisFault) {
                throw (AxisFault) e;
            } else {
                log.debug(Messages.getMessage("exception00"), e);
                throw AxisFault.makeFault(e);
            }
        } finally {
            if (handlerImpl != null) {
                handlerImpl.destroy();
            }
            // restore previous state
            setCurrentMessageContext(previousContext);
        }
        if (log.isDebugEnabled()) {
            log.debug("Exit: AxisClient::invoke");
        }
    }

    /**
     * @param context Stores the Service, port QName and optionnaly a HandlerInfoChainFactory
     * @return Returns a HandlerChain if one has been specified
     */
    protected HandlerChain getJAXRPChandlerChain(MessageContext context) {
        java.util.List chain = null;
        HandlerInfoChainFactory hiChainFactory = null;
        boolean clientSpecified = false;

        Service service = (Service) context.getProperty(Call.WSDL_SERVICE);
        if(service == null) {
            return null;
        }

        QName portName = (QName) context.getProperty(Call.WSDL_PORT_NAME);
        if(portName == null) {
            return null;
        }

        javax.xml.rpc.handler.HandlerRegistry registry;
        registry = service.getHandlerRegistry();
        if(registry != null) {
            chain = registry.getHandlerChain(portName);
            if ((chain != null) && (!chain.isEmpty())) {
                hiChainFactory = new HandlerInfoChainFactory(chain);
                clientSpecified = true;
            }
        }

        // Otherwise, use the container support
        if (!clientSpecified) {
            SOAPService soapService = context.getService();
            if (soapService != null) {
                // A client configuration exists for this service.  Check
                // to see if there is a HandlerInfoChain configured on it.
                hiChainFactory = (HandlerInfoChainFactory)
                        soapService.getOption(Constants.ATTR_HANDLERINFOCHAIN);
            }
        }

        if (hiChainFactory == null) {
            return null;
        }

        return hiChainFactory.createHandlerChain();
    }

}
