/*
 * 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.description.impl;

import org.apache.axis2.AxisFault;
import org.apache.axis2.addressing.wsdl.WSDL11ActionHelper;
import org.apache.axis2.description.AxisMessage;
import org.apache.axis2.description.AxisOperation;
import org.apache.axis2.description.AxisOperationFactory;
import org.apache.axis2.description.AxisService;
import org.apache.axis2.description.Parameter;
import org.apache.axis2.description.WSDL2Constants;
import org.apache.axis2.java.security.AccessController;
import org.apache.axis2.jaxws.ExceptionFactory;
import org.apache.axis2.jaxws.description.AttachmentDescription;
import org.apache.axis2.jaxws.description.EndpointDescriptionJava;
import org.apache.axis2.jaxws.description.EndpointInterfaceDescription;
import org.apache.axis2.jaxws.description.FaultDescription;
import org.apache.axis2.jaxws.description.OperationDescription;
import org.apache.axis2.jaxws.description.OperationDescriptionJava;
import org.apache.axis2.jaxws.description.OperationDescriptionWSDL;
import org.apache.axis2.jaxws.description.OperationRuntimeDescription;
import org.apache.axis2.jaxws.description.ParameterDescription;
import org.apache.axis2.jaxws.description.ParameterDescriptionJava;
import org.apache.axis2.jaxws.description.builder.DescriptionBuilderComposite;
import org.apache.axis2.jaxws.description.builder.FaultActionAnnot;
import org.apache.axis2.jaxws.description.builder.MethodDescriptionComposite;
import org.apache.axis2.jaxws.description.builder.OneWayAnnot;
import org.apache.axis2.jaxws.description.builder.ParameterDescriptionComposite;
import org.apache.axis2.jaxws.description.builder.converter.ConverterUtils;
import org.apache.axis2.jaxws.i18n.Messages;
import org.apache.axis2.jaxws.util.WSDL4JWrapper;
import org.apache.axis2.wsdl.WSDLConstants;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import jakarta.jws.Oneway;
import jakarta.jws.WebMethod;
import jakarta.jws.WebParam;
import jakarta.jws.WebParam.Mode;
import jakarta.jws.WebResult;
import jakarta.jws.soap.SOAPBinding;
import javax.wsdl.Binding;
import javax.wsdl.BindingInput;
import javax.wsdl.BindingOperation;
import javax.wsdl.BindingOutput;
import javax.wsdl.Definition;
import javax.wsdl.extensions.AttributeExtensible;
import javax.xml.namespace.QName;
import jakarta.xml.ws.Action;
import jakarta.xml.ws.AsyncHandler;
import jakarta.xml.ws.FaultAction;
import jakarta.xml.ws.RequestWrapper;
import jakarta.xml.ws.Response;
import jakarta.xml.ws.ResponseWrapper;
import jakarta.xml.ws.WebFault;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URL;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.Future;

/** @see ../OperationDescription */
// TODO: Axis2 does not support overloaded operations, although EndpointInterfaceDescription.addOperation() does support overloading
//       of methods represented by OperationDescription classes.  However, the AxisOperation contained in an OperationDescription
//       does NOT support overloaded methods.
//
//       While overloading is not supported by WS-I, it IS supported by JAX-WS (p11).
//       Note that this requires support in Axis2; currently WSDL11ToAxisServiceBuilder.populateOperations does not
//       support overloaded methods in the WSDL; the operations are stored on AxisService as children in a HashMap with the wsdl
//       operation name as the key.

class OperationDescriptionImpl
        implements OperationDescription, OperationDescriptionJava, OperationDescriptionWSDL {
    private final EndpointInterfaceDescription parentEndpointInterfaceDescription;
    private final AxisOperation axisOperation;
    private final QName operationQName;
    private Method seiMethod;
    private final MethodDescriptionComposite methodComposite;
    private ParameterDescription[] parameterDescriptions;
    private FaultDescription[] faultDescriptions;
    private static final Log log = LogFactory.getLog(OperationDescriptionImpl.class);
    // ===========================================
    // ANNOTATION related information
    // ===========================================

    // ANNOTATION: @Oneway
    private Oneway onewayAnnotation;
    private Boolean onewayIsOneway;

    // ANNOTATION: @XmlList
    private boolean isListType = false;

    // ANNOTATION: @RequestWrapper
    private RequestWrapper requestWrapperAnnotation;
    private String requestWrapperTargetNamespace;
    private String requestWrapperLocalName;
    private String requestWrapperClassName;
    private String requestWrapperPartName;
    // ANNOTATION: @ResponseWrapper
    private ResponseWrapper responseWrapperAnnotation;
    private String responseWrapperLocalName;
    private String responseWrapperTargetNamespace;
    private String responseWrapperClassName;
    private String responseWrapperPartName;
    // ANNOTATION: @Action
    private Action actionAnnotation;

    // ANNOTATION: @SOAPBinding
    // Note this is the Method-level annotation.  See EndpointInterfaceDescription for the Type-level annotation
    // Also note this annotation is only allowed on methods if SOAPBinding.Style is DOCUMENT and if the method-level
    // annotation is absent, the behavior defined on the Type is used.
    // per JSR-181 MR Sec 4.7 "Annotation: jakarta.jws.soap.SOAPBinding" pg 28
    private SOAPBinding soapBindingAnnotation;
    private jakarta.jws.soap.SOAPBinding.Style soapBindingStyle;
    public static final jakarta.jws.soap.SOAPBinding.Style SoapBinding_Style_VALID =
            jakarta.jws.soap.SOAPBinding.Style.DOCUMENT;
    private jakarta.jws.soap.SOAPBinding.Use soapBindingUse;
    // Default value per JSR-181 MR Sec 4.7 "Annotation: jakarta.jws.soap.SOAPBinding" pg 28
    public static final jakarta.jws.soap.SOAPBinding.Use SOAPBinding_Use_DEFAULT =
            jakarta.jws.soap.SOAPBinding.Use.LITERAL;
    private jakarta.jws.soap.SOAPBinding.ParameterStyle soapBindingParameterStyle;
    // Default value per JSR-181 MR Sec 4.7 "Annotation: jakarta.jws.soap.SOAPBinding" pg 28
    public static final jakarta.jws.soap.SOAPBinding.ParameterStyle SOAPBinding_ParameterStyle_DEFAULT =
            jakarta.jws.soap.SOAPBinding.ParameterStyle.WRAPPED;

    // ANNOTATION: @WebMethod
    private WebMethod webMethodAnnotation;
    private String webMethodOperationName;
    // Default value per JSR-181 MR Sec 4.2, pg 17
    public static final String WebMethod_Action_DEFAULT = "";
    private String webMethodAction;
    // Default value per JSR-181 MR sec 4.2, pg 17
    public static final Boolean WebMethod_Exclude_DEFAULT = Boolean.FALSE;
    private Boolean webMethodExclude;

    // ANNOTATION: @WebParam
    private String[] webParamNames;
    private Mode[] webParamMode;
    private String[] webParamTargetNamespace;


    // ANNOTATION: @WebResult
    private WebResult webResultAnnotation;
    private String webResultName;
    private String webResultPartName;
    // Default value per JSR-181 MR Sec 4.5.1, pg 23
    public static final String WebResult_TargetNamespace_DEFAULT = "";
    private String webResultTargetNamespace;
    // Default value per JSR-181 MR sec 4.5, pg 24
    public static final Boolean WebResult_Header_DEFAULT = Boolean.FALSE;
    private Boolean webResultHeader;
    
    //  Web Result Attachment Description information
    private boolean             _setAttachmentDesc = false;
    private AttachmentDescription attachmentDesc = null;
    
    private boolean hasRequestSwaRefAttachments = false;
    private boolean hasResponseSwaRefAttachments = false;
    private Map<String, AttachmentDescription> partAttachmentMap;
    
    private Method serviceImplMethod;
    private boolean serviceImplMethodFound = false;
    // For JAX-WS client async methods, this is the corresponding Sync method; for everything else,
    // this is "this".
    private OperationDescription syncOperationDescription = null;
    // RUNTIME INFORMATION
    Map<String, OperationRuntimeDescription> runtimeDescMap =
            Collections.synchronizedMap(new HashMap<String, OperationRuntimeDescription>());
    // Cache the actual Class of the type being returned. 
    private Class resultActualTypeClazz;

    OperationDescriptionImpl(Method method, EndpointInterfaceDescription parent) {
        parentEndpointInterfaceDescription = parent;
        methodComposite = null;
        partAttachmentMap = new HashMap<String, AttachmentDescription>();
        setSEIMethod(method);
		
        // The operationQName is intentionally unqualified to be consistent with the remaining parts of the system. 
        // Using a qualified name will cause breakage.
        // Don't do --> this.operationQName = new QName(parent.getTargetNamespace(), getOperationName());
        this.operationQName = new QName("", getOperationName());
        if (getEndpointInterfaceDescription().getEndpointDescription() != null) {
            if (!getEndpointInterfaceDescription().getEndpointDescription().getServiceDescription().isServerSide()) {
                axisOperation = createClientAxisOperation();
            } else {
                axisOperation = null;
            }
        } else {
            axisOperation = null;
        }
        if(this.axisOperation != null) {
            try {
                this.axisOperation.addParameter(new Parameter(OperationDescription.AXIS_OPERATION_PARAMETER,
                                                         this));  
            }
            catch(AxisFault af) {
                throw ExceptionFactory.makeWebServiceException(Messages.getMessage("operationDescriptionErr1"));
            }
        }
        buildAttachmentInformation();
    }

    OperationDescriptionImpl(AxisOperation operation, EndpointInterfaceDescription parent) {
        parentEndpointInterfaceDescription = parent;
        partAttachmentMap = new HashMap<String, AttachmentDescription>();
        axisOperation = operation;
        methodComposite = null;
        if(this.axisOperation != null) {
            this.operationQName = axisOperation.getName();
            try {
                this.axisOperation.addParameter(new Parameter(OperationDescription.AXIS_OPERATION_PARAMETER,
                                                         this));  
            }
            catch(AxisFault af) {
                throw ExceptionFactory.makeWebServiceException(Messages.getMessage("operationDescriptionErr1"));
            }
        } else {
            operationQName = null;
        }
        buildAttachmentInformation();
    }

    OperationDescriptionImpl(MethodDescriptionComposite mdc,
                             EndpointInterfaceDescription parent,
                             AxisOperation axisOperation) {

        parentEndpointInterfaceDescription = parent;
        partAttachmentMap = new HashMap<String, AttachmentDescription>();
        methodComposite = mdc;
        // The operationQName is intentionally unqualified to be consistent with the remaining parts of the system. 
        // Using a qualified name will cause breakage.
        // Don't do --> this.operationQName = new QName(parent.getTargetNamespace(), getOperationName());
        this.operationQName = new QName("", getOperationName());

        webMethodAnnotation = methodComposite.getWebMethodAnnot();

        parameterDescriptions = createParameterDescriptions();
        faultDescriptions = createFaultDescriptions();
        isListType = mdc.isListType();
        buildAttachmentInformation();

        //If an AxisOperation was already created for us by populateService then just use that one
        //Otherwise, create it
        if (axisOperation != null) {
            this.axisOperation = axisOperation;
        } else {
            this.axisOperation = createAxisOperation();
        }
        
        if(this.axisOperation != null) {
            try {
                this.axisOperation.addParameter(new Parameter(OperationDescription.AXIS_OPERATION_PARAMETER,
                                                         this));  
            }
            catch(AxisFault af) {
                throw ExceptionFactory.makeWebServiceException(Messages.getMessage("operationDescriptionErr1"));
            }
        }
        
        // Register understood headers on axisOperation
        registerMustUnderstandHeaders();
    }

    /**
     * Create an AxisOperation for this Operation.  Note that the ParameterDescriptions must
     * be created before calling this method since, for a DOC/LIT/BARE (aka UNWRAPPED) message, the 
     * ParamaterDescription is used to setup the AxisMessage correctly for use in SOAP Body-based
     * dispatching on incoming DOC/LIT/BARE messages.
     */
    private AxisOperation createClientAxisOperation() {
        AxisOperation newAxisOperation = null;
        try {
            if (isOneWay()) {
                newAxisOperation =
                        AxisOperationFactory.getOperationDescription(WSDL2Constants.MEP_URI_OUT_ONLY);
            } else {
                newAxisOperation =
                        AxisOperationFactory.getOperationDescription(WSDL2Constants.MEP_URI_OUT_IN);
            }
            //REVIEW: There are several other MEP's, such as: OUT_ONLY, IN_OPTIONAL_OUT, OUT_IN, OUT_OPTIONAL_IN, ROBUST_OUT_ONLY,
            //                                              ROBUST_IN_ONLY
            //      Determine how these MEP's should be handled, if at all
        } catch (Exception e) {
            throw ExceptionFactory.makeWebServiceException(Messages.getMessage("clientAxisOprErr"),e);
        }

        newAxisOperation.setName(determineOperationQName(seiMethod));
        newAxisOperation.setSoapAction(this.getAction());
        
        //*************************************************************************************
        //NOTE: assumption here is that all info. need to generate the actions will have to come
        //      from annotations (or default values)
        //*************************************************************************************
        
        String messageExchangePattern = newAxisOperation.getMessageExchangePattern();
        String operationName = newAxisOperation.getName().getLocalPart();
        String targetNS = getEndpointInterfaceDescriptionImpl().getTargetNamespace();        
        String portTypeName = getEndpointInterfaceDescriptionImpl().getPortType().getLocalPart();
         
        //We don't have a name at this point, shouldn't matter if we have the MEP.
        //On the client the input and output actions are reversed.
        String inputName = null;
        String inputAction = getOutputAction();

        //If we still don't have an action then fall back to the Default Action Pattern.
        if (inputAction == null || inputAction.length() == 0) {
            inputAction =
                WSDL11ActionHelper.getInputActionFromStringInformation( messageExchangePattern, 
                                                                        targetNS, 
                                                                        portTypeName, 
                                                                        operationName, 
                                                                        inputName);
        }
                
        ArrayList inputActions = new ArrayList();
        inputActions.add(inputAction);
        newAxisOperation.setWsamappingList(inputActions);
        
        //Map the action to the operation on the actual axisService
        //TODO: Determine whether this should be done at a higher level in the 
        //      description hierarchy
        getEndpointInterfaceDescriptionImpl().getEndpointDescriptionImpl().
            getAxisService().mapActionToOperation(inputAction, newAxisOperation);        
        
        //set the OUTPUT ACTION

        //We don't have a name at this point, shouldn't matter if we have the MEP
        //On the client the input and output actions are reversed.
        String outputName = null;
        String outputAction = getInputAction();

        if (outputAction == null || outputAction.length() == 0) {
            outputAction =
                WSDL11ActionHelper.getOutputActionFromStringInformation( messageExchangePattern, 
                                                                         targetNS, 
                                                                         portTypeName, 
                                                                         operationName, 
                                                                         outputName);
        }
        
        newAxisOperation.setOutputAction(outputAction);

        setFaultActions(newAxisOperation, operationName, targetNS, portTypeName);

        getEndpointInterfaceDescriptionImpl().getEndpointDescriptionImpl().
            getAxisService().addOperation(newAxisOperation);
        
        return newAxisOperation;
    }
    
    /**
     * Create an AxisOperation for this Operation.  Note that the ParameterDescriptions must
     * be created before calling this method since, for a DOC/LIT/BARE (aka UNWRAPPED) message, the 
     * ParamaterDescription is used to setup the AxisMessage correctly for use in SOAP Body-based
     * dispatching on incoming DOC/LIT/BARE messages.
     */
    private AxisOperation createAxisOperation() {
        AxisOperation newAxisOperation = null;
        try {
            if (isOneWay()) {
                newAxisOperation = AxisOperationFactory
                        .getOperationDescription(WSDL2Constants.MEP_URI_IN_ONLY);
            } else {
                newAxisOperation =
                        AxisOperationFactory.getOperationDescription(WSDL2Constants.MEP_URI_IN_OUT);
            }
            //TODO: There are several other MEP's, such as: OUT_ONLY, IN_OPTIONAL_OUT, OUT_IN, OUT_OPTIONAL_IN, ROBUST_OUT_ONLY,
            //                                              ROBUST_IN_ONLY
            //      Determine how these MEP's should be handled, if at all
        } catch (Exception e) {
            throw ExceptionFactory.makeWebServiceException(Messages.getMessage("clientAxisOprErr"), e);
        }

        newAxisOperation.setName(determineOperationQName(getEndpointInterfaceDescriptionImpl(),this.methodComposite));
        newAxisOperation.setSoapAction(this.getAction());

        //*************************************************************************************
        //NOTE: assumption here is that all info. need to generate the actions will have to come
        //      from annotations (or default values)
        //*************************************************************************************
        
        String messageExchangePattern = newAxisOperation.getMessageExchangePattern();
        String operationName = newAxisOperation.getName().getLocalPart();
        String targetNS = getEndpointInterfaceDescriptionImpl().getTargetNamespace();        
        String portTypeName = getEndpointInterfaceDescriptionImpl().getPortType().getLocalPart();
         
        //We don't have a name at this point, shouldn't matter if we have the MEP
        String inputName = null;
        String inputAction = getInputAction();
        
        //If we don't have an action then fall back to the Default Action Pattern.
        if (inputAction == null || inputAction.length() == 0) {
            inputAction =
                WSDL11ActionHelper.getInputActionFromStringInformation(messageExchangePattern, 
                                                                       targetNS, 
                                                                       portTypeName, 
                                                                       operationName, 
                                                                       inputName);
        }
        
        ArrayList inputActions = new ArrayList();
        inputActions.add(inputAction);
        newAxisOperation.setWsamappingList(inputActions);
        
        //Map the action to the operation on the actual axisService
        //TODO: Determine whether this should be done at a higher level in the 
        //      description hierarchy
        getEndpointInterfaceDescriptionImpl().getEndpointDescriptionImpl().
            getAxisService().mapActionToOperation(inputAction, newAxisOperation);
        
        //set the OUTPUT ACTION

        //We don't have a name at this point, shouldn't matter if we have the MEP
        String outputName = null;
        String outputAction = getOutputAction();
        
        //If we don't have an action then fall back to the Default Action Pattern.
        if (outputAction == null || outputAction.length() == 0) {
            outputAction =
                WSDL11ActionHelper.getOutputActionFromStringInformation(messageExchangePattern,
                                                                        targetNS, 
                                                                        portTypeName, 
                                                                        operationName, 
                                                                        outputName);
        }
        
        newAxisOperation.setOutputAction(outputAction);
        
        //Set the FAULT ACTION
        setFaultActions(newAxisOperation, operationName, targetNS, portTypeName);

        // If this is a DOC/LIT/BARE operation, then set the QName of the input AxisMessage to the 
        // part for the first IN or IN/OUT non-header parameter.  If there are no parameters, then don't set
        // anything.  The AxisMessage name is used to do SOAP-body based routing of DOC/LIT/BARE
        // incoming messages.
        if (getSoapBindingStyle() == jakarta.jws.soap.SOAPBinding.Style.DOCUMENT
                && getSoapBindingUse() == jakarta.jws.soap.SOAPBinding.Use.LITERAL
                && getSoapBindingParameterStyle() == jakarta.jws.soap.SOAPBinding.ParameterStyle.BARE)
        {
            ParameterDescription[] paramDescs = getParameterDescriptions();
            if (paramDescs != null && paramDescs.length > 0) {
                for (ParameterDescription paramDesc : paramDescs) {
                    WebParam.Mode paramMode = paramDesc.getMode();
                    if (!paramDesc.isHeader()
                            && (paramMode == WebParam.Mode.IN || paramMode == WebParam.Mode.INOUT))
                    {
                        // We've found the first IN or INOUT non-header parameter, so set the AxisMessage
                        // QName based on this parameter then break out of the loop.
                        AxisMessage axisMessage =
                                newAxisOperation.getMessage(WSDLConstants.MESSAGE_LABEL_IN_VALUE);
                        String elementName = paramDesc.getParameterName();
                        String partNamespace = paramDesc.getTargetNamespace();
                        if (log.isDebugEnabled()) {
                            log.debug("Setting up annotation based Doc/Lit/Bare operation: " +
                                    newAxisOperation.getName()
                                    + "; axisMessage: " + axisMessage + "; name: "
                                    + elementName + "; partTNS: " + partNamespace);
                        }
                        if (axisMessage == null) {
                            throw ExceptionFactory.makeWebServiceException(Messages.getMessage("createAxisOprErr1"));
                        } else if (DescriptionUtils.isEmpty(partNamespace)) {
                            throw ExceptionFactory.makeWebServiceException(Messages.getMessage("createAxisOprErr2"));
                        } else if (DescriptionUtils.isEmpty(elementName)) {
                            throw ExceptionFactory.makeWebServiceException(Messages.getMessage("createAxisOprErr3"));
                        } else {
                            QName partQName = new QName(partNamespace, elementName);
                            if(log.isDebugEnabled()) {
                                log.debug("Setting AxisMessage element QName for bare mapping: " +
                                        partQName);
                            }
                            axisMessage.setElementQName(partQName);
                            
                            getEndpointInterfaceDescriptionImpl().getEndpointDescriptionImpl()
                        		.getAxisService()
                        			.addMessageElementQNameToOperationMapping(partQName, newAxisOperation);
                        }
                        break;
                    }
                }
            } else {
                // There are no parameters for this Doc/Lit/Bare operation.  That means the inbound soap:Body will
                // be empty, so when asking the Body for the first element QName for routing purposes, a null will 
                // be returned.  By mapping a null key to this operation here, it will be the one found for the null
                // element during routing.
                getEndpointInterfaceDescriptionImpl().getEndpointDescriptionImpl().getAxisService()
                    .addMessageElementQNameToOperationMapping(null, newAxisOperation);
            }
        }
        return newAxisOperation;
    }

    private void setFaultActions(AxisOperation newAxisOperation,
                                 String operationName,
                                 String targetNS,
                                 String portTypeName) {
        // Walk the fault information
        FaultDescription[] faultDescs = getFaultDescriptions();
        
        //Generate fault actions according to the Default Action Pattern.
        if (faultDescs != null) {
            for (FaultDescription faultDesc : faultDescs) {
        
                AxisMessage faultMessage = new AxisMessage();
                String faultName = faultDesc.getName();
                
                if (faultName == null || faultName.equals("")) {
                    faultName = faultDesc.getExceptionClassName();
                    // Remove package name to get just class name
                    faultName = faultName.substring((faultName.lastIndexOf('.'))+1);
                }
                
                faultMessage.setName(faultName);
                if (log.isDebugEnabled()) {
                    log.debug("Set faultName = "+faultName+" for faultMessage = "+faultMessage+" and faultDesc = "+faultDesc);
                }
                
                String faultAction = 
                        WSDL11ActionHelper.getFaultActionFromStringInformation( targetNS, 
                                        portTypeName, 
                                        operationName, 
                                        faultMessage.getName());

                if (log.isDebugEnabled()) {
                    log.debug("Default faultAction = "+faultAction);
                }
                
                newAxisOperation.addFaultAction(faultDesc.getExceptionClassName(),  faultAction);
                newAxisOperation.setFaultMessages(faultMessage);
            }
        }
        
        //Override the fault actions based on any FaultAction annotations that are defined.
        FaultAction[] faultActions = getFaultActions();
        
        if (faultActions != null) {
            for (FaultAction faultAction : faultActions) {
                
                String className = null;
                
                if(faultAction instanceof FaultActionAnnot
                        &&
                        ((FaultActionAnnot) faultAction).classNameString() != null) {
                    className = ((FaultActionAnnot) faultAction).classNameString();
                }
                else if(faultAction.className() != null) {
                    className = faultAction.className().getName();
                }
                
                if(className != null) {
                    if(log.isDebugEnabled()) {
                        log.debug("Looking for FaultDescription for class: " + className + 
                                  " from @FaultAction annotation");
                    }
                    FaultDescription faultDesc = resolveFaultByExceptionName(className);
                    if (faultDesc != null)  {
                        String faultActionString = faultAction.value();
                        if (log.isDebugEnabled()) {
                            log.debug("faultAction value = "+faultActionString);
                        }

                        if (faultActionString != null && !faultActionString.equals("")) {
                            newAxisOperation.addFaultAction(className, faultActionString);
                        }
                    }
                }
            }
        }
    }

    /**
     * Adds the AxisOperation corresponding to this OperationDescription to the AxisService if it
     * isn't already there. It also addes the AxisOperation to any other routing mechanisms for
     * that AxisService: - For Doc/Lit/Bare operations it is added to the
     * MessageElementQNameToOperationMapping
     *
     * @param axisService
     */
    void addToAxisService(AxisService axisService) {
        AxisOperation newAxisOperation = getAxisOperation();
        QName axisOpQName = newAxisOperation.getName();
        // See if this operation is already added to the AxisService.  Note that we need to check the 
        // children of the service rather than using the getOperation(axisOpQName) method.  The reason is 
        // that method checks the alias table in addition to the children, and it is possible at this point
        // for an operation to have just been added to the alias table but not yet as a child.  It is this method
        // that will add that newly-created operation to the service.
        AxisOperation existingAxisOperation = (AxisOperation) axisService.getChild(axisOpQName);

        if (existingAxisOperation == null) {
            axisService.addOperation(newAxisOperation);
            // For a Doc/Lit/Bare operation, we also need to add the element mapping
        }
        if (getSoapBindingStyle() == jakarta.jws.soap.SOAPBinding.Style.DOCUMENT
                && getSoapBindingUse() == jakarta.jws.soap.SOAPBinding.Use.LITERAL
                && getSoapBindingParameterStyle() == jakarta.jws.soap.SOAPBinding.ParameterStyle
                .BARE) {
            AxisMessage axisMessage =
                    null;
            if (existingAxisOperation!=null) {
                axisMessage = existingAxisOperation.getMessage(WSDLConstants.MESSAGE_LABEL_IN_VALUE);
            } else {
                axisMessage = newAxisOperation.getMessage(WSDLConstants.MESSAGE_LABEL_IN_VALUE);
            }
            if (axisMessage != null) {
                QName elementQName = axisMessage.getElementQName();
                if (!DescriptionUtils.isEmpty(elementQName) && 
                    axisService.getOperationByMessageElementQName(elementQName) == null) {
                    axisService.addMessageElementQNameToOperationMapping(elementQName,
                            newAxisOperation);
                }
            }
        }
    }

    void setSEIMethod(Method method) {
        if (seiMethod != null) {
        	throw ExceptionFactory.makeWebServiceException(
        			new UnsupportedOperationException(Messages.getMessage("seiMethodErr")));
        } else {
            // Reset any cached state (see AXIS2-5115)
            webMethodAnnotation = null;
            webMethodOperationName = null;
            webMethodAction = null;
            webMethodExclude = null;
            requestWrapperAnnotation = null;
            requestWrapperLocalName = null;
            requestWrapperTargetNamespace = null;
            requestWrapperClassName = null;
            requestWrapperPartName = null;
            responseWrapperAnnotation = null;
            responseWrapperLocalName = null;
            responseWrapperTargetNamespace = null;
            responseWrapperClassName = null;
            responseWrapperPartName = null;
            webParamNames = null;
            webParamTargetNamespace = null;
            webParamMode = null;
            webResultAnnotation = null;
            webResultName = null;
            webResultPartName = null;
            webResultTargetNamespace = null;
            webResultHeader = null;
            soapBindingAnnotation = null;
            soapBindingStyle = null;
            soapBindingUse = null;
            soapBindingParameterStyle = null;
            actionAnnotation = null;
            onewayAnnotation = null;
            onewayIsOneway = null;
            resultActualTypeClazz = null;
            
            seiMethod = method;
            webMethodAnnotation = (WebMethod)
                getAnnotation(seiMethod, WebMethod.class);
            parameterDescriptions = createParameterDescriptions();
            faultDescriptions = createFaultDescriptions();
            isListType = ConverterUtils.hasXmlListAnnotation(seiMethod.getAnnotations());
        }
        // Register understood headers on axisOperation
        registerMustUnderstandHeaders();
    }

    public EndpointInterfaceDescription getEndpointInterfaceDescription() {
        return parentEndpointInterfaceDescription;
    }

    public EndpointInterfaceDescriptionImpl getEndpointInterfaceDescriptionImpl() {
        return (EndpointInterfaceDescriptionImpl)parentEndpointInterfaceDescription;
    }

    public AxisOperation getAxisOperation() {
        // Note that only the sync operations, and not the JAX-WS async client versions of an 
        // operation, will have an AxisOperation associated with it.  For those async operations, 
        // get the AxisOperation associated with the sync method and return that.
        if (axisOperation == null) {
            OperationDescription opDesc = getSyncOperation();
            if (opDesc != null && opDesc != this) {
                return getSyncOperation().getAxisOperation();
            }
        } 
        
        return axisOperation;
    }

    public QName getName() {
        return operationQName;
    }

    // Java-related getters
    public String getJavaMethodName() {
        String returnString = null;

        if (!isDBC()) {
            if (seiMethod != null) {
                returnString = seiMethod.getName();
            }
        } else {
            if (methodComposite != null) {
                returnString = methodComposite.getMethodName();
            }
        }

        return returnString;
    }

    public String getJavaDeclaringClassName() {
        if (!isDBC() && seiMethod != null) {
            Class clazz = seiMethod.getDeclaringClass();
            return clazz.getCanonicalName();
        } else if (methodComposite != null) {
            return methodComposite.getDeclaringClass();
        }
        return null;
    }

    public String[] getJavaParameters() {

        ArrayList<String> returnParameters = new ArrayList<String>();

        if (!isDBC()) {
            if (seiMethod != null) {
                Class[] paramaters = seiMethod.getParameterTypes();
                for (Class param : paramaters) {
                    returnParameters.add(param.getName());
                }
            }

        } else {
            if (methodComposite != null) {

                Iterator<ParameterDescriptionComposite> iter =
                        methodComposite.getParameterDescriptionCompositeList().iterator();
                while (iter.hasNext()) {
                    returnParameters.add(iter.next().getParameterType());
                }
            }
        }

        // TODO: This is different than the rest, which return null instead of an empty array
        return returnParameters.toArray(new String[0]);
    }

    /**
     * Note this will return NULL unless the operation was built via introspection on the SEI. In
     * other words, it will return null if the operation was built with WSDL.
     *
     * @return
     */
    public Method getSEIMethod() {
        return seiMethod;
    }

    MethodDescriptionComposite getMethodDescriptionComposite() {
        return methodComposite;
    }

    private boolean isWrappedParameters() {
        return getSoapBindingParameterStyle() == jakarta.jws.soap.SOAPBinding.ParameterStyle.WRAPPED;
    }

    private ParameterDescription[] createParameterDescriptions() {

        ArrayList<ParameterDescription> buildParameterList = new ArrayList<ParameterDescription>();

        if (!isDBC()) {
            Class[] parameters = seiMethod.getParameterTypes();
            Type[] paramaterTypes = seiMethod.getGenericParameterTypes();
            Annotation[][] annotations = seiMethod.getParameterAnnotations();

            for (int i = 0; i < parameters.length; i++) {
                ParameterDescription paramDesc = new ParameterDescriptionImpl(i, parameters[i],
                                                                              paramaterTypes[i],
                                                                              annotations[i], this);
                buildParameterList.add(paramDesc);
            }

        } else {

            for (int i = 0; i < methodComposite.getParameterDescriptionCompositeList().size(); i++)
            {
                ParameterDescription paramDesc =
                        new ParameterDescriptionImpl(i,
                                                     methodComposite.getParameterDescriptionComposite(
                                                             i),
                                                     this);
                buildParameterList.add(paramDesc);
            }
        }

        return buildParameterList.toArray(new ParameterDescription[buildParameterList.size()]);

    }

    private FaultDescription[] createFaultDescriptions() {

        ArrayList<FaultDescription> buildFaultList = new ArrayList<FaultDescription>();

        if (!isDBC()) {
            // get exceptions this method "throws"
            Class[] webFaultClasses = seiMethod.getExceptionTypes();

            for (Class wfClass : webFaultClasses) {
                // according to JAXWS 3.7, the @WebFault annotation is only used for customizations,
                // so we'll add all declared exceptions
                WebFault wfanno = null;
                for (Annotation anno : wfClass.getAnnotations()) {
                    if (anno.annotationType() == WebFault.class) {
                        wfanno = (WebFault)anno;
                    }
                }
                buildFaultList.add(new FaultDescriptionImpl(wfClass, wfanno, this));
            }
        } else {
            // TODO do I care about methodComposite like the paramDescription does?
            //Call FaultDescriptionImpl for all non-generic exceptions...Need to check a
            // a couple of things
            // 1. If this is a generic exception, ignore it
            // 2. If this is not a generic exception, then find it in the DBC Map
            //       If not found in map, then throw not found exception
            //3. Pass the validated WebFault dbc and possibly the classImpl dbc to FaultDescription
            //4. Possibly set AxisOperation.setFaultMessages array...or something like that

            String[] webFaultClassNames = methodComposite.getExceptions();

            HashMap<String, DescriptionBuilderComposite> dbcMap =
                    getEndpointInterfaceDescriptionImpl().getEndpointDescriptionImpl()
                            .getServiceDescriptionImpl().getDBCMap();

            if (webFaultClassNames != null) {
                for (String wfClassName : webFaultClassNames) {
                    //	Try to find this exception class in the dbc list. If we can't find it
                    //  then just assume that its a generic exception.

                    DescriptionBuilderComposite faultDBC = dbcMap.get(wfClassName);

                    if (faultDBC != null) {
                        // JAXWS 3.7 does not require @WebFault annotation
                        // We found a valid exception composite thats annotated
                        buildFaultList.add(new FaultDescriptionImpl(faultDBC, this));
                    }

                }
            }
        }

        return buildFaultList.toArray(new FaultDescription[0]);
    }

    // =====================================
    // ANNOTATION: WebMethod
    // =====================================
    public WebMethod getAnnoWebMethod() {
        if (webMethodAnnotation == null) {
            if (isDBC() && methodComposite != null) {
                webMethodAnnotation = methodComposite.getWebMethodAnnot();
            } else if (!isDBC() && seiMethod != null) {
                webMethodAnnotation = (WebMethod) getAnnotation(seiMethod, WebMethod.class);
            } else {
                if (log.isDebugEnabled()) {
                    log.debug("Unable to get WebMethod annotation");
                }
            }
        }
        return webMethodAnnotation;
    }

    static QName determineOperationQName(Method javaMethod) {
        if (log.isDebugEnabled())
        {
          log.debug("Operation QName determined to be: "+new QName(determineOperationName(javaMethod)));
        }

        return new QName(determineOperationName(javaMethod));
    }

    //According to section 4.1.1 of JSR181, the Operation should inherit
    //the target namespace from the @WebService annotation on the SEI or
    //service implementation bean.  However, changing the above method
    //currently causes problems with the clients and leaving it as is
    //does not seem to have produced any issues (as of yet.)
    public static QName determineOperationQName(EndpointInterfaceDescription eid, MethodDescriptionComposite mdc) {
        if (log.isDebugEnabled())
        {
          log.debug("Operation QName determined to be: "+((eid!= null)?new QName(eid.getTargetNamespace(), determineOperationName(mdc)):new QName(determineOperationName(mdc))));
        }
        return ((eid!= null)?new QName(eid.getTargetNamespace(), determineOperationName(mdc)):new QName(determineOperationName(mdc)));
    }
    
    private static String determineOperationName(Method javaMethod) {

        String operationName = null;
        if (javaMethod == null) {
            return null;
        }

        WebMethod wmAnnotation = (WebMethod) getAnnotation(javaMethod,WebMethod.class);
        // Per JSR-181 MR Sec 4.2 "Annotation: jakarta.jws.WebMethod" pg 17,
        // if @WebMethod specifies and operation name, use that.  Otherwise
        // default is the Java method name
        if (wmAnnotation != null && !DescriptionUtils.isEmpty(wmAnnotation.operationName())) {
            operationName = wmAnnotation.operationName();
        } else {
            operationName = javaMethod.getName();
        }

        return operationName;
    }

    //TODO: For now, we are overriding the above method only because it is static, these should
    //be combined at some point
    private static String determineOperationName(MethodDescriptionComposite mdc) {
        String operationName = null;

        if (mdc == null) {
            return null;
        }
        WebMethod wmAnnotation = mdc.getWebMethodAnnot();
        if (wmAnnotation != null && !DescriptionUtils.isEmpty(wmAnnotation.operationName())) {
            operationName = wmAnnotation.operationName();
        } else {
            operationName = mdc.getMethodName();
        }

        return operationName;
    }

    public String getOperationName() {
        return getAnnoWebMethodOperationName();
    }

    public String getAnnoWebMethodOperationName() {
        if (webMethodOperationName == null) {
            if (!isDBC() && seiMethod != null) {
                webMethodOperationName = determineOperationName(seiMethod);
            } else if (methodComposite != null) {
                webMethodOperationName = determineOperationName(methodComposite);
            }
        }
        return webMethodOperationName;
    }

    /* (non-Javadoc)
     * @see org.apache.axis2.jaxws.description.OperationDescription#getSyncOperation()
     */
    public OperationDescription getSyncOperation() {
        if (log.isDebugEnabled()) {
            log.debug("Current OperationDescription Web Method annotation \"operation\" name: " 
                    + getOperationName());
            log.debug("Current OperationDescription java method name: " + getJavaMethodName());
        }
        if (syncOperationDescription != null) {
            // No need to do anything; the sync operation has already been set and will be
            // returned below
        } else if (!isJAXWSAsyncClientMethod()) {
            // The current OpDesc is not an async operation.  Cache it, then return it below.
            syncOperationDescription = this;
        } else {
            // We haven't found a sync opdesc for this operation yet, so try again.  See the 
            // comments in the interface declaration for this method on why this might occur.
            OperationDescription opDesc = null;
            
            String webMethodAnnoName = getOperationName();
            String javaMethodName = getJavaMethodName();
            if (webMethodAnnoName != null && webMethodAnnoName.length() > 0 &&
                    webMethodAnnoName != javaMethodName) {
                EndpointInterfaceDescription eid = getEndpointInterfaceDescription();
                if (eid != null) {
                    //searching for operationDescription of synchronous operation.
                    OperationDescription[] allOperations = eid.getOperations();
                    // Find a method that has a matching annotation but is not 
                    // an asynchronous operation.
                    for (OperationDescription operation : allOperations) {
                        if (webMethodAnnoName.equals(operation.getOperationName()) &&
                                !operation.isJAXWSAsyncClientMethod()) {
                            opDesc = operation;
                            break;
                        }
                    }
                }
            }
            // Note that opDesc might still be null
            syncOperationDescription = opDesc;
        }
        if (log.isDebugEnabled()) {
            if (syncOperationDescription != null) {
                // Just log the operation name here, to avoid an infinite loop
                log.debug("Synchronous operationDescription: " + syncOperationDescription.getOperationName());
            } else {
                log.debug("Synchronous operationDescription: null");
            }
        }
        return syncOperationDescription;
    }


    public String getAction() {
        return getAnnoWebMethodAction();
    }

    public String getAnnoWebMethodAction() {
        if (webMethodAction == null) {
            if (getAnnoWebMethod() != null &&
                    !DescriptionUtils.isEmpty(getAnnoWebMethod().action())) {
                webMethodAction = getAnnoWebMethod().action();
            } else {
                webMethodAction = WebMethod_Action_DEFAULT;
            }
        }
        return webMethodAction;
    }

    public boolean isExcluded() {
        return getAnnoWebMethodExclude();
    }

    public boolean getAnnoWebMethodExclude() {
        if (webMethodExclude == null) {
            // TODO: Validation: if this attribute specified, no other elements allowed per JSR-181 MR Sec 4.2, pg 17
            // TODO: Validation: This element is not allowed on endpoint interfaces
            // Unlike the elements with a String value, if the annotation is present, exclude will always 
            // return a usable value since it will default to FALSE if the element is not present.
            if (getAnnoWebMethod() != null) {
                webMethodExclude = Boolean.valueOf(getAnnoWebMethod().exclude());
            } else {
                webMethodExclude = WebMethod_Exclude_DEFAULT;
            }
        }

        return webMethodExclude.booleanValue();
    }

    // ==========================================
    // ANNOTATION: RequestWrapper
    // ==========================================
    public RequestWrapper getAnnoRequestWrapper() {
        if (requestWrapperAnnotation == null) {
            if (!isDBC() && seiMethod != null) {
                requestWrapperAnnotation = (RequestWrapper) 
                    getAnnotation(seiMethod,RequestWrapper.class);
            } else if (isDBC() && methodComposite != null) {
                requestWrapperAnnotation = methodComposite.getRequestWrapperAnnot();
            } else {
                if (log.isDebugEnabled()) {
                    log.debug("Unable to get RequestWrapper annotation");
                }
            }
        }
        return requestWrapperAnnotation;
    }

    public String getRequestWrapperLocalName() {
        return getAnnoRequestWrapperLocalName();
    }

    /**
     * For wrapped parameter style (based on the annotation and the WSDL), returns the wrapper
     * value.  For non-wrapped (i.e. bare) parameter style, returns null.
     *
     * @return
     */
    public String getAnnoRequestWrapperLocalName() {
        if (!isWrappedParameters()) {
            // A wrapper is only meaningful for wrapped parameters
            return null;
        }
        if (requestWrapperLocalName == null) {
            if (getAnnoRequestWrapper() != null
                    && !DescriptionUtils.isEmpty(getAnnoRequestWrapper().localName())) {
                requestWrapperLocalName = getAnnoRequestWrapper().localName();
            } else {
                // The default value of localName is the value of operationQName as
                // defined in the WebMethod annotation. [JAX-WS Sec. 7.3, p. 80]
                requestWrapperLocalName = getAnnoWebMethodOperationName();
            }
        }
        return requestWrapperLocalName;
    }

    public String getRequestWrapperTargetNamespace() {
        return getAnnoRequestWrapperTargetNamespace();
    }

    /**
     * For wrapped parameter style (based on the annotation and the WSDL), returns the wrapper
     * value.  For non-wrapped (i.e. bare) parameter style, returns null.
     *
     * @return
     */
    public String getAnnoRequestWrapperTargetNamespace() {
        if (!isWrappedParameters()) {
            // A wrapper is only meaningful for wrapped parameters
            return null;
        }
        if (requestWrapperTargetNamespace == null) {
            if (getAnnoRequestWrapper() != null &&
                    !DescriptionUtils.isEmpty(getAnnoRequestWrapper().targetNamespace())) {
                requestWrapperTargetNamespace = getAnnoRequestWrapper().targetNamespace();
            } else {
                // The default value for targetNamespace is the target namespace of the SEI. [JAX-WS Sec 7.3, p. 80]
                requestWrapperTargetNamespace =
                        getEndpointInterfaceDescription().getTargetNamespace();
            }
        }
        return requestWrapperTargetNamespace;
    }

    public String getRequestWrapperClassName() {
        return getAnnoRequestWrapperClassName();
    }

    /**
     * For wrapped parameter style (based on the annotation and the WSDL), returns the wrapper
     * value.  For non-wrapped (i.e. bare) parameter style, returns null.
     *
     * @return
     */
    public String getAnnoRequestWrapperClassName() {
        if (!isWrappedParameters()) {
            // A wrapper is only meaningful for wrapped parameters
            return null;
        }
        if (requestWrapperClassName == null) {
            if (getAnnoRequestWrapper() != null &&
                    !DescriptionUtils.isEmpty(getAnnoRequestWrapper().className())) {
                requestWrapperClassName = getAnnoRequestWrapper().className();
            } else {
                // There is no default for the RequestWrapper class name.  
                // In such cases the programming layer (JAXWS) may use a programming spec specific
                // mechanism to find the class, build the class, or operate without the class.
                requestWrapperClassName = null;
            }
        }
        return requestWrapperClassName;
    }
    
    public String getRequestWrapperPartName(){
    	return getAnnoRequestWrapperPartName();
    }
    
    /**
     * Return PartName for RequestWrapper annotation if one present.
     * @return
     */
    public String getAnnoRequestWrapperPartName(){
    	if(!isWrappedParameters()){
    		return null;
    	}
        if (requestWrapperPartName == null) {
            if (getAnnoRequestWrapper() != null &&
                    !DescriptionUtils.isEmpty(getAnnoRequestWrapper().partName())) {
                requestWrapperPartName = getAnnoRequestWrapper().partName();
            } else {
                // There is no default for the RequestWrapper part name.  
            	requestWrapperPartName = null;
            }
        }
        if(log.isDebugEnabled()){
            if(requestWrapperPartName!=null){
                log.debug("RequestWrapperPartName ="+requestWrapperPartName);
            }else{
                log.debug("RequestWrapperPartName = NULL");
            }
        }
        return requestWrapperPartName;
    }
    // ===========================================
    // ANNOTATION: ResponseWrapper
    // ===========================================
    public ResponseWrapper getAnnoResponseWrapper() {
        if (responseWrapperAnnotation == null) {
            if (!isDBC() && seiMethod != null) {
                responseWrapperAnnotation = (ResponseWrapper)
                    getAnnotation(seiMethod,ResponseWrapper.class);
            } else if (isDBC() && methodComposite != null) {
                responseWrapperAnnotation = methodComposite.getResponseWrapperAnnot();
            } else {
                if (log.isDebugEnabled()) {
                    log.debug("Unable to get ResponseWrapper annotation");
                }
            }
        }
        return responseWrapperAnnotation;
    }

    public String getResponseWrapperLocalName() {
        return getAnnoResponseWrapperLocalName();
    }

    /**
     * For wrapped parameter style (based on the annotation and the WSDL), returns the wrapper
     * value.  For non-wrapped (i.e. bare) parameter style, returns null.
     *
     * @return
     */
    public String getAnnoResponseWrapperLocalName() {
        if (!isWrappedParameters()) {
            // A wrapper is only meaningful for wrapped parameters
            return null;
        }
        if (responseWrapperLocalName == null) {
            if (getAnnoResponseWrapper() != null &&
                    !DescriptionUtils.isEmpty(getAnnoResponseWrapper().localName())) {
                responseWrapperLocalName = getAnnoResponseWrapper().localName();
            } else {
                // The default value of localName is the value of operationQName as 
                // defined in the WebMethod annotation appended with "Response". [JAX-WS Sec. 7.4, p. 81]
                responseWrapperLocalName = getAnnoWebMethodOperationName() + "Response";
            }
        }
        return responseWrapperLocalName;
    }

    public String getResponseWrapperTargetNamespace() {
        return getAnnoResponseWrapperTargetNamespace();
    }

    /**
     * For wrapped parameter style (based on the annotation and the WSDL), returns the wrapper
     * value.  For non-wrapped (i.e. bare) parameter style, returns null.
     *
     * @return
     */
    public String getAnnoResponseWrapperTargetNamespace() {
        if (!isWrappedParameters()) {
            // A wrapper is only meaningful for wrapped parameters
            return null;
        }
        if (responseWrapperTargetNamespace == null) {
            if (getAnnoResponseWrapper() != null &&
                    !DescriptionUtils.isEmpty(getAnnoResponseWrapper().targetNamespace())) {
                responseWrapperTargetNamespace = getAnnoResponseWrapper().targetNamespace();
            } else {
                // The default value for targetNamespace is the target namespace of the SEI. [JAX-WS Sec 7.3, p. 80]
                responseWrapperTargetNamespace =
                        getEndpointInterfaceDescription().getTargetNamespace();
            }
        }
        return responseWrapperTargetNamespace;
    }

    public String getResponseWrapperClassName() {
        return getAnnoResponseWrapperClassName();
    }

    /**
     * For wrapped parameter style (based on the annotation and the WSDL), returns the wrapper
     * value.  For non-wrapped (i.e. bare) parameter style, returns null.
     *
     * @return
     */
    public String getAnnoResponseWrapperClassName() {
        if (!isWrappedParameters()) {
            // A wrapper is only meaningful for wrapped parameters
            return null;
        }
        if (responseWrapperClassName == null) {
            if (getAnnoResponseWrapper() != null &&
                    !DescriptionUtils.isEmpty(getAnnoResponseWrapper().className())) {
                responseWrapperClassName = getAnnoResponseWrapper().className();
            } else {
                // There is no default for the ResponseWrapper class name.  
                // In such cases the programming layer (JAXWS) may use a programming spec specific
                // mechanism to find the class, build the class, or operate without the class.
                responseWrapperClassName = null;
            }
        }
        return responseWrapperClassName;
    }

    public String getResponseWrapperPartName(){
    	return getAnnoResponseWrapperPartName();
    }
    
    /**
     * return ResponseWrapper PartName if one present.
     * @return
     */
    public String getAnnoResponseWrapperPartName(){
    	if(!isWrappedParameters()){
    		return null;
    	}
        if (responseWrapperPartName == null) {
            if (getAnnoResponseWrapper() != null &&
                    !DescriptionUtils.isEmpty(getAnnoResponseWrapper().partName())) {
                responseWrapperPartName = getAnnoResponseWrapper().partName();
            } else {
                // There is no default for the ResponseWrapper part name.  
                responseWrapperPartName = null;
            }
        }
        if(log.isDebugEnabled()){
            if(responseWrapperPartName!=null){
                log.debug("ResponseWrapperPartName ="+responseWrapperPartName);
            }else{
                log.debug("ResponseWrapperPartName = NULL");
            }
        }
        return responseWrapperPartName;
    }
    // ===========================================
    // ANNOTATION: WebFault
    // ===========================================

    /*
     * TODO some of the WebFault stuff should be moved to FaultDescription
     */

    /*
    *  TODO:  this will need revisited.  The problem is that a WebFault is not mapped 1:1 to an
    *  OperationDescription.  We should do a better job caching the information.  For now, I'm
    *  following the getWebParam() pattern.
    *
    *  This is gonna get complicated.  One other thing to consider is that a method (opdesc) may declare
    *  several types of exceptions it throws
    *
    */

    public FaultDescription[] getFaultDescriptions() {
        return faultDescriptions;
    }

    public FaultDescription resolveFaultByExceptionName(String exceptionClassName) {
        if (faultDescriptions != null) {
            for (FaultDescription fd : faultDescriptions) {
                if (exceptionClassName.equals(fd.getExceptionClassName()))
                    return fd;
            }
        }
        return null;
    }

    // ===========================================
    // ANNOTATION: WebParam
    // ===========================================
    // Note that this annotation is handled by the ParameterDescripton.
    // Methods are provided on OperationDescription as convenience methods.
    public ParameterDescription[] getParameterDescriptions() {
        return parameterDescriptions;
    }

    public ParameterDescription getParameterDescription(String parameterName) {
        // TODO: Validation: For BARE paramaterUse, only a single IN our INOUT paramater and a single output (either return or OUT or INOUT) is allowed 
        //       Per JSR-224, Sec 3.6.2.2, pg 37
        ParameterDescription matchingParamDesc = null;
        if (parameterName != null && !parameterName.equals("")) {
            for (ParameterDescription paramDesc : parameterDescriptions) {
                if (parameterName.equals(paramDesc.getParameterName())) {
                    matchingParamDesc = paramDesc;
                    break;
                }
            }
        }
        return matchingParamDesc;
    }

    public ParameterDescription getParameterDescription(int parameterNumber) {
        return parameterDescriptions[parameterNumber];
    }

    public String[] getParamNames() {
        return getAnnoWebParamNames();
    }

    public String[] getAnnoWebParamNames() {
        if (webParamNames == null) {
            ArrayList<String> buildNames = new ArrayList<String>();
            ParameterDescription[] paramDescs = getParameterDescriptions();
            for (ParameterDescription currentParamDesc : paramDescs) {
                buildNames.add(currentParamDesc.getParameterName());
            }
            webParamNames = buildNames.toArray(new String[0]);
        }
        return webParamNames;
    }

    public String[] getAnnoWebParamTargetNamespaces() {
        if (webParamTargetNamespace == null) {
            ArrayList<String> buildTargetNS = new ArrayList<String>();
            ParameterDescription[] paramDescs = getParameterDescriptions();
            for (ParameterDescription currentParamDesc : paramDescs) {
                buildTargetNS.add(currentParamDesc.getTargetNamespace());
            }
            webParamTargetNamespace = buildTargetNS.toArray(new String[0]);
        }
        return webParamTargetNamespace;
    }

    public String getAnnoWebParamTargetNamespace(String name) {
        String returnTargetNS = null;
        ParameterDescription paramDesc = getParameterDescription(name);
        if (paramDesc != null) {
            returnTargetNS = paramDesc.getTargetNamespace();
        }
        return returnTargetNS;
    }


    public Mode[] getAnnoWebParamModes() {
        if (webParamMode == null) {
            ArrayList<Mode> buildModes = new ArrayList<Mode>();
            ParameterDescription[] paramDescs = getParameterDescriptions();
            for (ParameterDescription currentParamDesc : paramDescs) {
                buildModes.add(((ParameterDescriptionJava)currentParamDesc).getAnnoWebParamMode());
            }
            webParamMode = buildModes.toArray(new Mode[0]);
        }
        return webParamMode;
    }

    public boolean isAnnoWebParamHeader(String name) {
        ParameterDescription paramDesc = getParameterDescription(name);
        if (paramDesc != null) {
            return paramDesc.isHeader();
        }
        return false;
    }

    // ===========================================
    // ANNOTATION: WebResult
    // ===========================================
    public WebResult getAnnoWebResult() {
        if (webResultAnnotation == null) {
            if (!isDBC() && seiMethod != null) {
                webResultAnnotation = (WebResult)
                    getAnnotation(seiMethod,WebResult.class);
            } else if (methodComposite != null) {
                webResultAnnotation = methodComposite.getWebResultAnnot();
            } else {
                if (log.isDebugEnabled()) {
                    log.debug("Unable to get WebResult annotation");
                }
            }
        }
        return webResultAnnotation;
    }

    public boolean isWebResultAnnotationSpecified() {
        return getAnnoWebResult() != null;
    }

    public boolean isOperationReturningResult() {
        boolean isResult = false;
        if (!isAnnoOneWay()) {
            if (!isDBC() && seiMethod != null) {
                if (seiMethod.getReturnType() != Void.TYPE) {
                    isResult = true;
                }
            } else if (methodComposite != null) {
                if (!DescriptionUtils.isEmpty(methodComposite.getReturnType()) &&
                        !methodComposite.getReturnType().equals("void"))
                    isResult = true;
            } else {
                if (log.isDebugEnabled()) {
                    log.debug("No class to determine if result is returned");
                }
            }
        }
        return isResult;
    }

    public String getResultName() {
        return getAnnoWebResultName();
    }

    public String getAnnoWebResultName() {
        if (!isOperationReturningResult()) {
            return null;
        }
        if (webResultName == null) {
            if (getAnnoWebResult() != null && !DescriptionUtils.isEmpty(getAnnoWebResult().name()))
            {
                webResultName = getAnnoWebResult().name();
            } else if (getAnnoSoapBindingStyle() == SOAPBinding.Style.DOCUMENT
                    && getAnnoSoapBindingParameterStyle() == SOAPBinding.ParameterStyle.BARE) {
                // Default for operation style DOCUMENT and paramater style BARE per JSR 181 MR Sec 4.5.1, pg 23
                webResultName = getAnnoWebMethodOperationName() + "Response";

            } else {
                // Defeault value is "return" per JSR-181 MR Sec. 4.5.1, p. 22
                webResultName = "return";
            }
        }
        return webResultName;
    }

    public String getResultPartName() {
        return getAnnoWebResultPartName();
    }

    public String getAnnoWebResultPartName() {
        if (!isOperationReturningResult()) {
            return null;
        }
        if (webResultPartName == null) {
            if (getAnnoWebResult() != null &&
                    !DescriptionUtils.isEmpty(getAnnoWebResult().partName())) {
                webResultPartName = getAnnoWebResult().partName();
            } else {
                // Default is the WebResult.name per JSR-181 MR Sec 4.5.1, pg 23
                webResultPartName = getAnnoWebResultName();
            }
        }
        return webResultPartName;
    }

    public String getResultTargetNamespace() {
        return getAnnoWebResultTargetNamespace();
    }

    public String getAnnoWebResultTargetNamespace() {
        if (!isOperationReturningResult()) {
            return null;
        }
        if (webResultTargetNamespace == null) {
            if (getAnnoWebResult() != null &&
                    !DescriptionUtils.isEmpty(getAnnoWebResult().targetNamespace())) {
                webResultTargetNamespace = getAnnoWebResult().targetNamespace();
            } else if (getAnnoSoapBindingStyle() == SOAPBinding.Style.DOCUMENT
                    && getAnnoSoapBindingParameterStyle() == SOAPBinding.ParameterStyle.WRAPPED
                    && !getAnnoWebResultHeader()) {
                // Default for operation style DOCUMENT and paramater style WRAPPED and the return value
                // does not map to a header per JSR-181 MR Sec 4.5.1, pg 23-24
                webResultTargetNamespace = WebResult_TargetNamespace_DEFAULT;
            } else {
                // Default is the namespace from the WebService per JSR-181 MR Sec 4.5.1, pg 23-24
                webResultTargetNamespace =
                        ((EndpointDescriptionJava)getEndpointInterfaceDescription()
                                .getEndpointDescription()).getAnnoWebServiceTargetNamespace();
            }

        }
        return webResultTargetNamespace;
    }

    public boolean isResultHeader() {
        return getAnnoWebResultHeader();
    }

    public boolean getAnnoWebResultHeader() {
        if (!isOperationReturningResult()) {
            return false;
        }
        if (webResultHeader == null) {
            if (getAnnoWebResult() != null) {
                // Unlike the elements with a String value, if the annotation is present, exclude will always 
                // return a usable value since it will default to FALSE if the element is not present.
                webResultHeader = Boolean.valueOf(getAnnoWebResult().header());
            } else {
                webResultHeader = WebResult_Header_DEFAULT;
            }
        }
        return webResultHeader.booleanValue();
    }

    // ===========================================
    // ANNOTATION: SOAPBinding
    // ===========================================
    public SOAPBinding getAnnoSoapBinding() {
        // TODO: VALIDATION: Only style of DOCUMENT allowed on Method annotation; remember to check the Type's style setting also
        //       JSR-181 Sec 4.7 p. 28
        if (soapBindingAnnotation == null) {
            if (!isDBC() && seiMethod != null) {
                soapBindingAnnotation = (SOAPBinding)
                    getAnnotation(seiMethod,SOAPBinding.class);
            } else if (isDBC() && methodComposite != null) {
                soapBindingAnnotation = methodComposite.getSoapBindingAnnot();
            } else {
                if (log.isDebugEnabled()) {
                    log.debug("Unable to get SOAP Binding annotation");
                }
            }
        }
        return soapBindingAnnotation;
    }

    public jakarta.jws.soap.SOAPBinding.Style getSoapBindingStyle() {
        return getAnnoSoapBindingStyle();
    }

    public jakarta.jws.soap.SOAPBinding.Style getAnnoSoapBindingStyle() {
        if (soapBindingStyle == null) {
            if (getAnnoSoapBinding() != null && getAnnoSoapBinding().style() != null) {
                soapBindingStyle = getAnnoSoapBinding().style();
            } else {
                // Per JSR-181 MR Sec 4.7, pg 28: if not specified, use the Type value.
                soapBindingStyle = getEndpointInterfaceDescription().getSoapBindingStyle();
            }
        }
        return soapBindingStyle;
    }

    public jakarta.jws.soap.SOAPBinding.Use getSoapBindingUse() {
        return getAnnoSoapBindingUse();
    }

    public jakarta.jws.soap.SOAPBinding.Use getAnnoSoapBindingUse() {
        if (soapBindingUse == null) {
            if (getAnnoSoapBinding() != null && getAnnoSoapBinding().use() != null) {
                soapBindingUse = getAnnoSoapBinding().use();
            } else {
                // Per JSR-181 MR Sec 4.7, pg 28: if not specified, use the Type value.
                soapBindingUse = getEndpointInterfaceDescription().getSoapBindingUse();
            }
        }
        return soapBindingUse;
    }

    public jakarta.jws.soap.SOAPBinding.ParameterStyle getSoapBindingParameterStyle() {
        return getAnnoSoapBindingParameterStyle();
    }

    public jakarta.jws.soap.SOAPBinding.ParameterStyle getAnnoSoapBindingParameterStyle() {
        if (soapBindingParameterStyle == null) {
            if (getAnnoSoapBinding() != null && getAnnoSoapBinding().parameterStyle() != null) {
                soapBindingParameterStyle = getAnnoSoapBinding().parameterStyle();
            } else {
                // Per JSR-181 MR Sec 4.7, pg 28: if not specified, use the Type value.
                soapBindingParameterStyle =
                        getEndpointInterfaceDescription().getSoapBindingParameterStyle();
            }
        }
        return soapBindingParameterStyle;
    }
    
    // ===========================================
    // ANNOTATION: Action
    // ===========================================
    public Action getAnnoAction() {
        if (actionAnnotation == null) {
            if (!isDBC() && seiMethod != null) {
                actionAnnotation = (Action) getAnnotation(seiMethod, Action.class);
            }
            else if (methodComposite != null) {
                actionAnnotation = methodComposite.getActionAnnot();
            }
            else {
                if (log.isDebugEnabled()) {
                    log.debug("Unable to get Action annotation.");
                }
            }
        }
        
        if (log.isDebugEnabled()) {
            log.debug("getAnnoAction: " + actionAnnotation);
        }
        
        return actionAnnotation;
    }
    
    private String getInputAction() {
        String inputAction = null;
        Action action = getAnnoAction();
        
        if (action != null) {
            inputAction = action.input();
        }
        
        if (log.isDebugEnabled()) {
            log.debug("getInputAction: " + inputAction);
        }
        
        return inputAction;
    }
    
    private String getOutputAction() {
        String outputAction = null;
        Action action = getAnnoAction();
        
        if (action != null) {
            outputAction = action.output();
        }
        
        if (log.isDebugEnabled()) {
            log.debug("getOutputAction: " + outputAction);
        }
        
        return outputAction;
    }
    
    private FaultAction[] getFaultActions() {
        FaultAction[] faultActions = null;
        Action action = getAnnoAction();
        
        if (action !=  null) {
            faultActions = action.fault();
        }
        
        if (log.isDebugEnabled()) {
            log.debug("getFaultActions: " + Arrays.toString(faultActions));
        }
        
        return faultActions;
    }

    // ===========================================
    // ANNOTATION: OneWay
    // ===========================================
    public Oneway getAnnoOneway() {
        if (onewayAnnotation == null) {
            // Get the onew-way annotation from either the method composite (server-side)
            // or from the SEI method (client-side).  
            if (isDBC() && methodComposite != null) {
                if (methodComposite.isOneWay()) {
                    onewayAnnotation = OneWayAnnot.createOneWayAnnotImpl();
                }
            } else if (!isDBC() && seiMethod != null) {
                onewayAnnotation = (Oneway) getAnnotation(seiMethod,Oneway.class);
            } else {
                if (log.isDebugEnabled()) {
                    log.debug("Unable to get OneWay annotation");
                }
            }
        }
        return onewayAnnotation;
    }

    public boolean isOneWay() {
        return isAnnoOneWay();
    }

    public boolean isAnnoOneWay() {
        if (onewayIsOneway == null) {
            if (getAnnoOneway() != null) {
                // The presence of the annotation indicates the method is oneway
                onewayIsOneway = Boolean.TRUE;
            } else {
                // If the annotation is not present, the default is this is NOT a One Way method
                onewayIsOneway = Boolean.FALSE;
            }
        }
        return onewayIsOneway.booleanValue();
    }

    private boolean isDBC() {
        if (methodComposite != null)
            return true;
        else
            return false;
    }

    /* (non-Javadoc)
     * @see org.apache.axis2.jaxws.description.OperationDescription#getResultType()
     */
    public Class getResultType() {
        Class returnClass = null;
        if (!isDBC() && getSEIMethod() != null) {
            Method seiMethod = this.getSEIMethod();
            returnClass = seiMethod.getReturnType();
        } else if (methodComposite != null) {
            returnClass = methodComposite.getReturnTypeClass();
        } else {
            if (log.isDebugEnabled()) {
                log.debug("Unable to get result type from null class");
            }
        }
        return returnClass;
    }

    /* (non-Javadoc)
     * @see org.apache.axis2.jaxws.description.OperationDescription#getResultActualType()
     */
    public Class getResultActualType() {
        if(resultActualTypeClazz == null) {
            resultActualTypeClazz = findResultActualType();    
        }
        return resultActualTypeClazz;
    }
    
    public Class findResultActualType() {
        // TODO: Fix this!  it isn't doing the right thing for DBC as noted below with FIXME comments
        //       This is used to marshall the rsp on the service (dbc) and demarshall on the client (reflection)
        //       But we shouldn't get an async OpDesc on the service since getDispatchableOperation(QN) removes them.

        Class returnType = getResultType();
        if (returnType == null) {
            return null;
        }
        if (isJAXWSAsyncClientMethod()) {
            //pooling implementation
            if (Response.class == returnType) {
                if (!isDBC()) {
                    Type type = seiMethod.getGenericReturnType();
                    ParameterizedType pType = (ParameterizedType)type;
                    Type aType = pType.getActualTypeArguments()[0];
                    if (aType != null && ParameterizedType.class.isInstance(aType)) {
                        return (Class)((ParameterizedType)aType).getRawType();
                    }
                    return (Class)aType;
                } else {
                    // FIXME: This doesn't work for DBC.  That's OK for now because DBC isn't used on the client side
                    //        yet; the client is all Java Reflection.  On the Service side, the Async methods are not used.
                    //        This needs to return T for Response<T>, or List for Response<List<T>>>
                    return returnType;
                }
            }
            //Callback Implementation
            else {
                // FIXME: This doesn't work for DBC.  That's OK for now because DBC isn't used on the client side
                //        yet; the client is all Java Reflection.  On the Service side, the Async methods are not used.
                //        This needs to find and return T for AsyncHandler<T>, or List for AsyncHandler<List<T>>>
                Type[] type = getGenericParameterTypes();
                Class parameters[] = getParameterTypes();
                int i = 0;
                for (Class param : parameters) {
                    if (AsyncHandler.class.isAssignableFrom(param)) {
                        ParameterizedType pType = (ParameterizedType)type[i];
                        Type aType = pType.getActualTypeArguments()[0];
                        if (aType != null && ParameterizedType.class.isInstance(aType)) {
                            return (Class)((ParameterizedType)aType).getRawType();
                        }
                        return (Class)aType;
                    }
                    i++;
                }
            }
        }

        return returnType;
    }


    private Type[] getGenericParameterTypes() {
        if (isDBC()) {
            // FIXME: This doesn't work for DBC.  That's OK for now because DBC isn't used on the client side
            //        yet; the client is all Java Reflection.  On the Service side, the Async methods are not used.
            //        And this method is only used to parse out the JAX-WS Async parameter types to find
            //        AsyncHandler<T>.  The problem with the code that was removed is that a Type can not be
            //        instantiated, so we can't new up a Type inside the PDC.
        	throw ExceptionFactory.makeWebServiceException(new UnsupportedOperationException(Messages.getMessage("genParamTypesErr")));
//           Type [] type = new Type[parameterDescriptions.length];
//           for (int i=0; i < parameterDescriptions.length; i++){
//               type[i] = ((ParameterDescriptionImpl) parameterDescriptions[i]).getParameterActualGenericType();
//           }
//           return type;
        } else {
            Type [] type = seiMethod.getGenericParameterTypes();
            return type;
        }
    }

    private Class[] getParameterTypes() {
        if (isDBC()) {
            Class [] parameters = new Class[parameterDescriptions.length];
            for (int i = 0; i < parameterDescriptions.length; i++) {
                parameters[i] = parameterDescriptions[i].getParameterType();
            }
            return parameters;
        } else {
            Class [] parameters = seiMethod.getParameterTypes();
            return parameters;
        }
    }

    /*
    * (non-Javadoc)
    * @see org.apache.axis2.jaxws.description.OperationDescription#isJAXWSAsyncClientMethod()
    */
    public boolean isJAXWSAsyncClientMethod() {
        boolean answer = false;
        String methodName = null;
        String returnTypeName = null;
        if (isDBC()) {
            methodName = getMethodDescriptionComposite().getMethodName();
            returnTypeName = getMethodDescriptionComposite().getReturnType();
        } else {
            Method method = this.getSEIMethod();
            if (method != null) {
                methodName = method.getName();
                returnTypeName = method.getReturnType().getName();
            }
        }
        if (methodName != null && returnTypeName != null) {
            answer = (returnTypeName.contains(Response.class.getName()) ||
                    returnTypeName.contains(Future.class.getName()));
        }
        if (log.isDebugEnabled()) {
            log.debug("Method = " + methodName);
            log.debug("Return Type = " + returnTypeName);
            log.debug("Is client async = " + answer);
        }
        return answer;
    }

    /**
     * Return the Service Implementation method for this operation IFF it has been set by a previous
     * call to getMethodFromServiceImpl(Class serviceImplClass).  Otherwise a null is returned.
     *
     * @return
     */
    private Method getMethodFromServiceImpl() {
        return serviceImplMethod;

    }

    public Method getMethodFromServiceImpl(Class serviceImpl) {

        // TODO: This doesn't support overloaded methods in the service impl  This is
        //       DIFFERENT than overloaded WSDL operations (which aren't supported).  We
        //       MUST support overloaded service impl methods as long as they have different
        //       wsdl operation names.  For example:
        //  ServiceImple Class          SEI Class
        //                              @WebMethod.name = Foo1
        //  void foo()                  void foo()
        //                              @WebMethod.name = Foo2
        //  void foo(int)               void foo(int)
        //                              @WebMethod.name = Foo3
        //  void foo(String)            void foo(String)
        //
        //  There will be two OpDescs, Foo1 and Foo2; the incoming wsdl operation will correctly identify
        //  which OpDesc.  However, to return the correct service impl method, we need to compare the
        //  signatures, not just the method names.
        if (!serviceImplMethodFound) {
            Method[] methods = serviceImpl.getMethods();
            String opDescMethodName = getJavaMethodName();
            ParameterDescription[] paramDesc = getParameterDescriptions();
            // TODO: As noted above, a full signature is necessary, not just number of params
            int numberOfParams = 0;
            if (paramDesc != null) {
                numberOfParams = paramDesc.length;
            }

            // Loop through all the methods on the service impl and find the method that maps
            // to this OperationDescripton
            for (Method checkMethod : methods) {
                if (checkMethod.getName().equals(opDescMethodName)) {
                    Class[] methodParams = checkMethod.getParameterTypes();
                    // TODO: As noted above, a full signature is necessary, not just number of params
                    if (methodParams.length == numberOfParams) {
                        if (paramTypesMatch(paramDesc, methodParams)) {
                            serviceImplMethod = checkMethod;
                            break;
                        }
                    }
                }
            }
            serviceImplMethodFound = true;
        }
        return serviceImplMethod;
    }

    /**
     * This method will compare the types of the parameters in a <code>ParameterDescription</code>
     * vs. the type of the arguments in the parameters of a <code>Method</code>.
     *
     * @param paramDescs   - <code>ParameterDescription</code>[]
     * @param methodParams - <code>Class</code>[]
     * @return - <code>boolean</code>
     */
    private boolean paramTypesMatch(ParameterDescription[] paramDescs, Class[]
            methodParams) {
        for (int i = 0; i < paramDescs.length; i++) {
            String mParamType = methodParams[i].getName();
            String pdType = getPDType(paramDescs[i]);
            if (mParamType == null || !mParamType.equals(pdType)) {
                return false;
            }
        }
        return true;
    }

    /**
     * This will get a <code>String</code> representing the parameter class of a
     * <code>ParameterDescription</code>.
     *
     * @param pd - <code>ParameterDescrition</code>
     * @return - <code>String</code>
     */
    private String getPDType(ParameterDescription pd) {
        String type = null;
        if (pd.getParameterType() != null) {
            type = pd.getParameterType().getName();
        } else if (pd.getParameterActualType() != null) {
            type = pd.getParameterActualType().getName();
        }
        return type;
    }

    public OperationRuntimeDescription getOperationRuntimeDesc(String name) {
        // TODO Add toString support
        return runtimeDescMap.get(name);
    }

    public void setOperationRuntimeDesc(OperationRuntimeDescription ord) {
        // TODO Add toString support
        runtimeDescMap.put(ord.getKey(), ord);
    }
    
    public boolean isListType() {
    	return isListType;
    }
    
    /**
     * This method will return the namespace for the BindingInput that this operation
     * specifies. It will first look for a namespace on the WSDL Binding object and then 
     * default to the web service's target namespace.
     */
    public String getBindingInputNamespace() {
        String tns = null;
        Binding binding =
                this.getEndpointInterfaceDescriptionImpl()
                    .getEndpointDescriptionImpl()
                    .getWSDLBinding();
        if (binding != null) {
            if (log.isDebugEnabled()) {
                log.debug("Found WSDL binding");
            }
            // this call does not support overloaded WSDL operations as it
            // does not specify the name of the input and output messages
            BindingOperation bindingOp =
                    binding.getBindingOperation(getOperationName(), null, null);
            if (bindingOp != null && bindingOp.getBindingInput() != null) {
                if (log.isDebugEnabled()) {
                    log.debug("Found WSDL binding operation and input");
                }
                tns = getBindingNamespace(bindingOp.getBindingInput());
                if (tns != null && log.isDebugEnabled()) {
                    log.debug("For operation: " + bindingOp.getName()
                            + " returning the following namespace for input message"
                            + " from WSDL: " + tns);
                }
            }
        }
        if (tns == null) {
            tns = getEndpointInterfaceDescription().getTargetNamespace();
            if (log.isDebugEnabled()) {
                log.debug("For binding input returning @WebService.targetNamespace: " + tns);
            }
        }
        return tns;
    }

    /**
     * This method will return the namespace for the BindingOutput that this operation
     * specifies. It will first look for a namespace on the WSDL Binding object and then 
     * default to the web service's target namespace.
     */
    public String getBindingOutputNamespace() {
        String tns = null;
        Binding binding =
                this.getEndpointInterfaceDescriptionImpl()
                    .getEndpointDescriptionImpl()
                    .getWSDLBinding();
        if (binding != null) {
            if (log.isDebugEnabled()) {
                log.debug("Found WSDL binding");
            }
            // this call does not support overloaded WSDL operations as it
            // does not specify the name of the input and output messages
            BindingOperation bindingOp =
                    binding.getBindingOperation(getOperationName(), null, null);
            if (bindingOp != null && bindingOp.getBindingOutput() != null) {
                if (log.isDebugEnabled()) {
                    log.debug("Found WSDL binding operation and output");
                }
                tns = getBindingNamespace(bindingOp.getBindingOutput());
                if (tns != null && log.isDebugEnabled()) {
                    log.debug("For operation: " + bindingOp.getName()
                            + " returning the following namespace for output message"
                            + " from WSDL: " + tns);
                }
            }
        }
        if (tns == null) {
            tns = getEndpointInterfaceDescription().getTargetNamespace();
            if (log.isDebugEnabled()) {
                log.debug("For binding output returning @WebService.targetNamespace: " + tns);
            }
        }
        return tns;
    }

    
    /**
     * This method will retrieve the namespace that is specified by the BindingInput or
     * BindingOutput object.
     */
    private String getBindingNamespace(AttributeExtensible opInfo) {
        if (opInfo instanceof BindingInput) {
            BindingInput input = (BindingInput) opInfo;
            return DescriptionUtils.getNamespaceFromSOAPElement(input.getExtensibilityElements());
        } else if (opInfo instanceof BindingOutput) {
            BindingOutput output = (BindingOutput) opInfo;
            return DescriptionUtils.getNamespaceFromSOAPElement(output.getExtensibilityElements());
        }
        return null;
    }
    
    public AttachmentDescription getResultAttachmentDescription() {
        String partName = this.getResultPartName();
        if (partName != null) {
            if (log.isDebugEnabled()) {
                log.debug("Returning result AttachmentDescription for partName: " + partName);
            }
            return partAttachmentMap.get(partName);
        }
        if (log.isDebugEnabled()) {
            log.debug("Did not find result AttachmentDescription for partName");
        }
        return null;
    }
    
    /**
     * This method will drive the building of AttachmentDescription objects for the
     * operation input/output messages in the WSDL.
     *
     */
    private void buildAttachmentInformation() {
        if (log.isDebugEnabled()) {
            log.debug("Start buildAttachmentInformation");
        }
        // Only building attachment info if we find a full WSDL
        if (this.getEndpointInterfaceDescriptionImpl()
                .getEndpointDescriptionImpl()
                .isWSDLFullySpecified()) {
            if (log.isDebugEnabled()) {
                log.debug("A full WSDL is available.  Query the WSDL binding for the " +
                                "AttachmentDescription information.");
            }
            DescriptionUtils.getAttachmentFromBinding(this,
                                                      this.getEndpointInterfaceDescriptionImpl()
                                                          .getEndpointDescriptionImpl()
                                                          .getWSDLBinding());
        }  else {
            if (log.isDebugEnabled()) {
                log.debug("The WSDL is not available.  Looking for @WebService wsdlLocation.");
            }
            
            // Try getting a WSDL
            String wsdlLocation = this.getEndpointInterfaceDescriptionImpl().
                getEndpointDescriptionImpl().
                getAnnoWebServiceWSDLLocation();
            
            if (wsdlLocation == null || wsdlLocation.length() == 0) {
                if (log.isDebugEnabled()) {
                    log.debug("@WebService wsdlLocation is not specified.  " +
                                "Processing continues without AttachmentDescription information");
                }
            } else {
                if (log.isDebugEnabled()) {
                    log.debug("@WebService wsdlLocation is " + wsdlLocation);
                }
                
                Definition def = null;
                WSDL4JWrapper wsdl4j = null;
                try {
                    File file = new File(wsdlLocation);
                    URL url = file.toURI().toURL();
                    wsdl4j = new WSDL4JWrapper(url, true, 2);  // In this context, limit the wsdl memory
                    def = wsdl4j.getDefinition();
                } catch (Throwable t) {
                    if (log.isDebugEnabled()) {
                        log.debug("Error occurred while loading WSDL.  " +
                                        "Procesing continues without AttachmentDescription " +
                                        "information. " + t);
                    }
                }
                if (def != null) {
                    // Set the WSDL on the server
                    this.getEndpointInterfaceDescriptionImpl().getEndpointDescriptionImpl().
                        getServiceDescriptionImpl().setWsdlWrapper(wsdl4j);
                    if (log.isDebugEnabled()) {
                        log.debug("WSDL Definition is loaded.  Get the WSDL Binding.");
                    }
                    
                    Binding binding = this.getEndpointInterfaceDescriptionImpl().
                        getEndpointDescriptionImpl().getWSDLBinding();
                    
                    if (binding == null) {
                        if (log.isDebugEnabled()) {
                            log.debug("WSDL Binding was not found for serviceName=" +  
                                      this.getEndpointInterfaceDescription().
                                        getEndpointDescription().getServiceQName() +
                                      " and portName=" +
                                      this.getEndpointInterfaceDescription().
                                        getEndpointDescription().getPortQName());
                        }
                    } else {
                        if (log.isDebugEnabled()) {
                            log.debug("Query Binding for AttachmentDescription Information");
                        }
                        DescriptionUtils.getAttachmentFromBinding(this, binding);
                    }          
                }
            }
            
        }
        if (log.isDebugEnabled()) {
            log.debug("End buildAttachmentInformation");
        }
    }

    /**
     * This will return an AttachmentDescription based on a part name.
     */
    public AttachmentDescription getPartAttachmentDescription(String partName) {
        return partAttachmentMap.get(partName);
    }

    public void addPartAttachmentDescription(String partName, AttachmentDescription attachmentDesc) {
        partAttachmentMap.put(partName, attachmentDesc);
    }
    
    public boolean hasRequestSwaRefAttachments() {
        return hasRequestSwaRefAttachments;
    }
    
    public void setHasRequestSwaRefAttachments(boolean b) {
        hasRequestSwaRefAttachments = b;
    }
    
    public boolean hasResponseSwaRefAttachments() {
        return hasResponseSwaRefAttachments;
    }
    
    public void setHasResponseSwaRefAttachments(boolean b) {
        hasResponseSwaRefAttachments = b;
    }
            
    public String toString() {
        final String newline = "\n";
        final String sameline = "; ";
        StringBuffer string = new StringBuffer();
        try {
            string.append(super.toString());
            string.append(newline);
            string.append("Name: " + getName());
            string.append(sameline);
            string.append("Operation Name: " + getOperationName());
            string.append(sameline);
            string.append("Action: " + getAction());
            //
            string.append(newline);
            string.append("Operation excluded: " + (isExcluded() == true));
            string.append(sameline);
            string.append("Is oneway: " + (isOneWay() == true));
            string.append(sameline);
            string.append("Is returning result: " + (isOperationReturningResult() == true));
            string.append(sameline);
            string.append("Is result header: " + (isResultHeader() == true));
            string.append(sameline);
            string.append("Is JAXWS Client Async method: " + (isJAXWSAsyncClientMethod() == true));
            //
            string.append(newline);
            string.append("SOAP Style: " + getSoapBindingStyle());
            string.append(sameline);
            string.append("SOAP Use: " + getSoapBindingUse());
            string.append(sameline);
            string.append("SOAP Parameter Style: " + getSoapBindingParameterStyle());
            //
            string.append(newline);
            string.append("Result name: " + getResultName());
            string.append(sameline);
            string.append("Result part name: " + getResultPartName());
            string.append(sameline);
            string.append("Result type: " + getResultType());
            string.append(sameline);
            string.append("Result actual type: " + getResultActualType());
            if (getResultAttachmentDescription() != null) {
                string.append(newline);
                string.append(getResultAttachmentDescription().toString());
            }
            //
            string.append(newline);
            string.append("Request Wrapper class: " + getRequestWrapperClassName());
            string.append(sameline);
            string.append("Response Wrapper class: " + getResponseWrapperClassName());
            //
            string.append(newline);
            string.append("Java declaring class name: " + getJavaDeclaringClassName());
            string.append(newline);
            string.append("Java method name: " + getJavaMethodName());
            string.append(newline);
            string.append("Java parameters: " + Arrays.toString(getJavaParameters()));
            string.append(newline);
            string.append("Service Implementation method: " + getMethodFromServiceImpl());
            string.append(newline);
            string.append("Axis Operation: " + getAxisOperation());

            string.append(newline);
            ParameterDescription[] paramDescs = getParameterDescriptions();
            if (paramDescs != null && paramDescs.length > 0) {
                string.append("Number of Parameter Descriptions: " + paramDescs.length);
                for (ParameterDescription paramDesc : paramDescs) {
                    string.append(newline);
                    string.append("Parameter Description: " + paramDesc.toString());
                }
            } else {
                string.append("No Parameter Descriptions");
            }

            string.append(newline);
            FaultDescription[] faultDescs = getFaultDescriptions();
            if (faultDescs != null && faultDescs.length > 0) {
                string.append("Number of Fault Descriptions: " + faultDescs.length);
                for (FaultDescription faultDesc : faultDescs) {
                    string.append(newline);
                    string.append("Fault Description: " + faultDesc.toString());
                }
            } else {
                string.append("No Fault Descriptions");
            }

            if(!partAttachmentMap.isEmpty()) {
                string.append(newline);
                string.append("Number of Attachment Descriptions: "  + partAttachmentMap.size());
                string.append(newline);
                Iterator<AttachmentDescription> adIter = partAttachmentMap.values().iterator();
                while(adIter.hasNext()) {
                        string.append(adIter.next().toString());
                        string.append(newline);
                }
            } else {
                string.append(newline);
                string.append("No Attachment Descriptions");
                string.append(newline);
            }
            
            string.append("RuntimeDescriptions:" + this.runtimeDescMap.size());
            string.append(newline);
            for (OperationRuntimeDescription runtimeDesc : runtimeDescMap.values()) {
                string.append(runtimeDesc.toString());
                string.append(newline);
            }
        }
        catch (Throwable t) {
            string.append(newline);
            string.append("Complete debug information not currently available for " +
                    "OperationDescription");
            return string.toString();
        }
        return string.toString();
    }
    
    /** 
     * Adds a list of SOAP header QNames that are understood by JAXWS for this operation to the
     * AxisOperation.  This will be used by Axis2 to verify that all headers marked as
     * mustUnderstand have been or will be processed.
     * 
     * Server side headers considered understood [JAXWS 2.0 Sec 10.2.1 page 117]
     * - SEI method params that are in headers 
     * - Headers processed by application handlers (TBD)
     * 
     * Client side headers considered understood: None
     *
     */
    private void registerMustUnderstandHeaders() {
        
        // REVIEW: If client side (return value, OUT or INOUT params) needs to be supported then
        // this needs to process client and server differently.

        AxisOperation theAxisOperation = getAxisOperation(); 
        if (theAxisOperation == null) {
            if (log.isDebugEnabled()) {
                log.debug("The axis operation is null, so header QNames could not be registered.  OpDesc = " + this);
            }
            return;
        }

        // If any IN or INOUT parameters are in the header, then add their QNames to the list
        ParameterDescription paramDescs[] = getParameterDescriptions();
        ArrayList understoodQNames = new ArrayList();
        if (paramDescs != null && paramDescs.length > 0) {
            for (ParameterDescription paramDesc : paramDescs) {
                if (paramDesc.isHeader() 
                        && (paramDesc.getMode() == WebParam.Mode.IN 
                                || paramDesc.getMode() == WebParam.Mode.INOUT)) {
                    QName headerQN = new QName(paramDesc.getTargetNamespace(), 
                                               paramDesc.getParameterName());
                    understoodQNames.add(headerQN);
                    if (log.isDebugEnabled()) {
                        log.debug("OpDesc: understoodQName added to AxisOperation (if not null) as IN or INOUT param " + headerQN);
                    }
                } else if (paramDesc.isHeader() 
                        && (paramDesc.getMode() == WebParam.Mode.OUT)) {
                    QName headerQN = new QName(paramDesc.getTargetNamespace(), 
                            paramDesc.getParameterName());
                    understoodQNames.add(headerQN);
                    if (log.isDebugEnabled()) {
                        log.debug("OpDesc: understoodQName added to AxisOperation (if not null) as OUT param " + headerQN);
                    }
                }
            }
        }
        // Also check for result in header
        if (isResultHeader() && (getResultName() != null)) {
            QName headerQN = new QName(getResultTargetNamespace(), getResultName());
            understoodQNames.add(headerQN);
            if (log.isDebugEnabled()) {
                log.debug("OpDesc: understoodQName added to AxisOperation (if not null) as result param " + headerQN);
            }            
        }

        if (!understoodQNames.isEmpty()) {
            Parameter headerQNParameter = new Parameter(OperationDescription.HEADER_PARAMETER_QNAMES,
                                                        understoodQNames);
            try {
                theAxisOperation.addParameter(headerQNParameter);
            } catch (AxisFault e) {
                log.warn(Messages.getMessage("regMUHeadersErr", theAxisOperation.getClass().getName(), e.getMessage()));
                if (log.isDebugEnabled()) {
                    log.debug("Unable to add Parameter for header QNames to AxisOperation: " + theAxisOperation.getClass().getName(), e);
                }
            }
        }
    }
    /**
     * Get an annotation.  This is wrappered to avoid a Java2Security violation.
     * @param cls Class that contains annotation 
     * @param annotation Class of requrested Annotation
     * @return annotation or null
     */
    private static Annotation getAnnotation(final AnnotatedElement element, final Class annotation) {
        return (Annotation) AccessController.doPrivileged(new PrivilegedAction() {
            public Object run() {
                return element.getAnnotation(annotation);
            }
        });
    }
}
