/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.ws.metadata.builder.jaxrpc;

//$Id: JAXRPCMetaDataBuilder.java 12651 2010-07-15 16:28:56Z darran.lofthouse@jboss.com $

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.jws.soap.SOAPBinding.ParameterStyle;
import javax.xml.namespace.QName;
import javax.xml.rpc.ParameterMode;
import javax.xml.rpc.encoding.TypeMappingRegistry;

import org.apache.xerces.xs.XSTypeDefinition;
import org.jboss.logging.Logger;
import org.jboss.ws.Constants;
import org.jboss.ws.WSException;
import org.jboss.ws.core.binding.TypeMappingImpl;
import org.jboss.ws.core.jaxrpc.EncodedTypeMapping;
import org.jboss.ws.core.jaxrpc.LiteralTypeMapping;
import org.jboss.ws.core.jaxrpc.TypeMappingRegistryImpl;
import org.jboss.ws.core.jaxrpc.UnqualifiedFaultException;
import org.jboss.ws.core.soap.Style;
import org.jboss.ws.core.soap.Use;
import org.jboss.ws.extensions.xop.jaxrpc.XOPScanner;
import org.jboss.ws.metadata.builder.MetaDataBuilder;
import org.jboss.ws.metadata.jaxrpcmapping.ExceptionMapping;
import org.jboss.ws.metadata.jaxrpcmapping.JavaWsdlMapping;
import org.jboss.ws.metadata.jaxrpcmapping.JavaXmlTypeMapping;
import org.jboss.ws.metadata.jaxrpcmapping.MethodParamPartsMapping;
import org.jboss.ws.metadata.jaxrpcmapping.ServiceEndpointInterfaceMapping;
import org.jboss.ws.metadata.jaxrpcmapping.ServiceEndpointMethodMapping;
import org.jboss.ws.metadata.jaxrpcmapping.VariableMapping;
import org.jboss.ws.metadata.jaxrpcmapping.WsdlMessageMapping;
import org.jboss.ws.metadata.jaxrpcmapping.WsdlReturnValueMapping;
import org.jboss.ws.metadata.umdm.EndpointMetaData;
import org.jboss.ws.metadata.umdm.FaultMetaData;
import org.jboss.ws.metadata.umdm.OperationMetaData;
import org.jboss.ws.metadata.umdm.ParameterMetaData;
import org.jboss.ws.metadata.umdm.ServiceMetaData;
import org.jboss.ws.metadata.umdm.TypeMappingMetaData;
import org.jboss.ws.metadata.umdm.TypesMetaData;
import org.jboss.ws.metadata.umdm.WrappedParameter;
import org.jboss.ws.metadata.wsdl.WSDLBindingOperation;
import org.jboss.ws.metadata.wsdl.WSDLBindingOperationInput;
import org.jboss.ws.metadata.wsdl.WSDLBindingOperationOutput;
import org.jboss.ws.metadata.wsdl.WSDLDefinitions;
import org.jboss.ws.metadata.wsdl.WSDLEndpoint;
import org.jboss.ws.metadata.wsdl.WSDLInterface;
import org.jboss.ws.metadata.wsdl.WSDLInterfaceFault;
import org.jboss.ws.metadata.wsdl.WSDLInterfaceOperation;
import org.jboss.ws.metadata.wsdl.WSDLInterfaceOperationInput;
import org.jboss.ws.metadata.wsdl.WSDLInterfaceOperationOutfault;
import org.jboss.ws.metadata.wsdl.WSDLInterfaceOperationOutput;
import org.jboss.ws.metadata.wsdl.WSDLMIMEPart;
import org.jboss.ws.metadata.wsdl.WSDLProperty;
import org.jboss.ws.metadata.wsdl.WSDLRPCPart;
import org.jboss.ws.metadata.wsdl.WSDLRPCSignatureItem;
import org.jboss.ws.metadata.wsdl.WSDLSOAPHeader;
import org.jboss.ws.metadata.wsdl.WSDLTypes;
import org.jboss.ws.metadata.wsdl.WSDLUtils;
import org.jboss.ws.metadata.wsdl.WSDLRPCSignatureItem.Direction;
import org.jboss.ws.metadata.wsdl.xmlschema.JBossXSModel;
import org.jboss.wsf.common.JavaUtils;

/**
 * A meta data builder that is based on webservices.xml.
 *
 * @author Thomas.Diesler@jboss.org
 * @author <a href="mailto:jason.greene@jboss.org">Jason T. Greene</a>
 * @since 19-Oct-2005
 */
public abstract class JAXRPCMetaDataBuilder extends MetaDataBuilder
{
   // provide logging
   final Logger log = Logger.getLogger(JAXRPCMetaDataBuilder.class);

   protected QName lookupSchemaType(WSDLInterfaceOperation operation, QName element)
   {
      WSDLDefinitions wsdlDefinitions = operation.getWsdlInterface().getWsdlDefinitions();
      WSDLTypes wsdlTypes = wsdlDefinitions.getWsdlTypes();
      return wsdlTypes.getXMLType(element);
   }

   protected void setupTypesMetaData(ServiceMetaData serviceMetaData)
   {
      WSDLDefinitions wsdlDefinitions = serviceMetaData.getWsdlDefinitions();
      JavaWsdlMapping javaWsdlMapping = serviceMetaData.getJavaWsdlMapping();
      TypesMetaData typesMetaData = serviceMetaData.getTypesMetaData();

      // Copy the schema locations to the types meta data
      if (wsdlDefinitions != null)
      {
         WSDLTypes wsdlTypes = wsdlDefinitions.getWsdlTypes();
         typesMetaData.setSchemaModel(WSDLUtils.getSchemaModel(wsdlTypes));
      }

      // Copy the type mappings to the types meta data
      if (javaWsdlMapping != null)
      {
         for (JavaXmlTypeMapping xmlTypeMapping : javaWsdlMapping.getJavaXmlTypeMappings())
         {
            String javaTypeName = xmlTypeMapping.getJavaType();
            String qnameScope = xmlTypeMapping.getQnameScope();

            QName xmlType = xmlTypeMapping.getRootTypeQName();
            QName anonymousXMLType = xmlTypeMapping.getAnonymousTypeQName();
            if (xmlType == null && anonymousXMLType != null)
               xmlType = anonymousXMLType;

            String nsURI = xmlType.getNamespaceURI();
            if (Constants.NS_SCHEMA_XSD.equals(nsURI) == false && Constants.URI_SOAP11_ENC.equals(nsURI) == false)
            {
               TypeMappingMetaData tmMetaData = new TypeMappingMetaData(typesMetaData, xmlType, javaTypeName);
               tmMetaData.setQNameScope(qnameScope);
               typesMetaData.addTypeMapping(tmMetaData);
            }
         }

         for (ExceptionMapping exceptionMapping : javaWsdlMapping.getExceptionMappings())
         {
            QName xmlType = exceptionMapping.getWsdlMessage();
            String javaTypeName = exceptionMapping.getExceptionType();
            TypeMappingMetaData tmMetaData = new TypeMappingMetaData(typesMetaData, xmlType, javaTypeName);
            typesMetaData.addTypeMapping(tmMetaData);
         }
      }
   }

   protected void setupOperationsFromWSDL(EndpointMetaData epMetaData, WSDLEndpoint wsdlEndpoint, ServiceEndpointInterfaceMapping seiMapping)
   {
      WSDLDefinitions wsdlDefinitions = wsdlEndpoint.getInterface().getWsdlDefinitions();

      // For every WSDL interface operation build the OperationMetaData
      WSDLInterface wsdlInterface = wsdlEndpoint.getInterface();
      for (WSDLInterfaceOperation wsdlOperation : wsdlInterface.getOperations())
      {
         QName opQName = wsdlOperation.getName();
         String opName = opQName.getLocalPart();

         WSDLBindingOperation wsdlBindingOperation = wsdlOperation.getBindingOperation();
         if (wsdlBindingOperation == null)
            log.warn("Could not locate binding operation for:" + opQName);

         // Change operation according namespace defined on binding 
         // <soap:body use="encoded" namespace="http://MarshallTestW2J.org/" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
         String namespaceURI = wsdlBindingOperation.getNamespaceURI();
         if (namespaceURI != null)
            opQName = new QName(namespaceURI, opName);

         // Set java method name
         String javaName = opName.substring(0, 1).toLowerCase() + opName.substring(1);
         ServiceEndpointMethodMapping seiMethodMapping = null;
         if (seiMapping != null)
         {
            epMetaData.setServiceEndpointInterfaceName(seiMapping.getServiceEndpointInterface());

            seiMethodMapping = seiMapping.getServiceEndpointMethodMappingByWsdlOperation(opName);
            if (seiMethodMapping == null)
               throw new WSException("Cannot obtain method mapping for: " + opName);

            javaName = seiMethodMapping.getJavaMethodName();
         }

         OperationMetaData opMetaData = new OperationMetaData(epMetaData, opQName, javaName);
         epMetaData.addOperation(opMetaData);

         // Set the operation style
         String style = wsdlOperation.getStyle();
         epMetaData.setStyle((Constants.URI_STYLE_DOCUMENT.equals(style) ? Style.DOCUMENT : Style.RPC));

         // Set the operation MEP
         if (Constants.WSDL20_PATTERN_IN_ONLY.equals(wsdlOperation.getPattern()))
            opMetaData.setOneWay(true);

         // Set the operation SOAPAction
         if (wsdlBindingOperation != null)
            opMetaData.setSOAPAction(wsdlBindingOperation.getSOAPAction());

         // Get the type mapping for the encoding style
         String encStyle = epMetaData.getEncodingStyle().toURI();
         TypeMappingRegistry tmRegistry = new TypeMappingRegistryImpl();
         TypeMappingImpl typeMapping = (TypeMappingImpl)tmRegistry.getTypeMapping(encStyle);

         // Build the parameter meta data
         if (opMetaData.getStyle() == Style.RPC)
         {
            buildParameterMetaDataRpc(opMetaData, wsdlOperation, seiMethodMapping, typeMapping);
         }
         else
         {
            buildParameterMetaDataDoc(opMetaData, wsdlOperation, seiMethodMapping, typeMapping);
         }

         // Build operation faults
         buildFaultMetaData(opMetaData, wsdlOperation);

         // process further operation extensions
         processOpMetaExtensions(opMetaData, wsdlOperation);
      }
   }

   private ParameterMetaData buildInputParameter(OperationMetaData opMetaData, WSDLInterfaceOperation wsdlOperation, ServiceEndpointMethodMapping seiMethodMapping,
         TypeMappingImpl typeMapping, String partName, QName xmlName, QName xmlType, int pos, boolean optional)
   {
      WSDLRPCSignatureItem item = wsdlOperation.getRpcSignatureitem(partName);
      if (item != null)
         pos = item.getPosition();

      String javaTypeName = typeMapping.getJavaTypeName(xmlType);
      boolean mapped = false;
      if (seiMethodMapping != null)
      {
         MethodParamPartsMapping paramMapping = seiMethodMapping.getMethodParamPartsMappingByPartName(partName);
         if (paramMapping != null)
         {
            javaTypeName = paramMapping.getParamType();
            pos = paramMapping.getParamPosition();
            mapped = true;
         }
         else if (!optional)
         {
            throw new WSException("Cannot obtain method parameter mapping for message part '" + partName + "' in wsdl operation: "
                  + seiMethodMapping.getWsdlOperation());
         }
      }

      // For now we ignore unlisted headers with no mapping information
      if (!mapped && optional)
         return null;

      JavaWsdlMapping javaWsdlMapping = opMetaData.getEndpointMetaData().getServiceMetaData().getJavaWsdlMapping();
      if (javaTypeName == null && javaWsdlMapping != null)
      {
         String packageName = javaWsdlMapping.getPackageNameForNamespaceURI(xmlType.getNamespaceURI());
         if (packageName != null)
         {
            javaTypeName = packageName + "." + xmlType.getLocalPart();
            log.warn("Guess java type from package mapping: [xmlType=" + xmlType + ",javaType=" + javaTypeName + "]");
         }
      }

      if (javaTypeName == null)
         throw new WSException("Cannot obtain java type mapping for: " + xmlType);

      ParameterMetaData inMetaData = new ParameterMetaData(opMetaData, xmlName, xmlType, javaTypeName);
      inMetaData.setPartName(partName);
      inMetaData.setIndex(pos);
      opMetaData.addParameter(inMetaData);

      TypesMetaData typesMetaData = opMetaData.getEndpointMetaData().getServiceMetaData().getTypesMetaData();
      // In arrays of user types, wscompile does not generate a mapping in jaxrpc-mapping.xml
      if (typesMetaData.getTypeMappingByXMLType(xmlType) == null)
      {
         String nsURI = xmlType.getNamespaceURI();
         if (Constants.NS_SCHEMA_XSD.equals(nsURI) == false && Constants.URI_SOAP11_ENC.equals(nsURI) == false)
         {
            TypeMappingMetaData tmMetaData = new TypeMappingMetaData(typesMetaData, xmlType, javaTypeName);
            typesMetaData.addTypeMapping(tmMetaData);
         }
      }

      return inMetaData;
   }

   private ParameterMetaData buildOutputParameter(OperationMetaData opMetaData, WSDLInterfaceOperation wsdlOperation, ServiceEndpointMethodMapping seiMethodMapping,
         int pos, String partName, QName xmlName, QName xmlType, TypeMappingImpl typeMapping, boolean optional)
   {
      // Default is first listed output
      boolean hasReturnMapping = opMetaData.getReturnParameter() == null;

      WSDLRPCSignatureItem item = wsdlOperation.getRpcSignatureitem(partName);
      if (item != null)
      {
         hasReturnMapping = item.getDirection() == Direction.RETURN;
         pos = item.getPosition();
      }

      String javaTypeName = typeMapping.getJavaTypeName(xmlType);

      boolean mapped = false;
      if (seiMethodMapping != null)
      {
         MethodParamPartsMapping paramMapping = seiMethodMapping.getMethodParamPartsMappingByPartName(partName);
         if (paramMapping != null)
         {
            javaTypeName = paramMapping.getParamType();
            pos = paramMapping.getParamPosition();
            hasReturnMapping = false;
            mapped = true;
         }
         else
         {
            WsdlReturnValueMapping returnMapping = seiMethodMapping.getWsdlReturnValueMapping();
            if (returnMapping != null)
            {
               String mappingPart = returnMapping.getWsdlMessagePartName();
               if (mappingPart != null && partName.equals(mappingPart))
               {
                  javaTypeName = returnMapping.getMethodReturnValue();
                  hasReturnMapping = true;
                  mapped = true;
               }
            }
         }
      }

      // For now we ignore unlisted headers with no mapping information
      if (!mapped && optional)
         return null;

      JavaWsdlMapping javaWsdlMapping = opMetaData.getEndpointMetaData().getServiceMetaData().getJavaWsdlMapping();
      if (javaTypeName == null && javaWsdlMapping != null)
      {
         String packageName = javaWsdlMapping.getPackageNameForNamespaceURI(xmlType.getNamespaceURI());
         if (packageName != null)
         {
            javaTypeName = packageName + "." + xmlType.getLocalPart();
            log.warn("Guess java type from package mapping: [xmlType=" + xmlType + ",javaType=" + javaTypeName + "]");
         }
      }

      if (javaTypeName == null)
         throw new WSException("Cannot obtain java type mapping for: " + xmlType);

      ParameterMetaData outMetaData = new ParameterMetaData(opMetaData, xmlName, xmlType, javaTypeName);
      outMetaData.setPartName(partName);

      if (hasReturnMapping)
      {
         opMetaData.setReturnParameter(outMetaData);
      }
      else
      {
         outMetaData.setIndex(pos);
         outMetaData.setMode(ParameterMode.OUT);
         opMetaData.addParameter(outMetaData);
      }

      TypesMetaData typesMetaData = opMetaData.getEndpointMetaData().getServiceMetaData().getTypesMetaData();
      // In arrays of user types, wscompile does not generate a mapping in jaxrpc-mapping.xml
      if (typesMetaData.getTypeMappingByXMLType(xmlType) == null)
      {
         String nsURI = xmlType.getNamespaceURI();
         if (Constants.NS_SCHEMA_XSD.equals(nsURI) == false && Constants.URI_SOAP11_ENC.equals(nsURI) == false)
         {
            TypeMappingMetaData tmMetaData = new TypeMappingMetaData(typesMetaData, xmlType, javaTypeName);
            typesMetaData.addTypeMapping(tmMetaData);
         }
      }

      setupSOAPArrayParameter(outMetaData);
      return outMetaData;
   }

   private int processBindingParameters(OperationMetaData opMetaData, WSDLInterfaceOperation wsdlOperation, ServiceEndpointMethodMapping seiMethodMapping,
         TypeMappingImpl typeMapping, WSDLBindingOperation bindingOperation, int wsdlPosition)
   {
      WSDLBindingOperationInput bindingInput = bindingOperation.getInputs()[0];
      for (WSDLSOAPHeader header : bindingInput.getSoapHeaders())
      {
         QName xmlName = header.getElement();
         QName xmlType = lookupSchemaType(wsdlOperation, xmlName);
         String partName = header.getPartName();

         ParameterMetaData pmd = buildInputParameter(opMetaData, wsdlOperation, seiMethodMapping, typeMapping, partName, xmlName, xmlType, wsdlPosition++, !header
               .isIncludeInSignature());
         if (pmd != null)
            pmd.setInHeader(true);
      }
      for (WSDLMIMEPart mimePart : bindingInput.getMimeParts())
      {
         String partName = mimePart.getPartName();
         QName xmlName = new QName(partName);
         QName xmlType = mimePart.getXmlType();

         ParameterMetaData pmd = buildInputParameter(opMetaData, wsdlOperation, seiMethodMapping, typeMapping, partName, xmlName, xmlType, wsdlPosition++, false);
         pmd.setSwA(true);
         pmd.setMimeTypes(mimePart.getMimeTypes());
      }

      return wsdlPosition;
   }

   private int processBindingOutputParameters(OperationMetaData opMetaData, WSDLInterfaceOperation wsdlOperation, ServiceEndpointMethodMapping seiMethodMapping,
         TypeMappingImpl typeMapping, WSDLBindingOperation bindingOperation, int wsdlPosition)
   {
      WSDLBindingOperationOutput bindingOutput = bindingOperation.getOutputs()[0];
      for (WSDLSOAPHeader header : bindingOutput.getSoapHeaders())
      {
         String partName = header.getPartName();
         QName xmlName = header.getElement();

         ParameterMetaData outMetaData = opMetaData.getParameter(xmlName);
         if (outMetaData != null)
         {
            outMetaData.setMode(ParameterMode.INOUT);
         }
         else
         {
            QName xmlType = lookupSchemaType(wsdlOperation, xmlName);

            ParameterMetaData pmd = buildOutputParameter(opMetaData, wsdlOperation, seiMethodMapping, wsdlPosition, partName, xmlName, xmlType, typeMapping, !header
                  .isIncludeInSignature());
            if (pmd != null)
            {
               pmd.setInHeader(true);
               if (opMetaData.getReturnParameter() != pmd)
                  wsdlPosition++;
            }
         }
      }

      for (WSDLMIMEPart mimePart : bindingOutput.getMimeParts())
      {
         String partName = mimePart.getPartName();
         QName xmlName = new QName(partName);

         ParameterMetaData outMetaData = opMetaData.getParameter(xmlName);
         if (outMetaData != null)
         {
            outMetaData.setMode(ParameterMode.INOUT);
         }
         else
         {
            QName xmlType = mimePart.getXmlType();

            ParameterMetaData pmd = buildOutputParameter(opMetaData, wsdlOperation, seiMethodMapping, wsdlPosition, partName, xmlName, xmlType, typeMapping, false);
            pmd.setSwA(true);
            pmd.setMimeTypes(mimePart.getMimeTypes());

            if (opMetaData.getReturnParameter() != pmd)
               wsdlPosition++;
         }
      }

      return wsdlPosition;
   }

   /* SOAP-ENC:Array
    *
    * FIXME: This hack should be removed as soon as we can reliably get the
    * soapenc:arrayType from wsdl + schema.
    */
   private void setupSOAPArrayParameter(ParameterMetaData paramMetaData)
   {
      Use use = paramMetaData.getOperationMetaData().getUse();
      String xmlTypeLocalPart = paramMetaData.getXmlType().getLocalPart();
      if (use == Use.ENCODED && xmlTypeLocalPart.indexOf("ArrayOf") >= 0)
      {
         paramMetaData.setSOAPArrayParam(true);
         try
         {
            String javaTypeName = paramMetaData.getJavaTypeName();
            // This approach determins the array component type from the javaTypeName.
            // It will not work for user defined types, nor will the array dimension be
            // initialized properly. Ideally the array parameter meta data should be initialized
            // from the XSModel or wherever it is defined in WSDL.
            Class javaType = JavaUtils.loadJavaType(javaTypeName);
            Class compJavaType = javaType.getComponentType();

            if (xmlTypeLocalPart.indexOf("ArrayOfArrayOf") >= 0)
               compJavaType = compJavaType.getComponentType();

            boolean isSoapEnc = xmlTypeLocalPart.toLowerCase().indexOf("soapenc") > 0;
            TypeMappingImpl typeMapping = isSoapEnc ? new EncodedTypeMapping() : new LiteralTypeMapping();
            QName compXMLType = typeMapping.getXMLType(compJavaType);

            if (compXMLType != null)
            {
               boolean isBase64 = compXMLType.getLocalPart().startsWith("base64");
               if (isBase64 && xmlTypeLocalPart.toLowerCase().indexOf("hex") > 0)
                  compXMLType = isSoapEnc ? Constants.TYPE_SOAP11_HEXBINARY : Constants.TYPE_LITERAL_HEXBINARY;
            }
            
            paramMetaData.setSOAPArrayCompType(compXMLType);
         }
         catch (ClassNotFoundException e)
         {
            // ignore that user defined types cannot be loaded yet
         }
      }
   }

   private void setupXOPAttachmentParameter(WSDLInterfaceOperation operation, ParameterMetaData paramMetaData)
   {
      QName xmlType = paramMetaData.getXmlType();

      // An XOP parameter is detected if it is a complex type that derives from xsd:base64Binary
      WSDLTypes wsdlTypes = operation.getWsdlInterface().getWsdlDefinitions().getWsdlTypes();
      JBossXSModel schemaModel = WSDLUtils.getSchemaModel(wsdlTypes);
      String localPart = xmlType.getLocalPart() != null ? xmlType.getLocalPart() : "";
      String ns = xmlType.getNamespaceURI() != null ? xmlType.getNamespaceURI() : "";
      XSTypeDefinition xsType = schemaModel.getTypeDefinition(localPart, ns);
      XOPScanner scanner = new XOPScanner();
      if (scanner.findXOPTypeDef(xsType) != null | (localPart.equals("base64Binary") && ns.equals(Constants.NS_SCHEMA_XSD)))
      {
         // FIXME: read the xmime:contentType from the element declaration
         // See SchemaUtils#findXOPTypeDef(XSTypeDefinition typeDef) for details

         /*
          FIXME: the classloader is not set yet
          paramMetaData.setXopContentType(
          MimeUtils.resolveMimeType(paramMetaData.getJavaType())
          );
          */

         paramMetaData.setXOP(true);

      }
   }

   /*
    * Perhaps the JAX-RPC mapping model should be hash based. For now we optimize just this case.
    */
   private Map<String, String> createVariableMappingMap(VariableMapping[] mappings)
   {
      HashMap<String, String> map = new HashMap<String, String>();
      if (mappings != null)
         for (VariableMapping mapping : mappings)
            map.put(mapping.getXmlElementName(), mapping.getJavaVariableName());

      return map;
   }

   private void buildParameterMetaDataRpc(OperationMetaData opMetaData, WSDLInterfaceOperation wsdlOperation, ServiceEndpointMethodMapping seiMethodMapping,
         TypeMappingImpl typeMapping)
   {
      log.trace("buildParameterMetaDataRpc: " + opMetaData.getQName());

      WSDLBindingOperation bindingOperation = wsdlOperation.getBindingOperation();
      if (bindingOperation == null)
         throw new WSException("Could not locate binding operation for:" + opMetaData.getQName());

      // RPC has one input
      WSDLInterfaceOperationInput input = wsdlOperation.getInputs()[0];
      int wsdlPosition = 0;
      for (WSDLRPCPart part : input.getChildParts())
      {
         QName xmlType = part.getType();
         String partName = part.getName();
         QName xmlName = new QName(partName);

         ParameterMetaData pmd = buildInputParameter(opMetaData, wsdlOperation, seiMethodMapping, typeMapping, partName, xmlName, xmlType, wsdlPosition++, false);

         setupXOPAttachmentParameter(wsdlOperation, pmd);
         setupSOAPArrayParameter(pmd);
      }

      wsdlPosition = processBindingParameters(opMetaData, wsdlOperation, seiMethodMapping, typeMapping, bindingOperation, wsdlPosition);

      WSDLInterfaceOperationOutput[] outputs = wsdlOperation.getOutputs();
      if (outputs.length > 0)
      {
         WSDLInterfaceOperationOutput output = outputs[0];
         for (WSDLRPCPart part : output.getChildParts())
         {
            String partName = part.getName();

            ParameterMetaData outMetaData = opMetaData.getParameter(new QName(partName));
            if (outMetaData != null)
            {
               outMetaData.setMode(ParameterMode.INOUT);
            }
            else
            {
               QName xmlName = new QName(partName);
               QName xmlType = part.getType();

               ParameterMetaData pmd = buildOutputParameter(opMetaData, wsdlOperation, seiMethodMapping, wsdlPosition, partName, xmlName, xmlType, typeMapping, false);
               if (opMetaData.getReturnParameter() != pmd)
                  wsdlPosition++;

               setupXOPAttachmentParameter(wsdlOperation, pmd);
               setupSOAPArrayParameter(pmd);
            }
         }

         processBindingOutputParameters(opMetaData, wsdlOperation, seiMethodMapping, typeMapping, bindingOperation, wsdlPosition);
      }
      else if (wsdlOperation.getPattern() != Constants.WSDL20_PATTERN_IN_ONLY)
      {
         throw new WSException("RPC style was missing an output, and was not an IN-ONLY MEP.");
      }
   }

   private int processDocElement(OperationMetaData operation, WSDLInterfaceOperation wsdlOperation, WSDLBindingOperation bindingOperation,
         ServiceEndpointMethodMapping seiMethodMapping, TypeMappingImpl typeMapping, List<WrappedParameter> wrappedParameters,
         List<WrappedParameter> wrappedResponseParameters)
   {
      WSDLInterfaceOperationInput input = wsdlOperation.getInputs()[0];
      WSDLBindingOperationInput bindingInput = bindingOperation.getInputs()[0];
      int wsdlPosition;

      QName xmlName = input.getElement();
      QName xmlType = input.getXMLType();
      String javaTypeName = typeMapping.getJavaTypeName(xmlType);

      TypesMetaData typesMetaData = operation.getEndpointMetaData().getServiceMetaData().getTypesMetaData();
      TypeMappingMetaData typeMetaData = typesMetaData.getTypeMappingByXMLType(xmlType);
      if (typeMetaData != null)
         javaTypeName = typeMetaData.getJavaTypeName();

      if (javaTypeName == null)
         throw new WSException("Cannot obtain java type mapping for: " + xmlType);

      // Check if we need to wrap the parameters
      boolean isWrapped = isWrapped(seiMethodMapping, javaTypeName);
      operation.getEndpointMetaData().setParameterStyle(isWrapped ? ParameterStyle.WRAPPED : ParameterStyle.BARE);

      ParameterMetaData inMetaData = new ParameterMetaData(operation, xmlName, xmlType, javaTypeName);
      operation.addParameter(inMetaData);

      // Set the variable names
      if (inMetaData.getOperationMetaData().isDocumentWrapped())
      {
         if (seiMethodMapping == null)
            throw new IllegalArgumentException("Cannot wrap parameters without SEI method mapping");

         ServiceEndpointInterfaceMapping seiMapping = seiMethodMapping.getServiceEndpointInterfaceMapping();
         JavaXmlTypeMapping javaXmlTypeMapping = seiMapping.getJavaWsdlMapping().getTypeMappingForQName(xmlType);
         if (javaXmlTypeMapping == null)
            throw new WSException("Cannot obtain java/xml type mapping for: " + xmlType);

         Map<String, String> variableMap = createVariableMappingMap(javaXmlTypeMapping.getVariableMappings());
         for (MethodParamPartsMapping partMapping : seiMethodMapping.getMethodParamPartsMappings())
         {
            WsdlMessageMapping wsdlMessageMapping = partMapping.getWsdlMessageMapping();
            if (wsdlMessageMapping.isSoapHeader())
               continue;

            if (wsdlMessageMapping == null)
               throw new IllegalArgumentException("wsdl-message-message mapping required for document/literal wrapped");

            String elementName = wsdlMessageMapping.getWsdlMessagePartName();

            // Skip attachments
            if (bindingInput.getMimePart(elementName) != null)
               continue;

            String variable = variableMap.get(elementName);
            if (variable == null)
               throw new IllegalArgumentException("Could not determine variable name for element: " + elementName);

            WrappedParameter wrapped = new WrappedParameter(new QName(elementName), partMapping.getParamType(), variable, partMapping.getParamPosition());

            String parameterMode = wsdlMessageMapping.getParameterMode();
            if (parameterMode == null || parameterMode.length() < 2)
               throw new IllegalArgumentException("Invalid parameter mode for element: " + elementName);

            if (!"OUT".equals(parameterMode))
               wrappedParameters.add(wrapped);
            if (!"IN".equals(parameterMode))
            {
               wrapped.setHolder(true);
               // wrapped parameters can not be shared between request/response objects (accessors)
               if ("INOUT".equals(parameterMode))
                  wrapped = new WrappedParameter(wrapped);
               wrappedResponseParameters.add(wrapped);
            }
         }
         inMetaData.setWrappedParameters(wrappedParameters);
         wsdlPosition = wrappedParameters.size();
      }
      else
      {
         if (seiMethodMapping != null)
         {
            MethodParamPartsMapping part = seiMethodMapping.getMethodParamPartsMappingByPartName(input.getPartName());
            if (part != null)
            {
               inMetaData.setJavaTypeName(part.getParamType());
               inMetaData.setIndex(part.getParamPosition());
            }
         }

         setupXOPAttachmentParameter(wsdlOperation, inMetaData);
         wsdlPosition = 1;
      }

      return wsdlPosition;
   }

   private boolean isWrapped(ServiceEndpointMethodMapping seiMethodMapping, String javaTypeName)
   {
      boolean isWrapParameters = (seiMethodMapping != null ? seiMethodMapping.isWrappedElement() : false);
      log.trace("isWrapParameters based on wrapped-element: " + isWrapParameters);
      if (isWrapParameters == false && seiMethodMapping != null)
      {

         MethodParamPartsMapping[] partsMappings = seiMethodMapping.getMethodParamPartsMappings();
         if (partsMappings.length > 0)
         {
            List<String> anyTypes = new ArrayList<String>();
            anyTypes.add("javax.xml.soap.SOAPElement");
            anyTypes.add("org.w3c.dom.Element");

            boolean matchingPartFound = false;
            for (MethodParamPartsMapping partsMapping : partsMappings)
            {
               String methodMappingTypeName = partsMapping.getParamType();
               if (methodMappingTypeName.equals(javaTypeName))
               {
                  matchingPartFound = true;
                  break;
               }
               // Check xsd:anyType
               else if (anyTypes.contains(javaTypeName) && anyTypes.contains(methodMappingTypeName))
               {
                  matchingPartFound = true;
                  break;
               }
               // Check assignability,
               else
               {
                  try
                  {
                     Class paramType = JavaUtils.loadJavaType(methodMappingTypeName);
                     Class javaType = JavaUtils.loadJavaType(javaTypeName);

                     if (JavaUtils.isAssignableFrom(javaType, paramType))
                     {
                        matchingPartFound = true;
                        break;
                     }
                  }
                  catch (ClassNotFoundException e)
                  {
                     // Ignore. For simple types this should work, others should
                     // be lexically equal if it is not wrapped.
                  }
               }
            }
            // Do we really want to continue to handle invalid mappings?
            isWrapParameters = (matchingPartFound == false);
            log.trace("isWrapParameters based on matching parts: " + isWrapParameters);
         }
      }
      return isWrapParameters;
   }

   private int processOutputDocElement(OperationMetaData opMetaData, WSDLInterfaceOperation wsdlOperation, ServiceEndpointMethodMapping seiMethodMapping,
         TypeMappingImpl typeMapping, List<WrappedParameter> wrappedResponseParameters, int wsdlPosition)
   {
      WSDLInterfaceOperationOutput opOutput = wsdlOperation.getOutputs()[0];
      QName xmlName = opOutput.getElement();
      QName xmlType = opOutput.getXMLType();

      String javaTypeName = typeMapping.getJavaTypeName(xmlType);

      TypesMetaData typesMetaData = opMetaData.getEndpointMetaData().getServiceMetaData().getTypesMetaData();
      if (typesMetaData.getTypeMappingByXMLType(xmlType) != null)
         javaTypeName = typesMetaData.getTypeMappingByXMLType(xmlType).getJavaTypeName();

      if (javaTypeName == null)
         throw new WSException("Cannot obtain java/xml type mapping for: " + xmlType);

      ParameterMetaData outMetaData = new ParameterMetaData(opMetaData, xmlName, xmlType, javaTypeName);

      boolean hasReturnMapping = true;
      if (opMetaData.isDocumentWrapped())
      {
         if (seiMethodMapping == null)
            throw new IllegalArgumentException("Cannot wrap parameters without SEI method mapping");

         WsdlReturnValueMapping returnValueMapping = seiMethodMapping.getWsdlReturnValueMapping();
         if (returnValueMapping != null)
         {
            ServiceEndpointInterfaceMapping seiMapping = seiMethodMapping.getServiceEndpointInterfaceMapping();
            JavaWsdlMapping javaWsdlMapping = seiMapping.getJavaWsdlMapping();
            JavaXmlTypeMapping javaXmlTypeMapping = javaWsdlMapping.getTypeMappingForQName(xmlType);
            if (javaXmlTypeMapping == null)
               throw new WSException("Cannot obtain java/xml type mapping for: " + xmlType);

            Map<String, String> map = createVariableMappingMap(javaXmlTypeMapping.getVariableMappings());
            if (map.size() > 0)
            {
               String elementName = returnValueMapping.getWsdlMessagePartName();
               String variable = map.get(elementName);
               if (variable == null)
                  throw new IllegalArgumentException("Could not determine variable name for element: " + elementName);

               String wrappedType = returnValueMapping.getMethodReturnValue();
               QName element = new QName(elementName);
               WrappedParameter wrappedParameter = new WrappedParameter(element, wrappedType, variable, WrappedParameter.RETURN);
               wrappedResponseParameters.add(0, wrappedParameter);
            }
         }

         outMetaData.setWrappedParameters(wrappedResponseParameters);
      }
      else
      {
         if (seiMethodMapping != null)
         {
            MethodParamPartsMapping part = seiMethodMapping.getMethodParamPartsMappingByPartName(opOutput.getPartName());
            String mode = null;
            if (part != null)
            {
               WsdlMessageMapping wsdlMessageMapping = part.getWsdlMessageMapping();
               mode = wsdlMessageMapping.getParameterMode();
            }
            if ("INOUT".equals(mode))
            {
               ParameterMetaData inMetaData = opMetaData.getParameter(xmlName);
               if (inMetaData != null)
               {
                  inMetaData.setMode(ParameterMode.INOUT);
                  return wsdlPosition;
               }

               throw new WSException("Could not update IN parameter to be INOUT, as indicated in the mapping: " + opOutput.getPartName());
            }
            // It's potentialy possible that an input parameter could exist with the same part name
            else if ("OUT".equals(mode))
            {
               hasReturnMapping = false;
               javaTypeName = part.getParamType();
               outMetaData.setIndex(part.getParamPosition());
               outMetaData.setJavaTypeName(javaTypeName);
            }
            else
            {
               WsdlReturnValueMapping returnValueMapping = seiMethodMapping.getWsdlReturnValueMapping();
               if (returnValueMapping != null)
               {
                  javaTypeName = returnValueMapping.getMethodReturnValue();
                  outMetaData.setJavaTypeName(javaTypeName);
               }
            }
         }

         setupXOPAttachmentParameter(wsdlOperation, outMetaData);
         setupSOAPArrayParameter(outMetaData);
      }

      if (hasReturnMapping)
      {
         opMetaData.setReturnParameter(outMetaData);
      }
      else
      {
         opMetaData.addParameter(outMetaData);
         outMetaData.setMode(ParameterMode.OUT);
         wsdlPosition++;
      }

      return wsdlPosition;
   }

   private void buildParameterMetaDataDoc(OperationMetaData opMetaData, WSDLInterfaceOperation wsdlOperation, ServiceEndpointMethodMapping seiMethodMapping,
         TypeMappingImpl typeMapping)
   {
      log.trace("buildParameterMetaDataDoc: " + opMetaData.getQName());

      WSDLBindingOperation bindingOperation = wsdlOperation.getBindingOperation();
      if (bindingOperation == null)
         throw new WSException("Could not locate binding operation for:" + bindingOperation);

      List<WrappedParameter> wrappedParameters = new ArrayList<WrappedParameter>();
      List<WrappedParameter> wrappedResponseParameters = new ArrayList<WrappedParameter>();

      int wsdlPosition = 0;
      // WS-I BP 1.0 allows document/literal bare to have zero message parts
      if (wsdlOperation.getInputs().length > 0)
      {
         wsdlPosition = processDocElement(opMetaData, wsdlOperation, bindingOperation, seiMethodMapping, typeMapping, wrappedParameters, wrappedResponseParameters);
         wsdlPosition = processBindingParameters(opMetaData, wsdlOperation, seiMethodMapping, typeMapping, bindingOperation, wsdlPosition);
      }
      else
      {
         // Set the default to bare in case there isn't an input object, revisit this
         opMetaData.getEndpointMetaData().setParameterStyle(ParameterStyle.BARE);
      }

      if (wsdlOperation.getOutputs().length > 0)
      {
         wsdlPosition = processOutputDocElement(opMetaData, wsdlOperation, seiMethodMapping, typeMapping, wrappedResponseParameters, wsdlPosition);
         wsdlPosition = processBindingOutputParameters(opMetaData, wsdlOperation, seiMethodMapping, typeMapping, bindingOperation, wsdlPosition);
      }
   }

   /**
    * Build default action according to the pattern described in
    * http://www.w3.org/Submission/2004/SUBM-ws-addressing-20040810/
    * Section 3.3.2 'Default Action Pattern'<br>
    * [target namespace]/[port type name]/[input|output name]
    *
    * @param wsdlOperation
    * @return action value
    */
   private String buildWsaActionValue(WSDLInterfaceOperation wsdlOperation)
   {
      WSDLProperty wsaAction = wsdlOperation.getProperty(Constants.WSDL_ATTRIBUTE_WSA_ACTION.toString());
      String actionValue = null;

      if (null == wsaAction)
      {

         String tns = wsdlOperation.getName().getNamespaceURI();
         String portTypeName = wsdlOperation.getName().getLocalPart();
         WSDLProperty messageName = wsdlOperation.getProperty("http://www.jboss.org/jbossws/messagename/in");

         actionValue = new String(tns + "/" + portTypeName + "/" + messageName.getValue());
      }
      else
      {
         actionValue = wsaAction.getValue();
      }

      return actionValue;
   }

   protected void buildFaultMetaData(OperationMetaData opMetaData, WSDLInterfaceOperation wsdlOperation)
   {
      TypesMetaData typesMetaData = opMetaData.getEndpointMetaData().getServiceMetaData().getTypesMetaData();

      WSDLInterface wsdlInterface = wsdlOperation.getWsdlInterface();
      for (WSDLInterfaceOperationOutfault outFault : wsdlOperation.getOutfaults())
      {
         QName ref = outFault.getRef();

         WSDLInterfaceFault wsdlFault = wsdlInterface.getFault(ref);
         QName xmlName = wsdlFault.getElement();
         QName xmlType = wsdlFault.getXmlType();
         String javaTypeName = null;

         if (xmlType == null)
         {
            log.warn("Cannot obtain fault type for element: " + xmlName);
            xmlType = xmlName;
         }

         TypeMappingMetaData tmMetaData = typesMetaData.getTypeMappingByXMLType(xmlType);
         if (tmMetaData != null)
            javaTypeName = tmMetaData.getJavaTypeName();

         if (javaTypeName == null)
         {
            log.warn("Cannot obtain java type mapping for: " + xmlType);
            javaTypeName = new UnqualifiedFaultException(xmlType).getClass().getName();
         }

         FaultMetaData faultMetaData = new FaultMetaData(opMetaData, xmlName, xmlType, javaTypeName);
         opMetaData.addFault(faultMetaData);
      }
   }

}
