/**
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.openejb.server.axis.assembler;

import org.apache.openejb.OpenEJBException;
import org.apache.openejb.jee.ConstructorParameterOrder;
import org.apache.openejb.jee.ExceptionMapping;
import org.apache.openejb.jee.JavaWsdlMapping;
import org.apache.openejb.jee.JavaXmlTypeMapping;
import org.apache.openejb.jee.MethodParamPartsMapping;
import org.apache.openejb.jee.PackageMapping;
import org.apache.openejb.jee.ServiceEndpointMethodMapping;
import org.apache.openejb.jee.WsdlMessageMapping;
import org.apache.openejb.jee.WsdlReturnValueMapping;

import static org.apache.openejb.server.axis.assembler.JaxRpcParameterInfo.Mode;

import javax.wsdl.BindingInput;
import javax.wsdl.BindingOperation;
import javax.wsdl.Fault;
import javax.wsdl.Message;
import javax.wsdl.Operation;
import javax.wsdl.Part;
import javax.wsdl.extensions.soap.SOAPBinding;
import javax.wsdl.extensions.soap.SOAPBody;
import javax.wsdl.extensions.soap.SOAPOperation;
import javax.xml.namespace.QName;
import javax.xml.rpc.holders.BigDecimalHolder;
import javax.xml.rpc.holders.BigIntegerHolder;
import javax.xml.rpc.holders.BooleanHolder;
import javax.xml.rpc.holders.BooleanWrapperHolder;
import javax.xml.rpc.holders.ByteArrayHolder;
import javax.xml.rpc.holders.ByteHolder;
import javax.xml.rpc.holders.ByteWrapperHolder;
import javax.xml.rpc.holders.CalendarHolder;
import javax.xml.rpc.holders.DoubleHolder;
import javax.xml.rpc.holders.DoubleWrapperHolder;
import javax.xml.rpc.holders.FloatHolder;
import javax.xml.rpc.holders.FloatWrapperHolder;
import javax.xml.rpc.holders.IntHolder;
import javax.xml.rpc.holders.IntegerWrapperHolder;
import javax.xml.rpc.holders.LongHolder;
import javax.xml.rpc.holders.LongWrapperHolder;
import javax.xml.rpc.holders.ObjectHolder;
import javax.xml.rpc.holders.QNameHolder;
import javax.xml.rpc.holders.ShortHolder;
import javax.xml.rpc.holders.ShortWrapperHolder;
import javax.xml.rpc.holders.StringHolder;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class HeavyweightOperationInfoBuilder {
    private final JavaWsdlMapping mapping;
    private final ServiceEndpointMethodMapping methodMapping;

    private final XmlSchemaInfo schemaInfo;
    private final BindingStyle bindingStyle;

    //
    // Used to map exception class constructor args
    //
    private final Map<QName, String> publicTypes = new HashMap<QName, String>();
    private final Map<String, String> anonymousTypes = new HashMap<String, String>();

    //
    // Track in and out parameter names so we can verify that
    // everything has been mapped and mapped correctly
    //
    private final Set<String> inParamNames = new HashSet<String>();
    private final Set<String> outParamNames = new HashSet<String>();

    //
    // Track the wrapper elements - used by HeavyweightTypeInfoBuilder
    //
    private final Set<QName> wrapperElementQNames = new HashSet<QName>();

    private final String operationName;
    private final JaxRpcOperationInfo.OperationStyle operationStyle;
    private final Message inputMessage;
    private final Message outputMessage;
    private final Collection<Fault> faults = new ArrayList<Fault>();

    private JaxRpcOperationInfo operationInfo;

    public HeavyweightOperationInfoBuilder(BindingOperation bindingOperation, ServiceEndpointMethodMapping methodMapping, JavaWsdlMapping mapping, XmlSchemaInfo schemaInfo) throws OpenEJBException {
        Operation operation = bindingOperation.getOperation();
        this.operationName = operation.getName();
        this.operationStyle = JaxRpcOperationInfo.OperationStyle.valueOf(operation.getStyle().toString());
        this.outputMessage = operation.getOutput() == null ? null : operation.getOutput().getMessage();
        this.inputMessage = operation.getInput().getMessage();

        // faults
        for (Object o : operation.getFaults().values()) {
            faults.add((Fault) o);
        }

        this.mapping = mapping;
        this.methodMapping = methodMapping;
        this.schemaInfo = schemaInfo;

        // index types - used to process build exception class constructor args
        for (JavaXmlTypeMapping javaXmlTypeMapping : mapping.getJavaXmlTypeMapping()) {
            String javaClassName = javaXmlTypeMapping.getJavaType();
            if (javaXmlTypeMapping.getAnonymousTypeQname() != null) {
                String anonymousTypeQName = javaXmlTypeMapping.getAnonymousTypeQname();
                anonymousTypes.put(anonymousTypeQName, javaClassName);
            } else if (javaXmlTypeMapping.getRootTypeQname() != null) {
                QName qname = javaXmlTypeMapping.getRootTypeQname();
                publicTypes.put(qname, javaClassName);
            }
        }

        // BindingStyle
        if (methodMapping.getWrappedElement() != null) {
            bindingStyle = BindingStyle.DOCUMENT_LITERAL_WRAPPED;
        } else {
            BindingInput bindingInput = bindingOperation.getBindingInput();

            SOAPOperation soapOperation = JaxRpcServiceInfoBuilder.getExtensibilityElement(SOAPOperation.class, bindingOperation.getExtensibilityElements());
            String styleString = soapOperation.getStyle();
            if (styleString == null) {
                SOAPBinding soapBinding = JaxRpcServiceInfoBuilder.getExtensibilityElement(SOAPBinding.class, bindingInput.getExtensibilityElements());
                styleString = soapBinding.getStyle();
            }

            SOAPBody soapBody = JaxRpcServiceInfoBuilder.getExtensibilityElement(SOAPBody.class, bindingInput.getExtensibilityElements());
            String useString = soapBody.getUse();

            bindingStyle = BindingStyle.getBindingStyle(styleString, useString);
        }
    }

    public Set<QName> getWrapperElementQNames() throws OpenEJBException {
        buildOperationInfo();
        return Collections.unmodifiableSet(wrapperElementQNames);
    }

    public JaxRpcOperationInfo buildOperationInfo() throws OpenEJBException {
        if (operationInfo != null) {
            return operationInfo;
        }

        operationInfo = new JaxRpcOperationInfo();
        operationInfo.name = operationName;

        // Binding style rpc/encoded, doc/lit, wrapped, etc.
        operationInfo.bindingStyle = bindingStyle;

        // Operation style one way, request response, etc/
        operationInfo.operationStyle = operationStyle;

        // Java method name
        operationInfo.javaMethodName = methodMapping.getJavaMethodName();

        //
        // Map the parameters
        //
        mapParameters();

        //
        // Map return
        //
        if (methodMapping.getWsdlReturnValueMapping() != null) {
            mapReturnType();
        }

        // Validate output mapping is complete
        if (outputMessage != null && bindingStyle.isWrapped()) {
            Part inputPart = getWrappedPart(outputMessage);

            QName wrapperName = inputPart.getElementName();
            XmlElementInfo wraperElement = schemaInfo.elements.get(wrapperName);
            XmlTypeInfo wrapperType = schemaInfo.types.get(wraperElement.xmlType);

            Set<String> expectedOutParams = new HashSet<String>();
            for (XmlElementInfo expectedOutParam : wrapperType.elements.values()) {
                expectedOutParams.add(expectedOutParam.qname.getLocalPart());
            }
            if (!outParamNames.equals(expectedOutParams)) {
                throw new OpenEJBException("Not all wrapper children were mapped to parameters or a return value for operation " + operationName);
            }
        } else if (null != outputMessage) {
            if (!outParamNames.equals(outputMessage.getParts().keySet())) {
                throw new OpenEJBException("Not all output message parts were mapped to parameters or a return value for operation " + operationName);
            }
        }

        //
        // Map faults (exception)
        //
        for (Fault fault : faults) {
            JaxRpcFaultInfo faultInfo = mapFaults(fault);
            operationInfo.faults.add(faultInfo);
        }

        return operationInfo;
    }

    private JaxRpcParameterInfo[] mapParameters() throws OpenEJBException {
        List<MethodParamPartsMapping> paramMappings = methodMapping.getMethodParamPartsMapping();

        //
        // Map the ParameterDesc instance in an array so they can be ordered properly
        // before they are added to the the OperationDesc.
        //
        JaxRpcParameterInfo[] parameterInfos = new JaxRpcParameterInfo[paramMappings.size()];
        for (MethodParamPartsMapping paramMapping : paramMappings) {
            JaxRpcParameterInfo parameterInfo = mapParameter(paramMapping);
            parameterInfos[paramMapping.getParamPosition().intValue()] = parameterInfo;
        }

        //
        // verify that all parameters were mapped and we don't have nulls in the parameter array
        //
        for (int i = 0; i < parameterInfos.length; i++) {
            if (parameterInfos[i] == null) {
                throw new OpenEJBException("There is no mapping for parameter number " + i + " for operation " + operationName);
            }
        }

        //
        // Verify that all parameter names were mapped
        //
        if (bindingStyle.isWrapped()) {
            // verify that all child elements have a parameter mapping
            Part inputPart = getWrappedPart(inputMessage);

            QName wrapperName = inputPart.getElementName();
            XmlElementInfo wrapperElement = schemaInfo.elements.get(wrapperName);
            XmlTypeInfo wrapperType = schemaInfo.types.get(wrapperElement.xmlType);

            Set<String> expectedInParams = new HashSet<String>();
            for (XmlElementInfo expectedInParam : wrapperType.elements.values()) {
                expectedInParams.add(expectedInParam.qname.getLocalPart());
            }
            if (!inParamNames.equals(expectedInParams)) {
                throw new OpenEJBException("Not all wrapper children were mapped for operation name" + operationName);
            }
        } else {
            // verify all input message parts are mapped
            if (!inParamNames.equals(inputMessage.getParts().keySet())) {
                throw new OpenEJBException("Not all input message parts were mapped for operation name" + operationName);
            }
        }

        return parameterInfos;
    }

    private JaxRpcParameterInfo mapParameter(MethodParamPartsMapping paramMapping) throws OpenEJBException {
        WsdlMessageMapping wsdlMessageMappingType = paramMapping.getWsdlMessageMapping();
        QName wsdlMessageQName = wsdlMessageMappingType.getWsdlMessage();
        String wsdlMessagePartName = wsdlMessageMappingType.getWsdlMessagePartName();

        Mode mode = Mode.valueOf(wsdlMessageMappingType.getParameterMode());
        if ((mode == Mode.OUT || mode == Mode.INOUT) && outputMessage == null) {
            throw new OpenEJBException("Mapping for output parameter " + wsdlMessagePartName + " found, but no output message for operation " + operationName);
        }

        //
        // Determine the param qname and xml schema type
        //
        QName paramQName;
        QName paramXmlType;
        if (mode == Mode.IN || mode == Mode.INOUT) {
            //
            // IN or INOUT Parameter
            //
            if (!wsdlMessageQName.equals(inputMessage.getQName())) {
                throw new OpenEJBException("QName of input message: " + inputMessage.getQName() +
                    " does not match mapping message QName: " + wsdlMessageQName + " for operation " + operationName);
            }

            Part part = null;
            XmlElementInfo inParameter = null;
            if (bindingStyle.isWrapped()) {
                Part inPart = getWrappedPart(inputMessage);

                // operation name == wraper element name
                QName name = inPart.getElementName();
                if (!name.getLocalPart().equals(operationName)) {
                    throw new OpenEJBException("message " + inputMessage.getQName() + " refers to a global element named " +
                        name.getLocalPart() + ", which is not equal to the operation name " + operationName);
                }
                inParameter = getWrapperChild(inPart, wsdlMessagePartName);

                paramQName = new QName("", inParameter.qname.getLocalPart());
                paramXmlType = inParameter.xmlType;
            } else if (bindingStyle.isRpc()) {
                part = inputMessage.getPart(wsdlMessagePartName);
                if (part == null) {
                    throw new OpenEJBException("No part for wsdlMessagePartName " + wsdlMessagePartName + " in input message for operation " + operationName);
                }

                paramQName = new QName("", part.getName());

                // RPC can only use type
                paramXmlType = part.getTypeName();
            } else {
                part = inputMessage.getPart(wsdlMessagePartName);
                if (part == null) {
                    throw new OpenEJBException("No part for wsdlMessagePartName " + wsdlMessagePartName + " in input message for operation " + operationName);
                }
                // Document should use element, but type is allowed
                paramQName = getPartName(part);
                paramXmlType = paramQName;
            }
            inParamNames.add(wsdlMessagePartName);

            //
            // Verify INOUT parameter output message is consistent with input message
            //
            if (mode == Mode.INOUT) {
                if (bindingStyle.isWrapped()) {
                    // Verify output message supports this inout parameter
                    Part outPart = getWrappedPart(outputMessage);
                    XmlElementInfo outParameter = getWrapperChild(outPart, wsdlMessagePartName);
                    if (inParameter.xmlType != outParameter.xmlType) {
                        throw new OpenEJBException("The wrapper children " + wsdlMessagePartName + " do not have the same type for operation " + operationName);
                    }
                } else if (bindingStyle.isRpc()) {
                    // Verify output message supports this inout parameter
                    Part outPart = outputMessage.getPart(wsdlMessagePartName);
                    if (outPart == null) {
                        throw new OpenEJBException("No part for wsdlMessagePartName " + wsdlMessagePartName + " in output message for INOUT parameter of operation " + operationName);
                    }
                    // TODO this cannot happen.
                    if (!part.getName().equals(outPart.getName())) {
                        throw new OpenEJBException("Mismatched input part name: " + part.getName() + " and output part name: " + outPart.getName() + " for INOUT parameter for wsdlMessagePartName " + wsdlMessagePartName + " for operation " + operationName);
                    }
                    if (!(part.getElementName() == null ? outPart.getElementName() == null : part.getElementName().equals(outPart.getElementName()))) {
                        throw new OpenEJBException("Mismatched input part element name: " + part.getElementName() + " and output part element name: " + outPart.getElementName() + " for INOUT parameter for wsdlMessagePartName " + wsdlMessagePartName + " for operation " + operationName);
                    }
                    if (!(part.getTypeName() == null ? outPart.getTypeName() == null : part.getTypeName().equals(outPart.getTypeName()))) {
                        throw new OpenEJBException("Mismatched input part type name: " + part.getTypeName() + " and output part type name: " + outPart.getTypeName() + " for INOUT parameter for wsdlMessagePartName " + wsdlMessagePartName + " for operation " + operationName);
                    }
                } else {
                    part = outputMessage.getPart(wsdlMessagePartName);
                    if (part == null) {
                        throw new OpenEJBException("No part for wsdlMessagePartName " + wsdlMessagePartName + " in output message for operation " + operationName);
                    }
                    // todo this seem strange... shouldn't the name and type be the same as the in binding above
                    paramQName = getPartName(part);
                    paramXmlType = paramQName;
                }
                outParamNames.add(wsdlMessagePartName);
            }
        } else {
            //
            // OUT only Parameter
            //
            if (!wsdlMessageQName.equals(outputMessage.getQName())) {
                throw new OpenEJBException("QName of output message: " + outputMessage.getQName() +
                    " does not match mapping message QName: " + wsdlMessageQName + " for operation " + operationName);
            }

            if (bindingStyle.isWrapped()) {
                Part outPart = getWrappedPart(outputMessage);
                XmlElementInfo outParameter = getWrapperChild(outPart, wsdlMessagePartName);

                paramQName = new QName("", outParameter.qname.getLocalPart());
                paramXmlType = outParameter.xmlType;
            } else if (bindingStyle.isRpc()) {
                Part part = outputMessage.getPart(wsdlMessagePartName);
                if (part == null) {
                    throw new OpenEJBException("No part for wsdlMessagePartName " + wsdlMessagePartName + " in output message for operation " + operationName);
                }

                paramQName = new QName("", part.getName());

                // RPC can only use type
                paramXmlType = part.getTypeName();
            } else {
                Part part = outputMessage.getPart(wsdlMessagePartName);
                if (part == null) {
                    throw new OpenEJBException("No part for wsdlMessagePartName " + wsdlMessagePartName + " in output message for operation " + operationName);
                }

                paramQName = getPartName(part);
                paramXmlType = paramQName;
            }
            outParamNames.add(wsdlMessagePartName);
        }

        //
        // Determine the param java type
        //
        String paramJavaType;
        if (mode == Mode.IN) {
            // IN only prarmeters don't have holders
            paramJavaType = paramMapping.getParamType();
        } else if (rpcHolderClasses.containsKey(paramMapping.getParamType())) {
            // This is a standard type with a built in holder class
            paramJavaType = rpcHolderClasses.get(paramMapping.getParamType());
        } else {
            // holderClass == ${packageName}.holders.${typeName}Holder
            String packageName;
            String typeName;

            PackageMapping packageMapping = mapping.getPackageMappingMap().get(paramXmlType.getNamespaceURI());
            if (packageMapping != null) {
                packageName = packageMapping.getPackageType();

                // Type name is typeQName local part, but make sure it is capitalized correctly
                typeName = paramXmlType.getLocalPart();
                typeName = Character.toUpperCase(typeName.charAt(0)) + typeName.substring(1);
            } else {
                // a.b.foo.Bar >>> a.b.foo.holders.BarHolder
                String paramJavaTypeName = paramMapping.getParamType();
                int lastDot = paramJavaTypeName.lastIndexOf(".");
                packageName = paramJavaTypeName.substring(0, lastDot);
                typeName = paramJavaTypeName.substring(lastDot + 1);
            }

            paramJavaType = packageName + ".holders." + typeName + "Holder";
        }

        //
        // Build JaxRpcParameterInfo
        //
        JaxRpcParameterInfo parameterInfo = new JaxRpcParameterInfo();
        parameterInfo.qname = paramQName;
        parameterInfo.xmlType = paramXmlType;
        parameterInfo.javaType = paramJavaType;
        parameterInfo.mode = Mode.valueOf(wsdlMessageMappingType.getParameterMode());
        parameterInfo.soapHeader = wsdlMessageMappingType.getSoapHeader() != null;

        return parameterInfo;
    }

    private void mapReturnType() throws OpenEJBException {
        if (outputMessage == null) {
            throw new OpenEJBException("No output message, but a mapping for it for operation " + operationName);
        }

        // verify mapped return value qname matches expected output message name
        WsdlReturnValueMapping wsdlReturnValueMapping = methodMapping.getWsdlReturnValueMapping();
        if (!wsdlReturnValueMapping.getWsdlMessage().equals(outputMessage.getQName())) {
            throw new OpenEJBException("OutputMessage has QName: " + outputMessage.getQName() + " but mapping specifies: " + wsdlReturnValueMapping.getWsdlMessage() + " for operation " + operationName);
        }

        //
        // Determind return type qname and xml schema type
        //
        QName returnQName = null;
        QName returnXmlType = null;
        if (wsdlReturnValueMapping.getWsdlMessagePartName() != null) {
            String wsdlMessagePartName = wsdlReturnValueMapping.getWsdlMessagePartName();
            if (outParamNames.contains(wsdlMessagePartName)) {
                throw new OpenEJBException("output message part " + wsdlMessagePartName + " has both an INOUT or OUT mapping and a return value mapping for operation " + operationName);
            }

            if (bindingStyle.isWrapped()) {
                Part outPart = getWrappedPart(outputMessage);
                XmlElementInfo returnParticle = getWrapperChild(outPart, wsdlMessagePartName);

                returnQName = new QName("", returnParticle.qname.getLocalPart());
                returnXmlType = returnParticle.xmlType;
            } else if (bindingStyle.isRpc()) {
                Part part = outputMessage.getPart(wsdlMessagePartName);
                if (part == null) {
                    throw new OpenEJBException("No part for wsdlMessagePartName " + wsdlMessagePartName + " in output message for operation " + operationName);
                }

                returnQName = new QName("", part.getName());

                // RPC can only use type
                returnXmlType = part.getTypeName();
            } else {
                Part part = outputMessage.getPart(wsdlMessagePartName);
                if (part == null) {
                    throw new OpenEJBException("No part for wsdlMessagePartName " + wsdlMessagePartName + " in output message for operation " + operationName);
                }

                returnQName = getPartName(part);
                returnXmlType = returnQName;
            }

            outParamNames.add(wsdlMessagePartName);
        } else {
            // what does this mean????
        }

        operationInfo.returnQName = returnQName;
        operationInfo.returnXmlType = returnXmlType;
        operationInfo.returnJavaType = wsdlReturnValueMapping.getMethodReturnValue();
    }

    private JaxRpcFaultInfo mapFaults(Fault fault) throws OpenEJBException {
        Message message = fault.getMessage();
        ExceptionMapping exceptionMapping = mapping.getExceptionMappingMap().get(message.getQName());
        if (exceptionMapping == null) {
            throw new OpenEJBException("No exception mapping for fault " + fault.getName() + " and fault message " + message.getQName() + " for operation " + operationName);
        }

        // TODO investigate whether there are other cases in which the namespace of faultQName can be determined.
        // this is weird, but I can't figure out what it should be.
        // if part has an element rather than a type, it should be part.getElementName() (see below)
        Part part;
        if (exceptionMapping.getWsdlMessagePartName() != null) {
            // According to schema documentation, this will only be set when several headerfaults use the same message.
            String headerFaultMessagePartName = exceptionMapping.getWsdlMessagePartName();
            part = message.getPart(headerFaultMessagePartName);
        } else {
            part = (Part) message.getOrderedParts(null).iterator().next();
        }

        // Determine the fault qname and xml schema type
        QName faultQName;
        XmlTypeInfo faultTypeInfo;
        if (part.getElementName() != null) {
            XmlElementInfo elementInfo = schemaInfo.elements.get(part.getElementName());
            if (elementInfo == null) {
                throw new OpenEJBException("Can not find element: " + part.getElementName() + ", known elements: " + schemaInfo.elements.keySet());
            }
            faultTypeInfo = schemaInfo.types.get(elementInfo.xmlType);
            if (faultTypeInfo == null) {
                throw new OpenEJBException("Can not find type " + elementInfo.xmlType + " for element " + elementInfo.qname + ", known types: " + schemaInfo.types.keySet());
            }

            faultQName = part.getElementName();
        } else if (part.getTypeName() != null) {
            faultTypeInfo = schemaInfo.types.get(part.getTypeName());
            if (faultTypeInfo == null) {
                throw new OpenEJBException("Can not find type: " + part.getTypeName() + ", known elements: " + schemaInfo.types.keySet());
            }

            faultQName = new QName("", fault.getName());
        } else {
            throw new OpenEJBException("Neither type nor element name supplied for part: " + part);
        }

        //
        // Build the fault info
        //
        JaxRpcFaultInfo faultInfo = new JaxRpcFaultInfo();
        faultInfo.qname = faultQName;
        faultInfo.xmlType = faultTypeInfo.qname;
        faultInfo.javaType = exceptionMapping.getExceptionType();
        faultInfo.complex = faultTypeInfo.simpleBaseType == null;

        //
        // Map exception class constructor args
        //
        if (exceptionMapping.getConstructorParameterOrder() != null) {
            if (faultTypeInfo.simpleBaseType != null) {
                throw new OpenEJBException("ConstructorParameterOrder can only be set for complex types, not " + faultTypeInfo.qname);
            }

            Map<String, XmlElementInfo> elements = new HashMap<String, XmlElementInfo>();
            for (XmlElementInfo element : faultTypeInfo.elements.values()) {
                elements.put(element.qname.getLocalPart(), element);
            }

            ConstructorParameterOrder constructorParameterOrder = exceptionMapping.getConstructorParameterOrder();
            for (int i = 0; i < constructorParameterOrder.getElementName().size(); i++) {
                String paramName = constructorParameterOrder.getElementName().get(i);

                // get the parameter element
                XmlElementInfo paramElementInfo = elements.get(paramName);
                if (paramElementInfo == null) {
                    throw new OpenEJBException("Can not find element " + paramName + " in fault type " + faultTypeInfo.qname + ", known elements: " + elements.keySet());
                }

                // Java Type
                String paramJavaType = null;
                XmlTypeInfo paramTypeInfo = schemaInfo.types.get(paramElementInfo.xmlType);
                if (paramTypeInfo != null) {
                    if (paramTypeInfo.anonymous) {
                        paramJavaType = anonymousTypes.get(paramTypeInfo.qname.getLocalPart());
                    } else {
                        paramJavaType = publicTypes.get(paramTypeInfo.qname);
                    }
                }
                // if we don't have a java type yet, check the simple types
                if (paramJavaType == null) {
                    paramJavaType = qnameToJavaType.get(paramElementInfo.xmlType);
                }
                if (paramJavaType == null) {
                    throw new OpenEJBException("No class mapped for element type: " + paramElementInfo.xmlType);
                }

                JaxRpcParameterInfo parameterInfo = new JaxRpcParameterInfo();
                parameterInfo.qname = paramElementInfo.qname;
                parameterInfo.mode = Mode.OUT;
                // todo could be a soap header
                parameterInfo.soapHeader = false;
                parameterInfo.xmlType = paramElementInfo.xmlType;
                parameterInfo.javaType = paramJavaType;

                faultInfo.parameters.add(parameterInfo);
            }
        }
        return faultInfo;
    }


    private QName getPartName(Part part) {
        return part.getElementName() == null ? part.getTypeName() : part.getElementName();
    }

    private Part getWrappedPart(Message message) throws OpenEJBException {
        // a wrapped element can only have one part
        Collection parts = message.getParts().values();
        if (parts.size() != 1) {
            throw new OpenEJBException("message " + message.getQName() + " has " + parts.size() +
                " parts and should only have one as wrapper style mapping is specified for operation " +
                operationName);
        }
        return (Part) parts.iterator().next();
    }

    private XmlElementInfo getWrapperChild(Part part, String wsdlMessagePartName) throws OpenEJBException {
        // get the part name
        QName elementName = part.getElementName();
        wrapperElementQNames.add(elementName);

        // get the wrapper element
        XmlElementInfo wrapperElement = schemaInfo.elements.get(elementName);
        if (wrapperElement == null) {
            throw new OpenEJBException("No global element named " + elementName + " for operation " + operationName);
        }

        // get the wrapper type
        XmlTypeInfo wrapperType = schemaInfo.types.get(wrapperElement.xmlType);
        if (wrapperType == null) {
            throw new OpenEJBException("Can not find type " + wrapperElement.xmlType + " for element " + wrapperElement.qname + ", known types: " + schemaInfo.types.keySet());
        }

        // get the part type
        for (XmlElementInfo wrapperChild : wrapperType.elements.values()) {
            if (wrapperChild.qname.getLocalPart().equals(wsdlMessagePartName)) {
                return wrapperChild;
            }
        }
        throw new OpenEJBException("Global element named " + elementName + " does not define a child element named " + wsdlMessagePartName + " required by the operation " + operationName);
    }

    //see jaxrpc 1.1 4.2.1
    private static final Map<QName, String> qnameToJavaType = new HashMap<QName, String>();

    static {
        qnameToJavaType.put(new QName("http://www.w3.org/2001/XMLSchema", "string"), String.class.getName());
        qnameToJavaType.put(new QName("http://www.w3.org/2001/XMLSchema", "integer"), BigInteger.class.getName());
        qnameToJavaType.put(new QName("http://www.w3.org/2001/XMLSchema", "int"), int.class.getName());
        qnameToJavaType.put(new QName("http://www.w3.org/2001/XMLSchema", "long"), long.class.getName());
        qnameToJavaType.put(new QName("http://www.w3.org/2001/XMLSchema", "short"), short.class.getName());
        qnameToJavaType.put(new QName("http://www.w3.org/2001/XMLSchema", "decimal"), BigDecimal.class.getName());
        qnameToJavaType.put(new QName("http://www.w3.org/2001/XMLSchema", "float"), float.class.getName());
        qnameToJavaType.put(new QName("http://www.w3.org/2001/XMLSchema", "double"), double.class.getName());
        qnameToJavaType.put(new QName("http://www.w3.org/2001/XMLSchema", "boolean"), boolean.class.getName());
        qnameToJavaType.put(new QName("http://www.w3.org/2001/XMLSchema", "byte"), byte.class.getName());
        qnameToJavaType.put(new QName("http://www.w3.org/2001/XMLSchema", "unsignedInt"), long.class.getName());
        qnameToJavaType.put(new QName("http://www.w3.org/2001/XMLSchema", "unsignedShort"), int.class.getName());
        qnameToJavaType.put(new QName("http://www.w3.org/2001/XMLSchema", "unsignedByte"), short.class.getName());
        qnameToJavaType.put(new QName("http://www.w3.org/2001/XMLSchema", "QName"), QName.class.getName());
        qnameToJavaType.put(new QName("http://www.w3.org/2001/XMLSchema", "dateTime"), Calendar.class.getName());
        qnameToJavaType.put(new QName("http://www.w3.org/2001/XMLSchema", "date"), Calendar.class.getName());
        qnameToJavaType.put(new QName("http://www.w3.org/2001/XMLSchema", "time"), Calendar.class.getName());
        qnameToJavaType.put(new QName("http://www.w3.org/2001/XMLSchema", "anyURI"), URI.class.getName());
        qnameToJavaType.put(new QName("http://www.w3.org/2001/XMLSchema", "base64Binary"), byte[].class.getName());
        qnameToJavaType.put(new QName("http://www.w3.org/2001/XMLSchema", "hexBinary"), byte[].class.getName());
        qnameToJavaType.put(new QName("http://www.w3.org/2001/XMLSchema", "anySimpleType"), String.class.getName());
    }


    /**
     * Supporting the Document/Literal Wrapped pattern
     * <p/>
     * See http://www-106.ibm.com/developerworks/webservices/library/ws-whichwsdl/ for a nice explanation and example
     * <p/>
     * wrapped-element tag is used
     * WSDL message with a single part
     * part uses the 'element' attribute to point to an elemement in the types section
     * the element type and the element's name match the operation name
     */

    // standard holder classes by type
    private static final Map<String, String> rpcHolderClasses = new HashMap<String, String>();

    static {
        rpcHolderClasses.put(BigDecimal.class.getName(), BigDecimalHolder.class.getName());
        rpcHolderClasses.put(BigInteger.class.getName(), BigIntegerHolder.class.getName());
        rpcHolderClasses.put(boolean.class.getName(), BooleanHolder.class.getName());
        rpcHolderClasses.put(Boolean.class.getName(), BooleanWrapperHolder.class.getName());
        rpcHolderClasses.put(byte[].class.getName(), ByteArrayHolder.class.getName());
        rpcHolderClasses.put(byte.class.getName(), ByteHolder.class.getName());
        rpcHolderClasses.put(Byte.class.getName(), ByteWrapperHolder.class.getName());
        rpcHolderClasses.put(Calendar.class.getName(), CalendarHolder.class.getName());
        rpcHolderClasses.put(double.class.getName(), DoubleHolder.class.getName());
        rpcHolderClasses.put(Double.class.getName(), DoubleWrapperHolder.class.getName());
        rpcHolderClasses.put(float.class.getName(), FloatHolder.class.getName());
        rpcHolderClasses.put(Float.class.getName(), FloatWrapperHolder.class.getName());
        rpcHolderClasses.put(int.class.getName(), IntHolder.class.getName());
        rpcHolderClasses.put(Integer.class.getName(), IntegerWrapperHolder.class.getName());
        rpcHolderClasses.put(long.class.getName(), LongHolder.class.getName());
        rpcHolderClasses.put(Long.class.getName(), LongWrapperHolder.class.getName());
        rpcHolderClasses.put(Object.class.getName(), ObjectHolder.class.getName());
        rpcHolderClasses.put(QName.class.getName(), QNameHolder.class.getName());
        rpcHolderClasses.put(short.class.getName(), ShortHolder.class.getName());
        rpcHolderClasses.put(Short.class.getName(), ShortWrapperHolder.class.getName());
        rpcHolderClasses.put(String.class.getName(), StringHolder.class.getName());
    }
}