/*
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */

// $Id: ClientEngine.java,v 1.8.2.3 2005/04/12 16:07:31 starksm Exp $
package org.jboss.webservice.client;

// $Id: ClientEngine.java,v 1.8.2.3 2005/04/12 16:07:31 starksm Exp $

import org.jboss.axis.AxisFault;
import org.jboss.axis.ConfigurationException;
import org.jboss.axis.EngineConfiguration;
import org.jboss.axis.Handler;
import org.jboss.axis.MessageContext;
import org.jboss.axis.client.AxisClient;
import org.jboss.axis.client.Call;
import org.jboss.axis.utils.Messages;
import org.jboss.logging.Logger;
import org.jboss.webservice.handler.ClientHandlerChain;

import javax.xml.rpc.JAXRPCException;
import javax.xml.rpc.handler.HandlerChain;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * The ws4ee client engine.
 * <p/>
 * Note, every service-ref client maintains an instance of this client engine.
 *
 * @author Thomas.Diesler@jboss.org
 * @since 11-May-2004
 */
public class ClientEngine extends AxisClient
{
   /** @since 4.0.2 */
   static final long serialVersionUID = 53844570311989118L;
   protected static Logger log = Logger.getLogger(ClientEngine.class);

   // Maps the port binding QName to the client HandlerChain
   private Map handlerChainMap = new HashMap();

   /**
    * Constructs the client engine with a given configuration.
    */
   public ClientEngine(EngineConfiguration config)
   {
      super(config);
   }

   /**
    * Register a handler chain for the given port name
    */
   public void registerHandlerChain(String portName, List infos, Set roles)
   {
      ClientHandlerChain chain = new ClientHandlerChain(infos, roles);
      handlerChainMap.put(portName, chain);
   }

   /**
    * Main routine of the AXIS engine.  In short we locate the appropriate
    * handler for the desired service and invoke() it.
    */
   public void invoke(MessageContext msgContext) throws AxisFault
   {
      log.debug("invoke: " + msgContext);

      String hName = null;
      Handler handler = null;

      // save previous context
      MessageContext previousContext = getCurrentMessageContext();

      try
      {
         // set active context
         setCurrentMessageContext(msgContext);

         //   Now we do the 'real' work.  The flow is basically:
         //
         //   Global Request Chain
         //   Service Ref Request Handler Chain
         //   Transport Request Chain - must have a send at the end
         //   Transport Response Chain
         //   Service Ref Response Handler Chain
         //   Global Response Chain
         //************************************************************

         msgContext.setPastPivot(false);

         ClientHandlerChain handlerChain = (ClientHandlerChain)getHandlerChain(msgContext);

         // The CTS resets its HanderTracker after the JNDI lookup
         // So if we init the handlers during lookup, that won't be tracked
         if (handlerChain.getState() == ClientHandlerChain.STATE_CREATED)
         {
            // what is the config for a handler chain?
            handlerChain.init(null);
         }

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

         // Process the Request Handlers
         if (handlerChain.handleRequest(msgContext) == false)
         {
            // revert direction and change into a reply message
            log.warn("FIXME: handlerChain.handleRequest() returned false");
            return;
         }

         /** 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.
          */
         hName = msgContext.getTransportName();
         if (hName != null && (handler = getTransport(hName)) != null)
         {
            handler.invoke(msgContext);
         }
         else
         {
            throw new AxisFault(Messages.getMessage("noTransport00", hName));
         }

         // [todo] detect if we got a fault msg
         boolean isFaultMessage = false;

         // Process the Fault Handlers
         if (isFaultMessage && handlerChain.handleFault(msgContext) == false)
         {
            // skip over the remaining message handlers in the chain and go strait to the client
            log.warn("FIXME: handlerChain.handleFault() returned false");
            return;
         }

         // Process the Response Handlers
         else if (handlerChain.handleResponse(msgContext) == false)
         {
            // skip over the remaining message handlers in the chain and go strait to the client
            log.warn("FIXME: handlerChain.handleResponse() returned false");
            return;
         }

         /* Process the Global Response Chain */
         /***********************************/
         if ((handler = getGlobalResponse()) != null)
         {
            handler.invoke(msgContext);
         }
      }
      catch (ConfigurationException e)
      {
         throw new IllegalStateException(e.toString());
      }
      finally
      {
         // restore previous state
         setCurrentMessageContext(previousContext);
      }
   }

   /**
    * Returns the handler chain for the current message context
    *
    * @return
    */
   private HandlerChain getHandlerChain(MessageContext msgContext)
   {
      HandlerChain handlerChain = null;

      // Return an empty handler chain
      if (handlerChainMap.size() == 0)
         handlerChain = new ClientHandlerChain(null, null);

      // We only have one handler chain associated to one port, return it
      if (handlerChainMap.size() == 1)
      {
         String portName = (String)handlerChainMap.keySet().iterator().next();
         handlerChain = (HandlerChain)handlerChainMap.get(portName);
         log.debug("Using handler chain for port: " + portName);
      }

      Call call = (Call)msgContext.getProperty(MessageContext.CALL);
      if (call == null)
         throw new JAXRPCException("Cannot obtain current call");

      if (call.getPortName() != null)
      {
         String portName = call.getPortName().getLocalPart();
         handlerChain = (HandlerChain)handlerChainMap.get(portName);
         log.debug("Using handler chain for port: " + portName);
      }

      // return an empty handler chain
      if (handlerChain == null)
      {
         handlerChain = new ClientHandlerChain(null, null);
         log.debug("Using empty handler chain");
      }

      return handlerChain;
   }
}