/*
 * 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.axis2.jaxws.marshaller.impl.alt;

import org.apache.axis2.jaxws.ExceptionFactory;
import org.apache.axis2.jaxws.description.AttachmentDescription;
import org.apache.axis2.jaxws.description.AttachmentType;
import org.apache.axis2.jaxws.description.EndpointDescription;
import org.apache.axis2.jaxws.description.EndpointInterfaceDescription;
import org.apache.axis2.jaxws.description.OperationDescription;
import org.apache.axis2.jaxws.description.ParameterDescription;
import org.apache.axis2.jaxws.i18n.Messages;
import org.apache.axis2.jaxws.marshaller.MethodMarshaller;
import org.apache.axis2.jaxws.message.Block;
import org.apache.axis2.jaxws.message.Message;
import org.apache.axis2.jaxws.message.OccurrenceArray;
import org.apache.axis2.jaxws.message.Protocol;
import org.apache.axis2.jaxws.message.databinding.JAXBBlockContext;
import org.apache.axis2.jaxws.message.factory.JAXBBlockFactory;
import org.apache.axis2.jaxws.message.factory.MessageFactory;
import org.apache.axis2.jaxws.registry.FactoryRegistry;
import org.apache.axis2.jaxws.runtime.description.marshal.MarshalServiceRuntimeDescription;
import org.apache.axis2.util.JavaUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import jakarta.activation.DataHandler;
import jakarta.jws.WebService;
import jakarta.jws.WebParam.Mode;
import jakarta.jws.soap.SOAPBinding.Style;
import jakarta.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
import jakarta.xml.ws.Holder;
import jakarta.xml.ws.WebServiceException;

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;

/**
 *   1) The web service is Doc/Lit Wrapped, and 
 *   2) The wrapper and fault bean objects are missing (hence the term 'Minimal')
 *   
 */
/**
 * @author scheu
 *
 */
public class DocLitWrappedMinimalMethodMarshaller implements MethodMarshaller {

    private static Log log = LogFactory.getLog(DocLitWrappedMinimalMethodMarshaller.class);

    private static JAXBBlockFactory factory =
        (JAXBBlockFactory)FactoryRegistry.getFactory(JAXBBlockFactory.class);
    
    public DocLitWrappedMinimalMethodMarshaller() {
        super();
    }

    public Message marshalRequest(Object[] signatureArguments, 
            OperationDescription operationDesc,
            Map<String, Object> requestContext)
        throws WebServiceException {

        if (log.isDebugEnabled()) {
            log.debug("enter marshalRequest operationDesc = " + operationDesc.getName());
        }
        EndpointInterfaceDescription ed = operationDesc.getEndpointInterfaceDescription();
        EndpointDescription endpointDesc = ed.getEndpointDescription();
        Protocol protocol = Protocol.getProtocolForBinding(endpointDesc.getClientBindingID());

        // Note all exceptions are caught and rethrown with a WebServiceException
        try {

            // Sample Document message
            // ..
            // <soapenv:body>
            //    <m:operation>
            //      <param>hello</param>
            //    </m:operation>
            // </soapenv:body>
            //
            // Important points.
            //   1) There is no operation element under the body.
            //   2) The data blocks are located underneath the body.  
            //   3) The name of the data block (m:operation) is defined by the schema and match the name of the operation.
            //      This is called the wrapper element.  The wrapper element has a corresponding JAXB element pojo.
            //   4) The parameters (m:param) are child elements of the wrapper element.
            //   5) NOTE: For doc/literal wrapped "minimal", the wrapper JAXB element pojo is missing.

            // Get the operation information
            ParameterDescription[] pds = operationDesc.getParameterDescriptions();
            MarshalServiceRuntimeDescription marshalDesc =
                    MethodMarshallerUtils.getMarshalDesc(endpointDesc);
            TreeSet<String> packages = marshalDesc.getPackages();

            // Create the message 
            MessageFactory mf = (MessageFactory)FactoryRegistry.getFactory(MessageFactory.class);
            Message m = mf.create(protocol);

            // Indicate the style and wrapper element name.  This triggers the message to
            // put the data blocks underneath the wrapper element
            m.setStyle(Style.DOCUMENT);
            m.setIndirection(1);
            m.setOperationElement(getRequestWrapperQName(operationDesc));

            // The input object represent the signature arguments.
            // Signature arguments are both holders and non-holders
            // Convert the signature into a list of JAXB objects for marshalling
            List<PDElement> pdeList =
                    MethodMarshallerUtils.getPDElements(marshalDesc,
                                                        pds,
                                                        signatureArguments,
                                                        true,  // input
                                                        true,  // doc/lit wrapped
                                                        true); // false

            // We want to use "by Java Type" marshalling for 
            // all objects
            for (int i=0; i<pdeList.size(); i++) {
                
                PDElement pde = pdeList.get(i);
                
                // If the actual value is an array or list
                // this should be modeled as an 
                // occurrence of elements
                pde = processOccurrence(pde);
                pdeList.set(i, pde);
                
                // Set by java type marshaling
                ParameterDescription pd = pde.getParam();
                Class type = pd.getParameterActualType();
                pde.setByJavaTypeClass(type);
            }
            
            // Put values onto the message
            MethodMarshallerUtils.toMessage(pdeList, m, packages, requestContext);
            
            // Enable SWA for nested SwaRef attachments
            if (operationDesc.hasRequestSwaRefAttachments()) {
                m.setDoingSWA(true);
            }

            if (log.isDebugEnabled()) {
                log.debug("exit marshalRequest");
            }
            return m;
        } catch (Exception e) {
            if (log.isDebugEnabled()) {
                log.debug("fail marshalRequest e=" + e);
                log.debug(" " + JavaUtils.stackToString(e));
            }
            throw ExceptionFactory.makeWebServiceException(e);
        }
        
    }

    public Object[] demarshalRequest(Message message, OperationDescription operationDesc)
            throws WebServiceException {

        if (log.isDebugEnabled()) {
            log.debug("enter demarshalRequest operationDesc = " + operationDesc.getName());
        }
        EndpointInterfaceDescription ed = operationDesc.getEndpointInterfaceDescription();
        EndpointDescription endpointDesc = ed.getEndpointDescription();

        // Note all exceptions are caught and rethrown with a WebServiceException
        try {
         // Sample Document message
            // ..
            // <soapenv:body>
            //    <m:operation>
            //      <param>hello</param>
            //    </m:operation>
            // </soapenv:body>
            //
            // Important points.
            //   1) There is no operation element under the body.
            //   2) The data blocks are located underneath the body.  
            //   3) The name of the data block (m:operation) is defined by the schema and match the name of the operation.
            //      This is called the wrapper element.  The wrapper element has a corresponding JAXB element pojo.
            //   4) The parameters (m:param) are child elements of the wrapper element.
            //   5) NOTE: For doc/literal wrapped "minimal", the wrapper JAXB element pojo is missing.
            
            ParameterDescription[] pds = operationDesc.getParameterDescriptions();
            MarshalServiceRuntimeDescription marshalDesc =
                    MethodMarshallerUtils.getMarshalDesc(endpointDesc);
            TreeSet<String> packages = marshalDesc.getPackages();

            // Indicate that the style is Document, but the blocks are underneath
            // the wrapper element
            message.setStyle(Style.DOCUMENT);
            message.setIndirection(1);

            // Create an array of indices indicating where each parameter is located in the body
            // A -1 indicates that the parameter is not in the body
            int[] firstIndex = new int[pds.length];
            int[] lastIndex = new int[pds.length];
            for (int i=0; i<firstIndex.length; i++) {
                firstIndex[i] = -1;  
                lastIndex[i] = -1;  
            }
            calculateBodyIndex(firstIndex, lastIndex, pds, message.getBodyBlockQNames());
            
            // We want to use "by Java Type" unmarshalling for 
            // all objects
            Class[] javaTypes = new Class[pds.length];
            Class[] componentJavaTypes = new Class[pds.length];
            for (int i = 0; i < pds.length; i++) {
                ParameterDescription pd = pds[i];
                javaTypes[i] = pd.getParameterActualType();
                if (javaTypes[i].isArray()) {
                    componentJavaTypes[i] = javaTypes[i].getComponentType();
                } else if (javaTypes[i].isAssignableFrom(List.class)) {
                    componentJavaTypes[i] = getComponentType(pd, operationDesc, marshalDesc);
                } else {
                    componentJavaTypes[i]= null;
                }   
            }

            // Unmarshal the ParamValues from the Message
            List<PDElement> pvList = getPDElementsForDocLitWrappedMinimal(pds,
                    message,
                    packages,
                    true, // input
                    false,
                    javaTypes,
                    componentJavaTypes,
                    firstIndex,
                    lastIndex);

            // Build the signature arguments
            Object[] sigArguments = MethodMarshallerUtils.createRequestSignatureArgs(pds, pvList);

            // Note:  The code used to check to ensure that parameters were not null.
            // The code sited 3.6.2.3 of the JAX-WS specification, but that portion of the specification
            // is for rpc/literal marshaling.  This code is for document/literal marshaling.
            // Nulls are allowed.
            
            if (log.isDebugEnabled()) {
                log.debug("exit demarshalRequest operationDesc");
            }
            return sigArguments;
        } catch (Exception e) {
            if (log.isDebugEnabled()) {
                log.debug("fail demarshalRequest e= " + e);
                log.debug(" " + JavaUtils.stackToString(e));
            }
            throw ExceptionFactory.makeWebServiceException(e);
        }
    }


    public Message marshalResponse(Object returnObject, Object[] signatureArgs,
                                   OperationDescription operationDesc, Protocol protocol)
            throws WebServiceException {


        if (log.isDebugEnabled()) {
            log.debug("enter marshalResponse operationDesc = " + operationDesc.getName());
        }
        
        EndpointInterfaceDescription ed = operationDesc.getEndpointInterfaceDescription();
        EndpointDescription endpointDesc = ed.getEndpointDescription();
        // We want to respond with the same protocol as the request,
        // It the protocol is null, then use the Protocol defined by the binding
        if (protocol == null) {
            protocol = Protocol.getProtocolForBinding(endpointDesc.getBindingType());
        }

        // Note all exceptions are caught and rethrown with a WebServiceException
        try {
            // Sample Document message
            // ..
            // <soapenv:body>
            //    <m:operationResponse ... >
            //       <param>hello</param>
            //    </m:operationResponse>
            // </soapenv:body>
            //
            // Important points.
            //   1) There is no operation element in the message
            //   2) The data blocks are located underneath the body element. 
            //   3) The name of the data block (m:operationResponse) is defined by the schema.
            //      It matches the operation name + "Response", and it has a corresponding JAXB element.
            //      This element is called the wrapper element
            //   4) The parameters are (param) are child elements of the wrapper element.
            //   5) For "minimal" the pojo bean representing the OperationResponse is missing.
            
            // Get the operation information
            ParameterDescription[] pds = operationDesc.getParameterDescriptions();
            MarshalServiceRuntimeDescription marshalDesc =
                    MethodMarshallerUtils.getMarshalDesc(endpointDesc);
            TreeSet<String> packages = marshalDesc.getPackages();

            // Create the message 
            MessageFactory mf = (MessageFactory)FactoryRegistry.getFactory(MessageFactory.class);
            Message m = mf.create(protocol);

            // Indicate the style and wrapper element name.  This triggers the message to
            // put the data blocks underneath the operation element
            m.setStyle(Style.DOCUMENT);
            m.setIndirection(1);
            QName responseOp = getResponseWrapperQName(operationDesc);
            m.setOperationElement(responseOp);

            // Put the return object onto the message
            Class returnType = operationDesc.getResultActualType();
            String returnNS = null;
            String returnLocalPart = null;
            if (operationDesc.isResultHeader()) {
                returnNS = operationDesc.getResultTargetNamespace();
                returnLocalPart = operationDesc.getResultName();
            } else {
                returnNS = operationDesc.getResultTargetNamespace();
                returnLocalPart = operationDesc.getResultPartName();
            }

            if (returnType != void.class) {
                Element returnElement = null;
                QName returnQName = new QName(returnNS, returnLocalPart);
                if (representAsOccurrence(returnObject, returnType)) {
                    if (log.isDebugEnabled()) {
                        log.debug("Return element isListOrArray");
                    }
                    OccurrenceArray occurrenceArray = new OccurrenceArray(returnObject);
                    JAXBElement jaxb = new JAXBElement(returnQName, returnType, occurrenceArray);
                    returnElement = new Element(jaxb, returnQName);
                } else if (marshalDesc.getAnnotationDesc(returnType).hasXmlRootElement()) {
                    returnElement = new Element(returnObject, returnQName);
                } else {
                    returnElement = new Element(returnObject, returnQName, returnType);
                }
                MethodMarshallerUtils.toMessage(returnElement,
                                                returnType,
                                                operationDesc.isListType(),
                                                marshalDesc,
                                                m,
                                                returnType, // force marshal by type
                                                operationDesc.isResultHeader());
            }

            // Convert the holder objects into a list of JAXB objects for marshalling
            List<PDElement> pdeList =
                    MethodMarshallerUtils.getPDElements(marshalDesc,
                                                        pds,
                                                        signatureArgs,
                                                        false,  // output
                                                        true,   // doc/lit wrapped
                                                        false); // not rpc

            // We want to use "by Java Type" marshalling for 
            // all objects
            for (int i=0; i<pdeList.size(); i++) {
                
                PDElement pde = pdeList.get(i);
                
                // If the actual value is an array or list
                // this should be modeled as an 
                // occurrence of elements
                pde = processOccurrence(pde);
                pdeList.set(i, pde);
                
                // Set by java type marshaling
                ParameterDescription pd = pde.getParam();
                Class type = pd.getParameterActualType();
                pde.setByJavaTypeClass(type);
            }

            // TODO Should we check for null output body values?  Should we check for null output header values ?
            // Put values onto the message
            MethodMarshallerUtils.toMessage(pdeList, m, packages, null);
            
            // Enable SWA for nested SwaRef attachments
            if (operationDesc.hasResponseSwaRefAttachments()) {
                m.setDoingSWA(true);
            }

            if (log.isDebugEnabled()) {
                log.debug("exit marshalResponse");
            }
            return m;
        } catch (Exception e) {
            if (log.isDebugEnabled()) {
                log.debug("fail marshalResponse e= " + e);
                log.debug(" " + JavaUtils.stackToString(e));
            }
            throw ExceptionFactory.makeWebServiceException(e);
        }
    }

    /**
     * If the PDElement represents an array or List,
     * a new PDElement is returned that models the 
     * the array or List as a series of elements.
     * @param pde
     * @return new PDElement or same PDElement
     */
    private static PDElement processOccurrence(PDElement pde) {
        // All arrays and lists should be marshaled as
        // separate (occurrence) elements
        Element element = pde.getElement();
        if (element != null) {
            Object elementValue = element.getElementValue();
            if (elementValue instanceof JAXBElement) {
                JAXBElement jaxb = (JAXBElement) elementValue;
                Object value = jaxb.getValue();
                if (representAsOccurrence(value, jaxb.getDeclaredType())) {
                    if (log.isDebugEnabled()) {
                        log.debug("Build OccurrentArray");
                    }
                    OccurrenceArray occurrenceArray = new OccurrenceArray(value);
                    JAXBElement newJAXBElement = 
                        new JAXBElement(jaxb.getName(),
                                jaxb.getDeclaredType(),
                                occurrenceArray);
                    element = new Element(newJAXBElement, jaxb.getName());
                    pde = new PDElement(pde.getParam(), element, null);
                }
            }
        }
        return pde;
    }
    
    /**
     * @param value
     * @return true if this value should be represented as a series of occurrence
     * elements
     */
    private static boolean representAsOccurrence(Object value, Class inClass) {
        // Represent as a series of occurrence elements if not List/Array
        // but not a byte[].  A byte[] has its own encoding.
        
        boolean rc = false;
        Class cls = (value == null) ? inClass : value.getClass();
  
        if (cls == null) {
            return true;
        }else if (List.class.isAssignableFrom(cls)) {
            rc = true;
        } else if (cls.equals(byte[].class)) {
            rc = false;  // assume base64binary
        } else if (cls.isArray()) {
            rc = true;
        }
        if (log.isDebugEnabled()) {
            log.debug("representAsOccurrence for " + JavaUtils.getObjectIdentity(value) + 
                        " of class: " + inClass + rc);
        }
        return rc;
    }

    public Object demarshalResponse(Message message, Object[] signatureArgs,
                                    OperationDescription operationDesc)
            throws WebServiceException {

        if (log.isDebugEnabled()) {
            log.debug("enter demarshalResponse operationDesc = " + operationDesc.getName());
        }
        EndpointInterfaceDescription ed = operationDesc.getEndpointInterfaceDescription();
        EndpointDescription endpointDesc = ed.getEndpointDescription();

        // Note all exceptions are caught and rethrown with a WebServiceException
        try {
         // Sample Document message
            // ..
            // <soapenv:body>
            //    <m:operationResponse ... >
            //       <param>hello</param>
            //    </m:operationResponse>
            // </soapenv:body>
            //
            // Important points.
            //   1) There is no operation element in the message
            //   2) The data blocks are located underneath the body element. 
            //   3) The name of the data block (m:operationResponse) is defined by the schema.
            //      It matches the operation name + "Response", and it has a corresponding JAXB element.
            //      This element is called the wrapper element
            //   4) The parameters are (param) are child elements of the wrapper element.
            //   5) For "minimal" the pojo bean representing the OperationResponse is missing
            
            ParameterDescription[] pds = operationDesc.getParameterDescriptions();
            MarshalServiceRuntimeDescription marshalDesc =
                    MethodMarshallerUtils.getMarshalDesc(endpointDesc);
            TreeSet<String> packages = marshalDesc.getPackages();

            // Indicate that the style is Document. 
            message.setStyle(Style.DOCUMENT);
            message.setIndirection(1);

            // Create an array of indices indicating where each parameter is located in the body
            // A -1 indicates that the parameter is not in the body
            int[] firstIndex = new int[pds.length];
            int[] lastIndex = new int[pds.length];
            for (int i=0; i<firstIndex.length; i++) {
                firstIndex[i] = -1;  
                lastIndex[i] = -1;  
            }
            calculateBodyIndex(firstIndex, lastIndex, pds, message.getBodyBlockQNames());
            
            int firstBodyParamIndex = -1;
            
            for (int i=0; i < pds.length; i++) {
                if (pds[i].getMode() == Mode.OUT || pds[i].getMode() == Mode.INOUT) {
                    if (firstIndex[i] >= 0 && firstBodyParamIndex == -1) {
                        firstBodyParamIndex = firstIndex[i];
                    }
                }
            }
            
            
            // Get the return value.
            Class returnType = operationDesc.getResultActualType();
            Class returnComponentType = null;
            if (returnType.isArray()) {
                returnComponentType = returnType.getComponentType();
            } else if (returnType.isAssignableFrom(List.class)) {
                returnComponentType = getComponentType(null, operationDesc, marshalDesc);
            } else {
                returnComponentType= null;
            }
            Object returnValue = null;
            boolean hasReturnInBody = false;
            if (returnType != void.class) {
                // If the webresult is in the header, we need the name of the header so that we can find it.
                Element returnElement = null;
                if (operationDesc.isResultHeader()) {
                    returnElement = getReturnElementForDocLitWrappedMinimal(packages,
                                                                           message,
                                                                           returnType,
                                                                           returnComponentType,
                                                                           operationDesc.isListType(),
                                                                           true,  // is a header
                                                                           operationDesc.getResultTargetNamespace(),
                                                                           // header ns
                                                                           operationDesc.getResultPartName(),     // header local part
                                                                           MethodMarshallerUtils.numOutputBodyParams(pds) > 0,
                                                                           firstBodyParamIndex);

                } else {
                    returnElement = getReturnElementForDocLitWrappedMinimal(packages,
                                                                           message,
                                                                           returnType,
                                                                           returnComponentType,
                                                                           operationDesc.isListType(),
                                                                           false,
                                                                           null,
                                                                           null,
                                                                           MethodMarshallerUtils.numOutputBodyParams(pds) > 0,
                                                                           firstBodyParamIndex);
                    hasReturnInBody = true;

                }
                returnValue = returnElement.getTypeValue();
            }

            // We want to use "by Java Type" unmarshalling for 
            // all objects
            Class[] javaTypes = new Class[pds.length];
            Class[] componentJavaTypes = new Class[pds.length];
            for (int i = 0; i < pds.length; i++) {
                ParameterDescription pd = pds[i];
                Class type = pd.getParameterActualType();
                
                if (type.isArray()) {
                    componentJavaTypes[i] = type.getComponentType();
                } else if (type.isAssignableFrom(List.class)) {
                    componentJavaTypes[i] = getComponentType(pd, operationDesc, marshalDesc);
                } else {
                    componentJavaTypes[i]= null;
                }
                javaTypes[i] = type;
            }

            // Unmarshall the ParamValues from the Message
            List<PDElement> pvList = getPDElementsForDocLitWrappedMinimal(pds,
                    message,
                    packages,
                    false, // output
                    hasReturnInBody,
                    javaTypes,
                    componentJavaTypes,
                    firstIndex,
                    lastIndex);


            // Populate the response Holders
            MethodMarshallerUtils.updateResponseSignatureArgs(pds, pvList, signatureArgs);

            if (log.isDebugEnabled()) {
                log.debug("exit demarshalResponse");
            }
            return returnValue;
        } catch (Exception e) {
            if (log.isDebugEnabled()) {
                log.debug("fail demarshalResponse e=" + e);
                log.debug(" " + JavaUtils.stackToString(e));
            }
            throw ExceptionFactory.makeWebServiceException(e);
        }
    }

    public Message marshalFaultResponse(Throwable throwable,
                                        OperationDescription operationDesc, Protocol protocol)
            throws WebServiceException {

        if (log.isDebugEnabled()) {
            log.debug("marshalFaultResponse operationDesc = " + operationDesc);
        }
        EndpointInterfaceDescription ed = operationDesc.getEndpointInterfaceDescription();
        EndpointDescription endpointDesc = ed.getEndpointDescription();
        MarshalServiceRuntimeDescription marshalDesc =
                MethodMarshallerUtils.getMarshalDesc(endpointDesc);
        TreeSet<String> packages = marshalDesc.getPackages();

        // We want to respond with the same protocol as the request,
        // It the protocol is null, then use the Protocol defined by the binding
        if (protocol == null) {
            protocol = Protocol.getProtocolForBinding(endpointDesc.getBindingType());
        }

        // Note all exceptions are caught and rethrown with a WebServiceException
        try {
            // Create the message 
            MessageFactory mf = (MessageFactory)FactoryRegistry.getFactory(MessageFactory.class);
            Message m = mf.create(protocol);

            // Put the fault onto the message
            MethodMarshallerUtils.marshalFaultResponse(throwable,
                                                       marshalDesc,
                                                       operationDesc,
                                                       m);
            if (log.isDebugEnabled()) {
                log.debug("exit marshalFaultResponse");
            }
            return m;
        } catch (Exception e) {
            if (log.isDebugEnabled()) {
                log.debug("fail marshalFaultResponse e=" + e);
                log.debug(" " + JavaUtils.stackToString(e));
            }
            throw ExceptionFactory.makeWebServiceException(e);
        }
    }

    public Throwable demarshalFaultResponse(Message message, OperationDescription operationDesc)
            throws WebServiceException {

        if (log.isDebugEnabled()) {
            log.debug("demarshalFaultResponse operationDesc = " + operationDesc);
        }
        EndpointInterfaceDescription ed = operationDesc.getEndpointInterfaceDescription();
        EndpointDescription endpointDesc = ed.getEndpointDescription();
        MarshalServiceRuntimeDescription marshalDesc =
                MethodMarshallerUtils.getMarshalDesc(endpointDesc);

        // Note all exceptions are caught and rethrown with a WebServiceException
        try {
            Throwable t = MethodMarshallerUtils
                    .demarshalFaultResponse(operationDesc, marshalDesc, message);
            if (log.isDebugEnabled()) {
                log.debug("exit demarshalFaultResponse");
            }
            return t;
        } catch (Exception e) {
            if (log.isDebugEnabled()) {
                log.debug("fail demarshalFaultResponse e=" +e);
                log.debug(" " + JavaUtils.stackToString(e));
            }
            throw ExceptionFactory.makeWebServiceException(e);
        }
    }

    /**
     * @param opDesc
     * @return request wrapper qname
     */
    private static QName getRequestWrapperQName(OperationDescription opDesc) {

        QName qName = opDesc.getName();

        String localPart = opDesc.getRequestWrapperLocalName();
        String uri = opDesc.getRequestWrapperTargetNamespace();
        String prefix = "dlwmin";  // Prefer using an actual prefix


        qName = new QName(uri, localPart, prefix);
        return qName;
    }

    /**
     * @param opDesc
     * @return request wrapper qname
     */
    private static QName getResponseWrapperQName(OperationDescription opDesc) {

        QName qName = opDesc.getName();

        String localPart = opDesc.getResponseWrapperLocalName();
        String uri = opDesc.getResponseWrapperTargetNamespace();
        String prefix = "dlwmin";  // Prefer using an actual prefix


        qName = new QName(uri, localPart, prefix);
        return qName;
    }
    
    /**
     * Return the list of PDElements that is unmarshalled from the wire.
     * NOTE: This method is slower as the normal getPDElements method because it 
     * must cache the message in order to do QName matches.
     * This method is only necessary to support the cases where the incoming message
     * may have missing data items.  
     * Currently this is limited to the document/literal minimal case.
     * 
     * @param params ParameterDescription for this operation
     * @param message Message
     * @param packages set of packages needed to unmarshal objects for this operation
     * @param isInput indicates if input or output  params (input on server, output on client)
     * @param hasReturnInBody if isInput=false, then this parameter indicates whether a 
     * return value is expected in the body.
     * @param javaType 
     * @param javaComponentType
     * @param firstIndex (array indicating the first block corresponding to the parameter)
     * @param lastIndex  (array indicating the last block corresponding to the parameter)
     * @see getPDElements
     * @return ParamValues
     */
    static List<PDElement> getPDElementsForDocLitWrappedMinimal(ParameterDescription[] params,
                                         Message message,
                                         TreeSet<String> packages,
                                         boolean isInput,
                                         boolean hasReturnInBody, 
                                         Class[] javaType,
                                         Class[] javaComponentType,
                                         int[] firstIndex,
                                         int[] lastIndex
                                         ) throws XMLStreamException {

        if (log.isDebugEnabled()) {
            log.debug("start getPDElementsForDocLitWrappedMinimal");
        }
        List<PDElement> pdeList = new ArrayList<PDElement>();
        
        int totalBodyBlocks = message.getNumBodyBlocks();  
        
        
        
        // TODO What if return is an swa attachment, then this should start
        // at 1 not 0.
        int swaIndex = 0;
        
        for (int i = 0; i < params.length; i++) {
            ParameterDescription pd = params[i];
            
            if (log.isDebugEnabled()) {
                log.debug("  processing Parameter " + pd);
            }
            

            if (pd.getMode() == Mode.IN && isInput ||
                    pd.getMode() == Mode.INOUT ||
                    pd.getMode() == Mode.OUT && !isInput) {

                // Don't consider async handlers, they are are not represented on the wire,
                // thus they don't have a PDElement
                // TODO
                //if (isAsyncHandler(param)) {
                //    continue;
                //}

                Block block = null;
                JAXBBlockContext context = new JAXBBlockContext(packages);

                AttachmentDescription attachmentDesc = pd.getAttachmentDescription();
                if (attachmentDesc == null) {
                    
                    boolean isBase64Binary = byte[].class.equals(javaType[i]);
                    
                    
                    // In most cases the entire java object is unmarshalled.
                    // But in some cases, the java object is a series of
                    // elements.
                    boolean unmarshalComponents = false;
                    if (pd.isListType() || 
                        javaComponentType[i] == null ||
                        isBase64Binary) {
                        context.setProcessType(javaType[i]);
                        context.setIsxmlList(pd.isListType());
                    } else {
                        context.setProcessType(javaComponentType[i]);
                        unmarshalComponents = true;
                    }
                    
                    
                    // Unmarshal the object into a JAXB object or JAXBElement
                    Element element = null;
                    if (pd.isHeader()) {
                        
                        if (log.isDebugEnabled()) {
                            log.debug("  get block from the headers");
                        }
                        // Get the Block from the header
                        // NOTE The parameter name is always used to get the header 
                        // element
                        String localName = pd.getParameterName();
                        block = message.getHeaderBlock(pd.getTargetNamespace(),
                                                       localName,
                                                       context,
                                                       factory);
                        element = new Element(block.getBusinessObject(true), 
                                block.getQName());
                    } else if (firstIndex[i] >= 0) {
                        if (log.isDebugEnabled()) {
                            log.debug("  get block from the " + firstIndex[i] +
                                    " to the " + lastIndex[i]);
                        }
                        // You must use this method if there are more than one body block
                        // This method may cause OM expansion
                        if (unmarshalComponents) {
                            Object container = makeContainer(javaType[i],
                                    javaComponentType[i],
                                    (lastIndex[i] - firstIndex[i]) + 1);

                            for (int blockI=firstIndex[i]; blockI<=lastIndex[i]; blockI++) {
                                block = message.getBodyBlock(blockI, context, factory);
                                Object value = block.getBusinessObject(true);
                                if (value instanceof JAXBElement) {
                                    value = ((JAXBElement) value).getValue();
                                }
                                if (log.isDebugEnabled()) {
                                    log.debug(" get Business Object " + JavaUtils.getObjectIdentity(value) + " from block " + blockI);

                                }

                                addComponent(container, value, blockI - firstIndex[i]);
                                
                            }
                            element = new Element(container,     
                                    block.getQName());

                        } else {
                            block = message.getBodyBlock(firstIndex[i], context, factory);
                            element = new Element(block.getBusinessObject(true), 
                                    block.getQName());
                        }
                    }  else {
                        // Missing parameter
                        if (log.isDebugEnabled()) {
                            log.debug("  there is no block for this parameter.");
                        }
                        QName qName = new QName(pd.getTargetNamespace(), pd.getPartName());
                        if (!unmarshalComponents) {
                            element = new Element(null, qName);
                        } else {
                            Object container = makeContainer(javaType[i],
                                    javaComponentType[i], 0);
                            element = new Element(container, qName);
                        }
                    }
                    
                    
                    PDElement pde =
                        new PDElement(pd, element, javaComponentType[i] == null ? null
                                : javaComponentType[i]);
                    pdeList.add(pde);
                } else {
                    // Attachment Processing
                    if (attachmentDesc.getAttachmentType() == AttachmentType.SWA) {
                        String partName = pd.getPartName();
                        String cid = null;
                        if (log.isDebugEnabled()) {
                            log.debug("Getting the attachment dataHandler for partName=" + partName);
                        }
                        if (partName != null && partName.length() > 0) {
                            // Compliant WS-I behavior
                            cid = message.getAttachmentID(partName);
                        }
                        if (cid == null) {
                            if (log.isDebugEnabled()) {
                                log.debug("Attachment dataHandler was not found.  Fallback to use attachment " + swaIndex);
                            }
                            // Toleration mode for non-compliant attachment names
                            cid = message.getAttachmentID(swaIndex);
                        }
                        DataHandler dh = message.getDataHandler(cid);
                        Attachment attachment = new Attachment(dh, cid);
                        PDElement pde = new PDElement(pd, null, null, attachment);
                        pdeList.add(pde);
                        swaIndex++;
                    } else {
                        throw ExceptionFactory.makeWebServiceException(Messages.getMessage("pdElementErr"));
                    }
                }
            }
        }

        if (log.isDebugEnabled()) {
            log.debug("end getPDElementsWithMissingElements");
        }
        return pdeList;
    }
    
    /**
     * Calculate the index of the first block for the parameter and
     * the last block (inclusive) for the parameter
     * @param firstIndex
     * @param lastIndex
     * @param params
     * @param qNames
     */
    private static void calculateBodyIndex(int[] firstIndex, int[] lastIndex, 
            ParameterDescription[] params, 
            List<QName> qNames) {
        if (log.isDebugEnabled()) {
            log.debug("Start calculateBodyIndex");
            for (int i=0; i<qNames.size(); i++) {
                log.debug("   QName " + i + " = " + qNames.get(i));
            }
        }
        for (int pi=0; pi<params.length; pi++) {
            if (pi >= 0) {
                ParameterDescription pd = params[pi];
                if (log.isDebugEnabled()) {
                    log.debug("  ParameterDescription =" + pd.toString());
                    log.debug("  original firstIndex = " + firstIndex[pi]);
                    log.debug("  original lastIndex = " + lastIndex[pi]);
                }
                firstIndex[pi] = -1;  // Reset index
                lastIndex[pi] = -1;  // Reset index
                // Found a parameter that is expected in the body
                // Calculate its index by looking for an exact qname match
                for (int qi=0; qi<qNames.size(); qi++) {
                    QName qName = qNames.get(qi);
                    if (qName.getLocalPart().equals(pd.getPartName())) {
                        if (qName.getNamespaceURI().equals(pd.getTargetNamespace())) {
                            if (firstIndex[pi] < 0) {
                                if(log.isDebugEnabled()) {
                                    log.debug("    set first index to " + qi);
                                }
                                firstIndex[pi] = qi;
                            }
                            lastIndex[pi] = qi;
                        }
                    }
                }
                // Fallback to searching for just the part name
                if (firstIndex[pi] < 0) {
                    for (int qi=0; qi<qNames.size(); qi++) {
                        QName qName = qNames.get(qi);
                        if (qName.getLocalPart().equals(pd.getPartName())) {
                            if (firstIndex[pi] < 0) {
                                if(log.isDebugEnabled()) {
                                    log.debug("    fallback: set first index to " + qi);
                                }
                                firstIndex[pi] = qi;
                            }
                            lastIndex[pi] = qi;
                        }
                    }
                }
                if (log.isDebugEnabled()) {
                    log.debug("  last index = " + lastIndex[pi]);
                }
            }
        }
        if (log.isDebugEnabled()) {
            log.debug("End calculateBodyIndex");
        }
    }

    /**
     * Unmarshal the return object from the message
     *
     * @param packages
     * @param message
     * @param javaTypeClass
     * @param javaComponentTypeClass
     * @param isHeader
     * @param headerNS                 (only needed if isHeader)
     * @param headerLocalPart          (only needed if isHeader)
     * @param hasOutputBodyParams (true if the method has out or inout params other 
     * than the return value)
     * @param ioutputBodyArgIndes (first index of output body argument or -1)
     * 
     * @return Element
     * @throws WebService
     * @throws XMLStreamException
     */
    static Element getReturnElementForDocLitWrappedMinimal(TreeSet<String> packages,
                                    Message message,
                                    Class javaTypeClass,  
                                    Class javaComponentTypeClass,  
                                    boolean isList,
                                    boolean isHeader,
                                    String headerNS,
                                    String headerLocalPart,
                                    boolean hasOutputBodyParams,
                                    int outputBodyArgIndex) 

            throws WebServiceException, XMLStreamException {

        // The return object is the first block in the body
        JAXBBlockContext context = new JAXBBlockContext(packages);
        
        // In most cases the entire java object is unmarshalled.
        // But in some cases, the java object is a series of
        // elements.
        boolean unmarshalComponents = false;
        if (isList || javaComponentTypeClass == null) {
            context.setProcessType(javaTypeClass);
            context.setIsxmlList(isList);
        } else {
            context.setProcessType(javaComponentTypeClass);
            unmarshalComponents = true;
        }
        
        Element returnElement = null;
        if (isHeader) {
            
            // In header
            Block block = message.getHeaderBlock(headerNS, headerLocalPart, context, factory);
            // Get the business object.  We want to return the object that represents the type.
            returnElement = new Element(block.getBusinessObject(true), block.getQName());
        } 
        else {
            // In Body
            
            // Determine how many return elements are present
            int numBodyBlocks = message.getNumBodyBlocks();
            if (outputBodyArgIndex >= 0) {
                numBodyBlocks = outputBodyArgIndex;
            }
            if (!unmarshalComponents) {
                if (numBodyBlocks == 1) {
                    if (log.isDebugEnabled()) {
                        log.debug("Case A: Not unmarshalling components, and only 1 block");
                    }
                    // Normal case: Only one body block
                    // We can use the get body block method
                    // that streams the whole block content.
                    Block block = message.getBodyBlock(context, factory);
                    //We look for body block only when the return type associated with operation is not void.
                    //If a null body block is returned in response on a operation that is not void, its a user error.               
                    returnElement = new Element(block.getBusinessObject(true), block.getQName());
                } else if (numBodyBlocks > 1) {
                    if (log.isDebugEnabled()) {
                        log.debug("Case B: Not unmarshalling components, and multiple blocks");
                    }
                    // There is only one return element
                    Block block = message.getBodyBlock(0, context, factory);
                    returnElement = new Element(block.getBusinessObject(true), block.getQName());

                }       
            } else {        
                if (numBodyBlocks > 0) {
                    if (log.isDebugEnabled()) {
                        log.debug("Case C: unmarshalling components.  Number of blocks=" + numBodyBlocks);
                    }
                    // Assume that all the qnames matching the first QName are for the return
                    List<QName> qNames = message.getBodyBlockQNames();
                    int firstIndex = 0;
                    int lastIndex = 0;
                    QName returnQName = qNames.get(0);
                    if (log.isDebugEnabled()) {
                        log.debug(" returnQName =" + returnQName);
                    }
                    do {
                        lastIndex++;
                    } while (lastIndex < qNames.size() &&
                            returnQName.equals(qNames.get(lastIndex)));

                    // Multiple Elements for QName
                    int numElements = lastIndex - firstIndex;
                    if (log.isDebugEnabled()) {
                        log.debug(" number of return blocks=" + numElements);
                    }
                    Object container = makeContainer(javaTypeClass, 
                            javaComponentTypeClass, 
                            numElements);
                    
                    for (int blockI=firstIndex; blockI<lastIndex; blockI++) {
                        Block block = message.getBodyBlock(blockI, context, factory);
                        Object value = block.getBusinessObject(true);
                        if (value instanceof JAXBElement) {
                            value = ((JAXBElement) value).getValue();
                        }
                        if (log.isDebugEnabled()) {
                            log.debug(" get return Business object (" + JavaUtils.getObjectIdentity(value) +
                                    ") for block " + blockI);

                        }
                        
                        addComponent(container, value, blockI);
                        
                        
                    }
                    returnElement = new Element(container, returnQName);

                } else {
                    if (log.isDebugEnabled()) {
                        log.debug("Case D: unmarshalling components, but there are no blocks");
                    }
                    Object container = makeContainer(javaTypeClass, 
                            javaComponentTypeClass, 
                            0);
                    QName qName = new QName("", "return");
                    returnElement = new Element(container, qName);
                }
            }
        
            //We look for body block only when the return type associated with operation is not void.
            //If a null body block is returned in response on a operation that has non void return type, its a user error.
            if (returnElement == null){
                if(log.isDebugEnabled()){
                    log.debug("Empty Body Block Found in response Message for wsdl Operation defintion that expects an Output");
                    log.debug("Return type associated with SEI operation is not void, Body Block cannot be null");
                }
                throw ExceptionFactory.makeWebServiceException(Messages.getMessage("MethodMarshallerUtilErr1"));    
            }
        }
        
        if (log.isDebugEnabled()) {
            log.debug("getReturnElementForDocLitWrappedMinimal " + JavaUtils.getObjectIdentity(returnElement));
            
        }
        
        return returnElement;
    }
    
    /**
     * Make a container array or List
     * @param type
     * @param componentType
     * @param numComponents
     * @return container array or list
     */
    private static Object makeContainer(Class type, 
            Class componentType, 
            int numComponents) {
        Object container = null;
        if (type.isArray()) {
            container = Array.newInstance(componentType, 
                    numComponents);
        } else {
            try {
                container = type.newInstance();
            } catch (Exception e) {
                container = new ArrayList();
            }
        }
        return container;
    }
    
    /**
     * Add component to the container object
     * @param container array or List
     * @param component
     * @param index
     * @param container
     */
    private static void addComponent(Object container, Object component, int index) {
        if (container.getClass().isArray()) {
            if (component != null) {
                Array.set(container, index, component);
            }
        } else {
            ((List) container).add(component);
        }
    }
    
    /**
     * Return ComponentType, might need to look at the GenericType
     * @param pd ParameterDesc or null if return
     * @param operationDesc OperationDescription
     * @param msrd MarshalServiceRuntimeDescription
     * @return
     */
    private static Class getComponentType(ParameterDescription pd, 
            OperationDescription operationDesc,    
            MarshalServiceRuntimeDescription msrd) {
        Class componentType = null;
        if (log.isDebugEnabled()) {
            log.debug("start getComponentType");
            log.debug(" ParameterDescription=" + pd);
        }
        
        // Determine if array, list, or other
        Class cls = null;
        if (pd == null) {
            cls = operationDesc.getResultActualType();
        } else {
            cls = pd.getParameterActualType();
        }
        
        if (cls != null) {
            if (cls.isArray()) {
                componentType = cls.getComponentType();
            } else if (List.class.isAssignableFrom(cls)) {
                if (log.isDebugEnabled()) {
                    log.debug("Parameter is a List: " + cls);
                }
                Method method = msrd.getMethod(operationDesc);
                if (log.isDebugEnabled()) {
                    log.debug("Method is: " + method);
                }
                Type genericType = null;
                if (pd == null) {
                   genericType =method.getGenericReturnType();
                } else {
                    ParameterDescription[] pds = operationDesc.getParameterDescriptions();
                    for (int i=0; i< pds.length; i++) {
                        if (pds[i] == pd) {
                            genericType = method.getGenericParameterTypes()[i];
                        }
                    }
                }
                if (log.isDebugEnabled()) {
                    log.debug("genericType is: " + genericType.getClass() + " "+ genericType);
                }
                if (genericType instanceof Class) {
                    if (log.isDebugEnabled()) {
                        log.debug(" genericType instanceof Class");
                    }
                    componentType =  String.class;
                } else if (genericType instanceof ParameterizedType) {
                    if (log.isDebugEnabled()) {
                        log.debug(" genericType instanceof ParameterizedType");
                    }
                    ParameterizedType pt = (ParameterizedType) genericType;
                    if (pt.getRawType() == Holder.class) {
                        if (log.isDebugEnabled()) {
                            log.debug(" strip off holder");
                        }
                        genericType = pt.getActualTypeArguments()[0];
                        if (genericType instanceof Class) {
                            componentType =  String.class;
                        } else if (genericType instanceof ParameterizedType) {
                            pt = (ParameterizedType) genericType;
                        }
                    }
                    if (componentType == null) {
                        Type comp = pt.getActualTypeArguments()[0];
                        if (log.isDebugEnabled()) {
                            log.debug(" comp =" + comp.getClass() + " " + comp);
                        }
                        if (comp instanceof Class) {
                            componentType = (Class) comp;
                        } else if (comp instanceof ParameterizedType) {
                            componentType = (Class) ((ParameterizedType) comp).getRawType();
                        }
                    }
                }
                    
            }
        }
        
        
        if (log.isDebugEnabled()) {
            log.debug("end getComponentType=" + componentType);
        }
        return componentType;
    }
}
