/*
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.webservice.server;

// $Id: InvokerProvider.java,v 1.20.2.8 2005/04/12 16:07:46 starksm Exp $

import org.jboss.axis.AxisFault;
import org.jboss.axis.MessageContext;
import org.jboss.axis.handlers.soap.SOAPService;
import org.jboss.axis.message.SOAPEnvelopeAxisImpl;
import org.jboss.axis.providers.java.RPCInvocation;
import org.jboss.axis.providers.java.RPCProvider;
import org.jboss.axis.transport.http.HTTPConstants;
import org.jboss.deployment.DeploymentInfo;
import org.jboss.logging.Logger;
import org.jboss.mx.util.MBeanServerLocator;
import org.jboss.webservice.AxisServiceMBean;
import org.jboss.webservice.PortComponentInfo;
import org.jboss.webservice.handler.ServerHandlerChain;
import org.jboss.webservice.metadata.HandlerMetaData;
import org.jboss.webservice.metadata.InitParamMetaData;
import org.jboss.webservice.metadata.PortComponentMetaData;
import org.jboss.webservice.metadata.WebserviceDescriptionMetaData;
import org.w3c.dom.Document;

import javax.management.MBeanServer;
import javax.servlet.http.HttpServletRequest;
import javax.xml.namespace.QName;
import javax.xml.rpc.handler.HandlerInfo;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;

/**
 * An RPC provider base that provides access to some
 * webservice specific meta-data artifacts through JMX.
 *
 * @author Thomas.Diesler@jboss.org
 * @since 15-April-2004
 */
public abstract class InvokerProvider extends RPCProvider
{
   /** @since 4.0.2 */
   static final long serialVersionUID = 3036485550614460123L;
   // provide logging
   private Logger log = Logger.getLogger(InvokerProvider.class);

   // The MBeanServer
   protected MBeanServer server;

   // This endpoint's port component
   protected PortComponentInfo portComponentInfo;
   // This port components handler chain
   protected ServerHandlerChain handlerChain;

   /**
    * Get deployment meta info
    * <p/>
    * Note, the msgContext may be null if the request is not a SOAP message
    * this is the case for the list operation http://localhost:8080/ws4ee/servlet/AxisServlet
    *
    * @param msgContext the SOAP MessageContext, or null
    */
   public void initServiceDesc(SOAPService service, MessageContext msgContext)
           throws AxisFault
   {
      log.debug("initServiceDesc: service=" + service.getName());

      try
      {
         // locate the MBeanServer
         server = MBeanServerLocator.locateJBoss();

         String wsID = (String)service.getOption(PortComponentMetaData.PARAMETER_WEBSERVICE_ID);
         portComponentInfo = (PortComponentInfo)server.invoke(AxisServiceMBean.OBJECT_NAME,
                 "getPortComponentInfo", new Object[]{wsID}, new String[]{String.class.getName()});
         if (portComponentInfo == null)
            throw new ServiceException("Cannot obtain port component info for: " + wsID);

         // init the handler chain
         initHandlerChain();
      }
      catch (Exception e)
      {
         throw new ServiceException("Cannot initialize webservice", e);
      }

      // delegate to parent
      super.initServiceDesc(service, msgContext);
   }

   /**
    * Init the handler chain
    */
   private void initHandlerChain() throws Exception
   {
      ClassLoader cl = getContextClassLoader();

      HashSet handlerRoles = new HashSet();
      ArrayList handlerInfos = new ArrayList();

      HandlerMetaData[] handlers = portComponentInfo.getPortComponentMetaData().getHandlers();
      for (int i = 0; i < handlers.length; i++)
      {
         HandlerMetaData hMetaData = handlers[i];
         handlerRoles.addAll(Arrays.asList(hMetaData.getSoapRoles()));

         Class hClass = cl.loadClass(hMetaData.getHandlerClass());

         HashMap hConfig = new HashMap();
         InitParamMetaData[] params = hMetaData.getInitParams();
         for (int j = 0; j < params.length; j++)
         {
            InitParamMetaData param = params[j];
            hConfig.put(param.getParamName(), param.getParamValue());
         }
         QName[] hHeaders = hMetaData.getSoapHeaders();
         HandlerInfo info = new HandlerInfo(hClass, hConfig, hHeaders);

         String serviceName = portComponentInfo.getPortComponentMetaData().getPortComponentName();
         log.debug("Adding server side handler to service '" + serviceName + "': " + info);

         handlerInfos.add(info);
      }

      handlerChain = new ServerHandlerChain(handlerInfos, handlerRoles);

      ClassLoader ccl = Thread.currentThread().getContextClassLoader();
      try
      {
         Thread.currentThread().setContextClassLoader(getContextClassLoader());

         if (handlerChain.getState() == ServerHandlerChain.STATE_CREATED)
         {
            // what is the config for a handler chain?
            handlerChain.init(null);
         }
      }
      finally
      {
         Thread.currentThread().setContextClassLoader(ccl);
      }
   }

   /**
    * Get the context CLassLoader for this service
    */
   abstract protected ClassLoader getContextClassLoader();

   /**
    * Generate the WSDL for this service.
    * <p/>
    * Put in the "WSDL" property of the message context
    * as a org.w3c.dom.Document
    */
   public void generateWSDL(MessageContext msgContext) throws AxisFault
   {
      HttpServletRequest req = (HttpServletRequest)msgContext.getProperty(HTTPConstants.MC_HTTP_SERVLETREQUEST);
      if (req == null)
         throw new IllegalStateException("Cannot obtain HttpServletRequest from msg context");

      try
      {
         // For the base document the resourcePath should be null
         String resourcePath = (String)msgContext.getProperty(MessageContext.WSDLGEN_RESOURCE);
         String requestURI = req.getRequestURI();

         WebserviceDescriptionMetaData webserviceDescription = portComponentInfo.getPortComponentMetaData().getWebserviceDescription();
         DeploymentInfo di = portComponentInfo.getDeploymentInfo();
         WSDLRequestHandler wsdlRequestHandler = new WSDLRequestHandler(webserviceDescription, di);
         Document document = wsdlRequestHandler.getDocumentForPath(requestURI, resourcePath);

         msgContext.setProperty("WSDL", document);
      }
      catch (Exception e)
      {
         log.error("Cannot process WSDL document", e);
      }
   }

   public RPCInvocation createRPCInvocation(MessageContext msgContext, SOAPEnvelopeAxisImpl reqEnv, SOAPEnvelopeAxisImpl resEnv, Object obj)
   {
      RPCInvocation invocation = new JBossRPCInvocation(this, msgContext, reqEnv, resEnv, obj);
      return invocation;
   }

   public PortComponentInfo getPortComponentInfo()
   {
      return portComponentInfo;
   }

   /**
    * Returns the Class info about the service class.
    */
   protected Class getServiceClass(String clsName, SOAPService service, MessageContext msgContext)
           throws AxisFault
   {
      ClassLoader ccl = Thread.currentThread().getContextClassLoader();
      try
      {
         // The super implementation tries the classloader that is set in the msgContext
         // if not found it falls back to the context class loader
         Thread.currentThread().setContextClassLoader(getContextClassLoader());

         String seiName = portComponentInfo.getPortComponentMetaData().getServiceEndpointInterface();
         return super.getServiceClass(seiName, service, msgContext);
      }
      finally
      {
         Thread.currentThread().setContextClassLoader(ccl);
      }
   }

   /**
    * Return the class name of the service.
    * We return the SEI instead.
    */
   protected String getServiceClassName(org.jboss.axis.Handler handler)
   {
      return portComponentInfo.getPortComponentMetaData().getServiceEndpointInterface();
   }

   /**
    * Invoke the service endpoint.
    */
   protected final Object invokeMethod(MessageContext msgContext, Method method, Object obj, Object[] argValues) throws Exception
   {
      ClassLoader ccl = Thread.currentThread().getContextClassLoader();
      try
      {
         Thread.currentThread().setContextClassLoader(getContextClassLoader());
         return invokeServiceEndpoint(msgContext, method, obj, argValues);
      }
      finally
      {
         Thread.currentThread().setContextClassLoader(ccl);
      }
   }

   /**
    * Overwrite for EJB, and JSE endpoint invocation
    */
   protected abstract Object invokeServiceEndpoint(MessageContext msgContext, Method method, Object obj, Object[] argValues) throws Exception;
}