/*
 * 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 java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import jakarta.jws.WebService;
import jakarta.jws.soap.SOAPBinding;
import javax.wsdl.Definition;
import javax.wsdl.PortType;
import javax.xml.namespace.QName;

import org.apache.axis2.AxisFault;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.description.AxisOperation;
import org.apache.axis2.description.AxisOperationFactory;
import org.apache.axis2.description.AxisService;
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.EndpointDescription;
import org.apache.axis2.jaxws.description.EndpointInterfaceDescription;
import org.apache.axis2.jaxws.description.EndpointInterfaceDescriptionJava;
import org.apache.axis2.jaxws.description.EndpointInterfaceDescriptionWSDL;
import org.apache.axis2.jaxws.description.MethodRetriever;
import org.apache.axis2.jaxws.description.OperationDescription;
import org.apache.axis2.jaxws.description.ServiceDescriptionWSDL;
import org.apache.axis2.jaxws.description.builder.DescriptionBuilderComposite;
import org.apache.axis2.jaxws.description.builder.MDQConstants;
import org.apache.axis2.jaxws.description.builder.MethodDescriptionComposite;
import org.apache.axis2.jaxws.description.impl.ServiceDescriptionImpl;
import org.apache.axis2.jaxws.i18n.Messages;
import org.apache.axis2.jaxws.util.WSToolingUtils;
import org.apache.axis2.wsdl.WSDLConstants;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/** @see ../EndpointInterfaceDescription */
public class EndpointInterfaceDescriptionImpl
        implements EndpointInterfaceDescription, EndpointInterfaceDescriptionJava,
        EndpointInterfaceDescriptionWSDL {
    private final EndpointDescriptionImpl parentEndpointDescription;
    private final ArrayList<OperationDescription> operationDescriptions =
            new ArrayList<OperationDescription>();
    private Map<QName, List<OperationDescription>> dispatchableOperations;
    private final DescriptionBuilderComposite dbc;

    //Logging setup
    private static final Log log = LogFactory.getLog(EndpointInterfaceDescriptionImpl.class);

    // ===========================================
    // ANNOTATION related information
    // ===========================================

    // ANNOTATION: @WebService
    private WebService webServiceAnnotation;
    private String webServiceTargetNamespace;
    private String webService_Name;


    // ANNOTATION: @SOAPBinding
    // Note this is the Type-level annotation.  See OperationDescription for the Method-level annotation
    private SOAPBinding soapBindingAnnotation;
    private jakarta.jws.soap.SOAPBinding.Style soapBindingStyle;
    // Default value per JSR-181 MR Sec 4.7 "Annotation: jakarta.jws.soap.SOAPBinding" pg 28
    public static final jakarta.jws.soap.SOAPBinding.Style SOAPBinding_Style_DEFAULT =
            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 soapParameterStyle;
    // 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;

    
    /**
     * Add the operationDescription to the list of operations.  Note that we can not create the
     * list of dispatchable operations at this points.
     * @see #initializeDispatchableOperationsList()
     * 
     * @param operation The operation description to add to this endpoint interface
     */
    void addOperation(OperationDescription operation) {
        if (log.isDebugEnabled()) {
            log.debug("start addOperation for " + operation);
        }
        operationDescriptions.add(operation);
        // Clear the runtime description information, it will need to be rebuilt.
        ServiceDescriptionImpl sd = (getEndpointDescriptionImpl() == null) ? null:
            getEndpointDescriptionImpl().getServiceDescriptionImpl();
        if (sd != null) {
            sd.runtimeDescMap.clear();
        }
        
        if (log.isDebugEnabled()) {
            log.debug("end addOperation");
        }
    }

    /**
     * Construct a service requester (aka client-side) EndpointInterfaceDescription for the
     * given SEI class.  This constructor is used if hierachy is being built fully from annotations
     * and not WSDL.
     * @param sei
     * @param parent
     */
    EndpointInterfaceDescriptionImpl(Class sei, EndpointDescriptionImpl parent) {
        parentEndpointDescription = parent;
        dbc = new DescriptionBuilderComposite();
        dbc.setCorrespondingClass(sei);

        // Per JSR-181 all methods on the SEI are mapped to operations regardless
        // of whether they include an @WebMethod annotation.  That annotation may
        // be present to customize the mapping, but is not required (p14)
        for (Method method : getSEIMethods(dbc.getCorrespondingClass())) {
            OperationDescription operation = new OperationDescriptionImpl(method, this);
            addOperation(operation);
        } 
    }

    /**
     * Construct a service requester (aka client-side) EndpointInterfaceDescrption for
     * an SEI represented by an AxisService.  This constructor is used if the hierachy is
     * being built fully from WSDL.  The AxisService and underlying AxisOperations were built
     * based on the WSDL, so we will use them to create the necessary objects.
     *
     * @param parent
     */
    EndpointInterfaceDescriptionImpl(EndpointDescriptionImpl parent) {
        parentEndpointDescription = parent;
        dbc = new DescriptionBuilderComposite();
        AxisService axisService = parentEndpointDescription.getAxisService();
        if (axisService != null) {
            ArrayList publishedOperations = axisService.getPublishedOperations();
            Iterator operationsIterator = publishedOperations.iterator();
            while (operationsIterator.hasNext()) {
                AxisOperation axisOperation = (AxisOperation)operationsIterator.next();
                addOperation(new OperationDescriptionImpl(axisOperation, this));
            }
        }
    }
    /**
     * Construct as Provider-based endpoint which does not have specific WSDL operations.  Since there
     * are no specific WSDL operations in this case, there will be a single generic operation that
     * will accept any incoming operation.
     * 
     * @param dbc
     * @param parent
     */
    EndpointInterfaceDescriptionImpl(DescriptionBuilderComposite dbc, 
                                     EndpointDescriptionImpl parent) {
        if (log.isDebugEnabled()) {
            log.debug("Creating a EndpointInterfaceDescription for a generic WSDL-less provider");
        }
        parentEndpointDescription = parent;
        this.dbc = dbc;
        
        // Construct the generic provider AxisOperation to use then construct
        // an OperactionDescription for it.
        AxisOperation genericProviderAxisOp = null;
        try {
            genericProviderAxisOp = 
                AxisOperationFactory.getOperationDescription(WSDL2Constants.MEP_URI_IN_OUT);
        } catch (AxisFault e) {
            throw ExceptionFactory.makeWebServiceException(Messages.getMessage("eiDescrImplErr"),e);
        }
        
        genericProviderAxisOp.setName(new QName(JAXWS_NOWSDL_PROVIDER_OPERATION_NAME));
        OperationDescription opDesc = new OperationDescriptionImpl(genericProviderAxisOp, this);
        
        addOperation(opDesc);
        AxisService axisService = getEndpointDescription().getAxisService();
        axisService.addOperation(genericProviderAxisOp);
    }

    /**
     * Build an EndpointInterfaceDescription from a DescriptionBuilderComposite.  This EID has
     * WSDL operations associated with it.  It could represent an SEI-based endpoint built from
     * WSDL or annotations, OR it could represent a Provider-based enpoint built from WSDL.  It will
     * not represent a Provider-based endpoint built without WSDL (which does not know about
     * specific WSDL operations). For that type of EID, see:
     * @see  #EndpointInterfaceDescriptionImpl(DescriptionBuilderComposite dbc, EndpointDescriptionImpl parent)
     * @param dbc
     * @param isClass
     * @param parent
     */
    EndpointInterfaceDescriptionImpl(DescriptionBuilderComposite dbc,
                                     boolean isClass,
                                     EndpointDescriptionImpl parent) {

        parentEndpointDescription = parent;
        this.dbc = dbc;

        getEndpointDescription().getAxisService()
                .setTargetNamespace(getEndpointDescriptionImpl().getTargetNamespace());

        // Per JSR-181 all methods on the SEI are mapped to operations regardless
        // of whether they include an @WebMethod annotation.  That annotation may
        // be present to customize the mapping, but is not required (p14)

        //We are processing the SEI composite
        //For every MethodDescriptionComposite in this list, call OperationDescription 
        //constructor for it, then add this operation

        //Retrieve the relevant method composites for this dbc (and those in the superclass chain)
        
        MethodRetriever methodRetriever = getMethodRetriever();

        Iterator<MethodDescriptionComposite> iter = methodRetriever.retrieveMethods();
        
        if (log.isDebugEnabled()) {
            log.debug("EndpointInterfaceDescriptionImpl: Finished retrieving methods");
        }
        
        MethodDescriptionComposite mdc = null;

        while (iter.hasNext()) {
            mdc = iter.next();

            mdc.setDeclaringClass(dbc.getClassName());

            // Only add if it is a method that would be or is in the WSDL i.e. 
            // don't create an OperationDescriptor for the MDC representing the
            // constructor
            if (DescriptionUtils.createOperationDescription(mdc.getMethodName())) {
                //First check if this operation already exists on the AxisService, if so
                //then use that in the description hierarchy

                AxisService axisService = getEndpointDescription().getAxisService();
                AxisOperation axisOperation = axisService
                        .getOperation(OperationDescriptionImpl.determineOperationQName(this, mdc));
                
                OperationDescription operation =
                    new OperationDescriptionImpl(mdc, this, axisOperation);

                //1) if wsdl is defined then we should only expose operations that are in wsdl.
                //NOTE:If wsdl is defined AxisService will have all operations found in wsdl, 
                //AxisServiceBuilder will do that part before metadata layer is invoked.
                //2) if wsdl not defined we need to expose operations based on annotation, in 
                //which case we need add operations not found in AxisService. 
                if(getWSDLDefinition() != null){
                    if(log.isDebugEnabled()){
                        log.debug("wsdl definition found, we will not expose operation not found in AxisService.");
                    }
                    if (log.isDebugEnabled())
                        log.debug("EID: Just added operation= " + operation.getOperationName());
                    addOperation(operation);
                    //Cater to partial wsdl case, if wsdl is found but AxisService was
                    //not built using this wsdl we need to add operation to AxisService.
                    if(!getEndpointDescriptionImpl().isWSDLFullySpecified()){
                        if(log.isDebugEnabled()){
                            log.debug("Partial wsdl definition found, we will add operation to the AxisService.");
                        }
                        ((OperationDescriptionImpl)operation).addToAxisService(axisService);
                    }
                }
                //Since wsdl is not defined add all operations to AxisService and OperationDescriptionList.
                else if(axisOperation == null) {
                    if(log.isDebugEnabled()){
                        log.debug("wsdl defintion NOT found, we will expose operation using annotations.");
                    }
                    // This axisOperation did not already exist on the AxisService, and so was created
                    // with the OperationDescription, so we need to add the operation to the service
                    ((OperationDescriptionImpl)operation).addToAxisService(axisService);
                    if (log.isDebugEnabled())
                        log.debug("EID: Just added operation= " + operation.getOperationName());
                    addOperation(operation);
                }
                //This is the case where wsdl is not defined and AxisOperation is found in Axis Service.
                //Here we have to ensure that corresponding OperationDescription is added to OperationDescriptionList.
                else if(getWSDLDefinition()==null && axisOperation!=null){
                    if (log.isDebugEnabled())
                        log.debug("EID: Just added operation= " + operation.getOperationName());
                    addOperation(operation);
                }
            }
           
        }

        if (log.isDebugEnabled())
            log.debug("EndpointInterfaceDescriptionImpl: Finished Adding operations");

    }


    private static Method[] getSEIMethods(Class sei) {
        // Per JSR-181 all methods on the SEI are mapped to operations regardless
        // of whether they include an @WebMethod annotation.  That annotation may
        // be present to customize the mapping, but is not required (p14)
        Method[] seiMethods = sei.getMethods();
        ArrayList methodList = new ArrayList();
        if (sei != null) {
            for (Method method : seiMethods) {

                if (method.getDeclaringClass().getName().equals("java.lang.Object")) {
                    continue;
                }
                methodList.add(method);
                if (!Modifier.isPublic(method.getModifiers())) {
                    // JSR-181 says methods must be public (p14)
                    throw ExceptionFactory.makeWebServiceException(Messages.getMessage("seiMethodsErr"));
                }
                // TODO: other validation per JSR-181
            }

        }
        return (Method[])methodList.toArray(new Method[methodList.size()]);
//        return seiMethods;
    }

    /**
     * Update a previously created EndpointInterfaceDescription with information from an annotated
     * SEI.  This should only be necessary when the this was created with WSDL. In this case, the
     * information from the WSDL is augmented based on the annotated SEI.
     *
     * @param sei
     */
    void updateWithSEI(Class sei) {
        Class seiClass = dbc.getCorrespondingClass();
        if (seiClass != null && seiClass != sei) {
            throw ExceptionFactory.makeWebServiceException(new UnsupportedOperationException(Messages.getMessage("seiProcessingErr")));
        }
        else if (seiClass != null && seiClass == sei) {
            // We've already done the necessary updates for this SEI
            return;
        }
        else if (sei != null) {
            // Reset any cached state (see AXIS2-5115)
            webServiceAnnotation = null;
            webServiceTargetNamespace = null;
            webService_Name = null;
            soapBindingAnnotation = null;
            soapBindingStyle = null;
            soapBindingUse = null;
            soapParameterStyle = null;
            dispatchableOperations = null;
            
            seiClass = sei;
            dbc.setCorrespondingClass(sei);
            // Update (or possibly add) the OperationDescription for each of the methods on the SEI.
            for (Method seiMethod : getSEIMethods(seiClass)) {

                if (getOperation(seiMethod) != null) {
                    // If an OpDesc already exists with this java method set on it, then the OpDesc has already
                    // been updated for this method, so skip it.
                    continue;
                }
                // At this point (for now at least) the operations were created with WSDL previously.
                // If they had been created from an annotated class and no WSDL, then the seiClass would have 
                // already been set so we would have taken other branches in this if test.  (Note this could
                // change once AxisServices can be built from annotations by the ServiceDescription class).
                // Since the operations were created from WSDL, they will not have a java method, which
                // comes from the SEI, set on them yet.
                //
                // Another consideration is that currently Axis2 does not support overloaded WSDL operations.
                // That means there will only be one OperationDesc build from WSDL.  Still another consideration is
                // that the JAXWS async methods which may exist on the SEI will NOT exist in the WSDL.  An example
                // of these methods for the WSDL operation:
                //     String echo(String)
                // optionally generated JAX-WS SEI methods from the tooling; take note of the annotation specifying the 
                // operation name
                //     @WebMethod(operationName="echo" ...)
                //     Response<String> echoStringAsync(String)
                //     @WebMethod(operationName="echo" ...)
                //     Future<?> echoStringAsync(String, AsyncHandler)
                //
                // So given all the above, the code does the following based on the operation QName
                // (which might also be the java method name; see determineOperationQName for details)
                // (1) If an operationDesc does not exist, add it.
                // (2) If an operationDesc does exist but does not have a java method set on it, set it
                // (3) If an operationDesc does exist and has a java method set on it already, add a new one. 
                //
                // TODO: May need to change when Axis2 supports overloaded WSDL operations

                // Get the QName for this java method and then update (or add) the appropriate OperationDescription
                // See comments below for imporant notes about the current implementation.
                // NOTE ON OVERLOADED OPERATIONS
                // Axis2 does NOT currently support overloading WSDL operations.
                QName seiOperationQName =
                        OperationDescriptionImpl.determineOperationQName(seiMethod);
                OperationDescription[] updateOpDesc = getOperation(seiOperationQName);
                if (updateOpDesc == null || updateOpDesc.length == 0) {
                    // This operation wasn't defined in the WSDL.  Note that the JAX-WS async methods
                    // which are defined on the SEI are not defined as operations in the WSDL.
                    // Although they usually specific the same OperationName as the WSDL operation, 
                    // there may be cases where they do not.
                    OperationDescription operation = new OperationDescriptionImpl(seiMethod, this);
                    addOperation(operation);
                } else {
                    // Currently Axis2 does not support overloaded operations.  That means that even if the WSDL
                    // defined overloaded operations, there would still only be a single AxisOperation, and it
                    // would be the last operation encounterd.
                    // HOWEVER the generated JAX-WS async methods (see above) may (will always?) have the same
                    // operation name and so will come down this path; they need to be added.
                    // TODO: When Axis2 starts supporting overloaded operations, then this logic will need to be changed

                    // Loop through all the opdescs; if one doesn't currently have a java method set, set it
                    // If all have java methods set, then add a new one.  Assume we'll need to add a new one.
                    boolean addOpDesc = true;
                    for (OperationDescription checkOpDesc : updateOpDesc) {
                        if (checkOpDesc.getSEIMethod() == null) {
                            // TODO: Should this be checking (somehow) that the signature matches?  Probably not an issue until overloaded WSDL ops are supported.
                            
                            //Make sure that this is not one of the 'async' methods associated with
                            //this operation. If it is, let it be created as its own opDesc.
                            if (!DescriptionUtils.isAsync(seiMethod)) {
                                ((OperationDescriptionImpl) checkOpDesc).setSEIMethod(seiMethod);
                                addOpDesc = false;
                                break;
                            }
                        }
                    }
                    if (addOpDesc) {
                        OperationDescription operation =
                                new OperationDescriptionImpl(seiMethod, this);
                        addOperation(operation);
                    }
                }
            }
        }
    }

    /**
     * Return the OperationDescriptions corresponding to a particular Java method name. Note that an
     * array is returned because a method could be overloaded.
     *
     * @param javaMethodName String representing a Java Method Name
     * @return
     */
    public OperationDescription[] getOperationForJavaMethod(String javaMethodName) {
        if (log.isDebugEnabled()) {
            log.debug("starting on javaMethodName : " + javaMethodName + " , on operationDescriptions.size: " + operationDescriptions.size());
        }
        if (DescriptionUtils.isEmpty(javaMethodName)) {
            return null;
        }

        ArrayList<OperationDescription> matchingOperations = new ArrayList<OperationDescription>();
        for (OperationDescription operation : getOperations()) {
            if (javaMethodName.equals(operation.getJavaMethodName())) {
                matchingOperations.add(operation);
            } else {
                if (log.isDebugEnabled()) {
                    log.debug("getOperationForJavaMethod() found no match for javaMethodName: " + javaMethodName + "  on operation: " + operation.getJavaMethodName());
                }
	    }	    
        }

        if (log.isDebugEnabled()) {
            log.debug("getOperationForJavaMethod() found match size: " + matchingOperations.size());
        }
        if (matchingOperations.size() == 0)
            return null;
        else
            return matchingOperations.toArray(new OperationDescription[0]);
    }

    /**
     * Return the OperationDesription (only one) corresponding to the OperationName passed in.
     *
     * @param operationName
     * @return
     */
    public OperationDescription getOperation(String operationName) {
        if (DescriptionUtils.isEmpty(operationName)) {
            return null;
        }

        OperationDescription matchingOperation = null;
        for (OperationDescription operation : getOperations()) {
            if (operationName.equals(operation.getOperationName())) {
                matchingOperation = operation;
                break;
            }
        }
        return matchingOperation;
    }

    public OperationDescription[] getOperations() {
        return operationDescriptions.toArray(new OperationDescription[0]);
    }

    public EndpointDescriptionImpl getEndpointDescriptionImpl() {
        return (EndpointDescriptionImpl)parentEndpointDescription;
    }

    public EndpointDescription getEndpointDescription() {
        return parentEndpointDescription;
    }

    /**
     * Return an array of Operations given an operation QName.  Note that an array is returned since
     * a WSDL operation may be overloaded per JAX-WS.
     *
     * @param operationQName
     * @return
     */
    public OperationDescription[] getOperation(QName operationQName) {
        OperationDescription[] returnOperations = null;
        if (!DescriptionUtils.isEmpty(operationQName)) {
            ArrayList<OperationDescription> matchingOperations =
                    new ArrayList<OperationDescription>();
            OperationDescription[] allOperations = getOperations();
            for (OperationDescription operation : allOperations) {
                if (operation.getName().getLocalPart().equals(operationQName.getLocalPart())) {
                    matchingOperations.add(operation);
                }
            }
            // Only return an array if there's anything in it
            if (matchingOperations.size() > 0) {
                returnOperations = matchingOperations.toArray(new OperationDescription[0]);
            }
        }
        return returnOperations;
    }

    /* (non-Javadoc)
    * @see org.apache.axis2.jaxws.description.EndpointInterfaceDescription#getDispatchableOperation(QName operationQName)
    */
    public OperationDescription[] getDispatchableOperation(QName operationQName) {
        // REVIEW: Can this be synced at a more granular level?  Can't sync on dispatchableOperations because
        //         it may be null, but also the initialization must finish before next thread sees 
        //         dispatachableOperations != null
        synchronized(this) {
            if (dispatchableOperations == null) {
                initializeDispatchableOperationsList();
            }
        }

        // Note that OperationDescriptionImpl creates operation qname with empty namespace. Thus 
        // using only the localPart to get dispatchable operations.
    	QName key = new QName("",operationQName.getLocalPart());
    	List<OperationDescription> operations = dispatchableOperations.get(key);
    	if(operations!=null){
    		return operations.toArray(new OperationDescription[operations.size()]);
    	}
    	return new OperationDescription[0];
    }
    /* (non-Javadoc)
     * @see org.apache.axis2.jaxws.description.EndpointInterfaceDescription#getDispatchableOperations()
     */
    public OperationDescription[] getDispatchableOperations() {
        OperationDescription[] returnOperations = null;

        // REVIEW: Can this be synced at a more granular level?  Can't sync on dispatchableOperations because
        //         it may be null, but also the initialization must finish before next thread sees 
        //         dispatachableOperations != null
        synchronized(this) {
            if (dispatchableOperations == null) {
                initializeDispatchableOperationsList();
            }
        }
        Collection<List<OperationDescription>> dispatchableValues = dispatchableOperations.values();
        Iterator<List<OperationDescription>> iteratorValues = dispatchableValues.iterator();
        ArrayList<OperationDescription> allDispatchableOperations = new ArrayList<OperationDescription>();
        while (iteratorValues.hasNext()) {
            List<OperationDescription> opDescList = iteratorValues.next();
            allDispatchableOperations.addAll(opDescList);
        }
        if (allDispatchableOperations.size() > 0) {
            returnOperations = allDispatchableOperations.toArray(new OperationDescription[allDispatchableOperations.size()]);
        }
        return returnOperations;
    }

    /**
     * Create the list of dispatchable operations from the list of all the operations.  A 
     * dispatchable operation is one that can be invoked on the endpoint, so it DOES NOT include:
     * - JAXWS Client Async methods
     * - Methods that have been excluded via WebMethod.exclude annotation
     *
     * Note: We have to create the list of dispatchable operations in a lazy way; we can't
     * create it as the operations are added via addOperations() because on the client
     * that list is built in two parts; first using AxisOperations from the WSDL, which will
     * not have any annotation information (such as WebMethod.exclude).  That list will then
     *  be updated with SEI information, which is the point annotation information becomes
     *  available.
     */
    private void initializeDispatchableOperationsList() {
        dispatchableOperations = new HashMap<QName, List<OperationDescription>>();
        OperationDescription[] opDescs = getOperations();
        for (OperationDescription opDesc : opDescs) {
          if (!opDesc.isJAXWSAsyncClientMethod() && !opDesc.isExcluded()) {
              List<OperationDescription> dispatchableOperationsWithName = dispatchableOperations.get(opDesc.getName());
              if(dispatchableOperationsWithName == null) {
                  dispatchableOperationsWithName = new ArrayList<OperationDescription>();
                  dispatchableOperations.put(opDesc.getName(), dispatchableOperationsWithName);
              }
              dispatchableOperationsWithName.add(opDesc);
          }
        }
    }

    /**
     * Return an OperationDescription for the corresponding SEI method.  Note that this ONLY works
     * if the OperationDescriptions were created from introspecting an SEI.  If the were created
     * with a WSDL then use the getOperation(QName) method, which can return > 1 operation.
     *
     * @param seiMethod The java.lang.Method from the SEI for which an OperationDescription is
     *                  wanted
     * @return
     */
    public OperationDescription getOperation(Method seiMethod) {
        OperationDescription returnOperation = null;
        if (seiMethod != null) {
            OperationDescription[] allOperations = getOperations();
            for (OperationDescription operation : allOperations) {
                if (operation.getSEIMethod() != null && operation.getSEIMethod().equals(seiMethod))
                {
                    returnOperation = operation;
                }
            }
        }
        return returnOperation;
    }

    public Class getSEIClass() {
        return dbc.getCorrespondingClass();
    }
    // Annotation-realted getters

    // ========================================
    // SOAP Binding annotation realted methods
    // ========================================
    public SOAPBinding getAnnoSoapBinding() {
        if (soapBindingAnnotation == null) {
            soapBindingAnnotation = dbc.getSoapBindingAnnot();
        }
        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 {
                soapBindingStyle = SOAPBinding_Style_DEFAULT;
            }
        }
        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 {
                soapBindingUse = SOAPBinding_Use_DEFAULT;
            }
        }
        return soapBindingUse;
    }

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

    public jakarta.jws.soap.SOAPBinding.ParameterStyle getAnnoSoapBindingParameterStyle() {
        if (soapParameterStyle == null) {
            if (getAnnoSoapBinding() != null && getAnnoSoapBinding().parameterStyle() != null) {
                soapParameterStyle = getAnnoSoapBinding().parameterStyle();
            } else {
                soapParameterStyle = SOAPBinding_ParameterStyle_DEFAULT;
            }
        }
        return soapParameterStyle;
    }

    private Definition getWSDLDefinition() {
        return ((ServiceDescriptionWSDL)getEndpointDescription().getServiceDescription())
                .getWSDLDefinition();
    }

    public PortType getWSDLPortType() {
        PortType portType = null;
//        EndpointDescriptionWSDL endpointDescWSDL = (EndpointDescriptionWSDL) getEndpointDescription();
//        Binding wsdlBinding = endpointDescWSDL.getWSDLBinding();
//        if (wsdlBinding != null) {
//            portType = wsdlBinding.getPortType();
//        }
        Definition wsdlDefn = getWSDLDefinition();
        if (wsdlDefn != null) {
            String tns = getEndpointDescription().getTargetNamespace();
            String localPart = getEndpointDescription().getName();
            if (localPart != null) {
                portType = wsdlDefn.getPortType(new QName(tns, localPart));
            }
        }
        return portType;
    }


    public String getTargetNamespace() {
        return getAnnoWebServiceTargetNamespace();
    }

    public WebService getAnnoWebService() {
        if (webServiceAnnotation == null) {
            webServiceAnnotation = dbc.getWebServiceAnnot();
        }
        return webServiceAnnotation;
    }

    public String getAnnoWebServiceTargetNamespace() {
        if (webServiceTargetNamespace == null) {
            if (getAnnoWebService() != null
                    && !DescriptionUtils.isEmpty(getAnnoWebService().targetNamespace())) {
                webServiceTargetNamespace = getAnnoWebService().targetNamespace();
            } else {
                // Default value per JSR-181 MR Sec 4.1 pg 15 defers to "Implementation defined, 
                // as described in JAX-WS 2.0, section 3.2" which is JAX-WS 2.0 Sec 3.2, pg 29.
                webServiceTargetNamespace =
                    DescriptionUtils.makeNamespaceFromPackageName(
                            DescriptionUtils.getJavaPackageName(dbc.getClassName()),
                            "http");
            }
        }
        return webServiceTargetNamespace;
    }

    public String getAnnoWebServiceName() {
        if (webService_Name == null) {

            if (getAnnoWebService() != null
                    && !DescriptionUtils.isEmpty(getAnnoWebService().name())) {
                webService_Name = getAnnoWebService().name();
            } else {
                // Per the JSR 181 Specification, the default
                // is the simple name of the class.
                webService_Name = DescriptionUtils.getSimpleJavaClassName(dbc.getClassName());
            }
        }
        return webService_Name;
    }

    public String getName() {
        return getAnnoWebServiceName();
    }

    public QName getPortType() {
        String name = getName();
        String tns = getTargetNamespace();
        return new QName(tns, name);
    }

    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("PortType: " + getPortType());
            //
            string.append(newline);
            string.append("SOAP Style: " + getSoapBindingStyle());
            string.append(sameline);
            string.append("SOAP Use: " + getSoapBindingUse());
            string.append(sameline);
            string.append("SOAP Paramater Style: " + getSoapBindingParameterStyle());
            //
            string.append(newline);
            OperationDescription[] operations = getOperations();
            if (operations != null && operations.length > 0) {
                string.append("Number of operations: " + operations.length);
                for (OperationDescription operation : operations) {
                    string.append(newline);
                    string.append("Operation: " + operation.toString());
                }
            } else {
                string.append("OperationDescription array is null");
            }
        }
        catch (Throwable t) {
            string.append(newline);
            string.append("Complete debug information not currently available for " +
                    "EndpointInterfaceDescription");
            return string.toString();
        }
        return string.toString();
    }
      
    /*
     * This method returns a method retriever that properly implements the specified behavior, 
     * which is determined by a user-defined system property
     */
    private MethodRetriever getMethodRetriever () {

        MethodRetriever methodRetriever = null;
        //default behavior
        boolean newSunBehavior = false;
        
        String legacyWebmethod = null;
        String postWebMethodRules = readUsePostWebMethodRulesFlag();
        if (WSToolingUtils.hasValue(postWebMethodRules) && postWebMethodRules.equalsIgnoreCase("true")) {
            if (log.isDebugEnabled()) {
                log.debug("EndpointInterfaceDescriptionImpl: System property " + MDQConstants.USE_POST_WEB_METHOD_RULES + "  set to true, all other configurations are ignored");
            }
            newSunBehavior = true;
        } else {
            //Lets make sure the default behavior is supported by packaged JDK.
            //this check verifies JAX-WS2.2 tooling is supported.
            newSunBehavior = isNewSunBehaviorSupported();

            //The system property always overrides the manifest
            //property. So, if both are set than the manifest property will be ignored

            legacyWebmethod = readLegacyWebMethodFlag();
            if (WSToolingUtils.hasValue(legacyWebmethod) && legacyWebmethod.equalsIgnoreCase("true")) {
                if (log.isDebugEnabled()) {
                    log.debug("EndpointInterfaceDescriptionImpl: System or Manifest property " + MDQConstants.USE_LEGACY_WEB_METHOD_RULES + " set to true");
                }
                //LegacyWebmethod property set to true, use old JAX-WS tooling behavior.  
                newSunBehavior = false;
            } else {
                //If LegacyWebmehtod was not set check for sun property.
                String newSunRulesFlag = getNewSunRulesFlag();
                if (WSToolingUtils.hasValue(newSunRulesFlag) && newSunRulesFlag.equalsIgnoreCase("true")) {
                    if (log.isDebugEnabled()) {
                        log.debug("EndpointInterfaceDescriptionImpl: System property " + MDQConstants.USE_LEGACY_WEB_METHOD_RULES_SUN + " set to true");
                    }
                    newSunBehavior = false;
                }
            }
        }
        //Now based on the outcome of LegacyWebmethod and sun property check, retrieve methods to expose.
        methodRetriever = newSunBehavior ? new PostRI216MethodRetrieverImpl(dbc, this) : new LegacyMethodRetrieverImpl(dbc, this);
        //set LegacyWebmethod Definition on MethodRetriever.
        methodRetriever.setLegacyWebMethod(legacyWebmethod);
        if(log.isDebugEnabled()) {
            if (newSunBehavior) {
                log.debug("getMethodRetriever: returning a PostRI216MethodRetrieverImpl");
            } else {
                log.debug("getMethodRetriever: returning a LegacyMethodRetrieverImpl");
            }
        }
        
        return methodRetriever;
    }
    
    /**
     * The user has indicated that they want to use the new Sun behavior (regardless) of which flag 
     * they were using.
     * This method determines whether we have the proper JDK version for using the new SUN behavior for
     * retrieving methods. We determine this by checking the version of WsGen.
     * @param propertyToSet
     * @return
     */
    private boolean isNewSunBehaviorSupported() {

        if (log.isDebugEnabled()) {
            log.debug("isNewSunBehavior: Validating that JDK version can be used");
        }
        
        boolean versionValid = false;

        try {            
            String wsGenVersion = WSToolingUtils.getWsGenVersion();
            
            versionValid = WSToolingUtils.isValidVersion(wsGenVersion);
            
            if (log.isDebugEnabled()) {
                log.debug("isNewSunBehavior: versionValid is: " +versionValid);
            }
            
            if (!versionValid) {
                if (log.isDebugEnabled()) {
                    log.debug("New Sun tooling behavior is not supported with this version of the JDK");
                }
            }

            // We don't want to affect existing systems, if anything goes
            // wrong just display
            // a warning and default to old behavior
        } catch (ClassNotFoundException e) {
            if (log.isDebugEnabled()) {
                log.debug(" Unable to determine WsGen version being used");
            }
        } catch (IOException ioex) {
            if (log.isDebugEnabled()) {
                log.debug(" Unable to determine WsGen version being used");
            }
        }
        
        return versionValid;
    }
    
    private String readLegacyWebMethodFlag () {
        String legacyWebmethod = null;
        try {
            legacyWebmethod = getSystemProperty(MDQConstants.USE_LEGACY_WEB_METHOD_RULES);
        } catch (PrivilegedActionException e) {
            // Swallow and continue
            if (log.isWarnEnabled()) {
                log.debug("Exception getting USE_LEGACY_WEB_METHOD_RULES system property: " +e.getException());
            }
        }
        //System property not set, so let return the manifest property.
        if(!WSToolingUtils.hasValue(legacyWebmethod)){
            if (log.isDebugEnabled()){
                log.debug("EndpointInterfaceDescriptionImpl: system property '"+MDQConstants.USE_LEGACY_WEB_METHOD_RULES + "' not set" );
            }
            ConfigurationContext configContext = getEndpointDescription().getServiceDescription().getAxisConfigContext();
            if(configContext!=null){
                if (log.isDebugEnabled()){
                    log.debug("EndpointInterfaceDescriptionImpl: Reading Manifest property '"+ MDQConstants.USE_MANIFEST_LEGACY_WEB_METHOD_RULES+"'" );
                }
                String param =(String)configContext.getProperty(MDQConstants.USE_MANIFEST_LEGACY_WEB_METHOD_RULES);
                if(param == null){
                    if (log.isDebugEnabled()){
                        log.debug("EndpointInterfaceDescriptionImpl: Manifest property '"+ MDQConstants.USE_MANIFEST_LEGACY_WEB_METHOD_RULES+ "' not set" );
                    }
                }else{
                    if (log.isDebugEnabled()){
                        log.debug("EndpointInterfaceDescriptionImpl: Manifest property '"+ MDQConstants.USE_MANIFEST_LEGACY_WEB_METHOD_RULES+ "' is set to"+param );
                    }
                    legacyWebmethod = param;
                }
            }else{
                if (log.isDebugEnabled()){
                    log.debug("EndpointInterfaceDescriptionImpl: Unable to Read Manifest property '"+ MDQConstants.USE_MANIFEST_LEGACY_WEB_METHOD_RULES+"'" );
                    log.debug("EndpointInterfaceDescriptionImpl: AxisConfigContext was null" );
                }
            }
        }else{
            if (log.isDebugEnabled()){
                log.debug("EndpointInterfaceDescriptionImpl: system property '"+MDQConstants.USE_LEGACY_WEB_METHOD_RULES + "' set" );
            }
        }
        return legacyWebmethod;
    }
    
    private static String getNewSunRulesFlag () {
        String newSunRulesFlag = null;
        try {
            newSunRulesFlag = getSystemProperty(MDQConstants.USE_LEGACY_WEB_METHOD_RULES_SUN);
        } catch (PrivilegedActionException e) {
            // Swallow and continue
            if (log.isWarnEnabled()) {
                log.debug("Exception getting USE_LEGACY_WEB_METHOD_RULES_SUN system property: " +e.getException());
            }
        }
        if(WSToolingUtils.hasValue(newSunRulesFlag)){
            if (log.isDebugEnabled()){
                log.debug("EndpointInterfaceDescriptionImpl: system property '"+MDQConstants.USE_LEGACY_WEB_METHOD_RULES_SUN + "' is set" );
                log.debug("MDQConstants.USE_LEGACY_WEB_METHOD_RULES_SUN ="+newSunRulesFlag);
            }            
        }else{
            if (log.isDebugEnabled()){
                log.debug("EndpointInterfaceDescriptionImpl: system property '"+MDQConstants.USE_LEGACY_WEB_METHOD_RULES_SUN + "' is not set" );
            }
        }
        return newSunRulesFlag;
    }
    
    private String readUsePostWebMethodRulesFlag() {
        String usePostMethodRules = null;
        try {
            usePostMethodRules = getSystemProperty(MDQConstants.USE_POST_WEB_METHOD_RULES);
        } catch (PrivilegedActionException e) {
            // Swallow and continue
            if (log.isWarnEnabled()) {
                log.debug("Exception getting USE_POST_WEB_METHOD_RULES system property: " + e.getException());
            }
        }
        if (WSToolingUtils.hasValue(usePostMethodRules)) {
            if (log.isDebugEnabled()) {
                log.debug("EndpointInterfaceDescriptionImpl: system property '" + MDQConstants.USE_POST_WEB_METHOD_RULES + "' is set with " + usePostMethodRules);                
            }
        } else {
            if (log.isDebugEnabled()) {
                log.debug("EndpointInterfaceDescriptionImpl: system property '" + MDQConstants.USE_POST_WEB_METHOD_RULES + "' is not set");
            }
        }
        return usePostMethodRules;
    }

    private static String getSystemProperty(final String propertyName) throws PrivilegedActionException {
        return AccessController.doPrivileged(new PrivilegedExceptionAction<String>() {
            public String run() {
                return System.getProperty(propertyName);
            }
        });
    }
}
