/*
 * 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.11.8.1 2005/09/27 23:19:46 rcampbell Exp $

import org.jboss.axis.AxisFault;
import org.jboss.axis.Constants;
import org.jboss.axis.MessageContext;
import org.jboss.axis.description.OperationDesc;
import org.jboss.axis.description.ParameterDesc;
import org.jboss.axis.description.ParameterWrapping;
import org.jboss.axis.enums.Style;
import org.jboss.axis.enums.Use;
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.axis.utils.JavaUtils;
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.deployment.BeanXMLMetaData;
import org.jboss.webservice.deployment.MetaDataRegistry;
import org.jboss.webservice.handler.ServerHandlerChain;
import org.jboss.webservice.metadata.PortComponentMetaData;
import org.jboss.webservice.metadata.WebserviceDescriptionMetaData;
import org.jboss.webservice.metadata.serviceref.HandlerMetaData;
import org.jboss.webservice.metadata.serviceref.InitParamMetaData;
import org.w3c.dom.Document;

import javax.management.MBeanServer;
import javax.management.ObjectName;
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;
import java.util.Iterator;
import java.util.List;

/**
 * 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();
         String requestURL = req.getRequestURL().toString();
         String hostURL = requestURL.substring(0, requestURL.indexOf(requestURI));

         WebserviceDescriptionMetaData webserviceDescription = portComponentInfo.getPortComponentMetaData()
               .getWebserviceDescription();
         DeploymentInfo di = portComponentInfo.getDeploymentInfo();
         WSDLRequestHandler wsdlRequestHandler = new WSDLRequestHandler(webserviceDescription, di);
         Document document = wsdlRequestHandler.getDocumentForPath(hostURL, 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[] args) throws Exception
   {
      OperationDesc opDesc = msgContext.getOperation();
      Style style = opDesc.getStyle();
      Use use = opDesc.getUse();

      // check if need to unwrap a doc/lit request structure
      // [TDI] 30-Jun-2005
      Class[] paramTypes = method.getParameterTypes();
      if (style == Style.DOCUMENT && use == Use.LITERAL && opDesc.getNumInParams() == 1 && paramTypes.length >= 1)
      {
         ParameterDesc paramDesc = (ParameterDesc)opDesc.getInParams().get(0);
         QName typeQName = paramDesc.getTypeQName();
         Object reqStruct = args[0];
         Class firstParamType = paramTypes[0];
         if (reqStruct != null && !firstParamType.isAssignableFrom(reqStruct.getClass()))
         {
            if (paramTypes.length == 1 && JavaUtils.isConvertable(reqStruct, firstParamType, false))
            {
               reqStruct = JavaUtils.convert(reqStruct, firstParamType);
               args = new Object[] { reqStruct };
            }
            else
            {
               if (paramDesc.getWrappedVariables() == null)
               {
                  ObjectName oname = AxisServiceMBean.OBJECT_NAME;
                  MetaDataRegistry metaDataRegistry = (MetaDataRegistry)server.getAttribute(oname, "MetaDataRegistry");
                  BeanXMLMetaData tmMetaData = metaDataRegistry.getTypeMappingMetaData(typeQName);
                  if (tmMetaData == null)
                     throw new IllegalStateException("Cannot obtain type mapping meta data for: " + typeQName);

                  opDesc.setWrapParameters(true);
                  paramDesc.setWrappedVariables(tmMetaData.getElementOrder());
               }
               
               ParameterDesc retParamDesc = (ParameterDesc)opDesc.getReturnParamDesc();
               if (retParamDesc.getWrappedVariables() == null)
               {
                  QName retTypeQName = retParamDesc.getTypeQName();
                  ObjectName oname = AxisServiceMBean.OBJECT_NAME;
                  MetaDataRegistry metaDataRegistry = (MetaDataRegistry)server.getAttribute(oname, "MetaDataRegistry");
                  BeanXMLMetaData tmMetaData = metaDataRegistry.getTypeMappingMetaData(retTypeQName);
                  if (tmMetaData == null)
                     throw new IllegalStateException("Cannot obtain type mapping meta data for: " + retTypeQName);

                  opDesc.setWrapParameters(true);
                  retParamDesc.setWrappedVariables(tmMetaData.getElementOrder());
               }
               
               args = ParameterWrapping.unwrapRequestParameters(opDesc, reqStruct);
            }
         }
      }

      ClassLoader ccl = Thread.currentThread().getContextClassLoader();
      try
      {
         Thread.currentThread().setContextClassLoader(getContextClassLoader());
         Object retObj = invokeServiceEndpoint(msgContext, method, obj, args);

         // wrap the repsonse if this has not been done already, 
         // i.e. in the interceptor before the response envelope was prepared 
         ParameterDesc paramDesc = opDesc.getReturnParamDesc();
         Class resStructType = paramDesc.getJavaType();
         if (opDesc.isWrapParameters() && (retObj == null || !resStructType.isAssignableFrom(retObj.getClass())))
            retObj = ParameterWrapping.wrapResponseParameter(opDesc, retObj);

         return retObj;
      }
      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;
}
