/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2008-2009 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */
package org.ow2.jonas.ws.axis2.jaxws.description.impl;

import java.io.InputStream;
import java.security.PrivilegedAction;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;

import javax.jws.HandlerChain;
import javax.jws.WebService;
import javax.wsdl.Binding;
import javax.wsdl.Definition;
import javax.wsdl.Port;
import javax.wsdl.extensions.ExtensibilityElement;
import javax.wsdl.extensions.http.HTTPBinding;
import javax.wsdl.extensions.soap.SOAPAddress;
import javax.wsdl.extensions.soap12.SOAP12Address;
import javax.wsdl.extensions.soap12.SOAP12Binding;
import javax.xml.namespace.QName;
import javax.xml.ws.BindingType;
import javax.xml.ws.Service;
import javax.xml.ws.ServiceMode;
import javax.xml.ws.WebServiceProvider;
import javax.xml.ws.handler.PortInfo;
import javax.xml.ws.soap.SOAPBinding;

import org.apache.axis2.AxisFault;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.deployment.DeploymentException;
import org.apache.axis2.description.AxisService;
import org.apache.axis2.description.OutInAxisOperation;
import org.apache.axis2.description.OutOnlyAxisOperation;
import org.apache.axis2.description.Parameter;
import org.apache.axis2.description.WSDL11ToAllAxisServicesBuilder;
import org.apache.axis2.description.WSDL11ToAxisServiceBuilder;
import org.apache.axis2.engine.AxisConfiguration;
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.EndpointDescriptionJava;
import org.apache.axis2.jaxws.description.EndpointDescriptionWSDL;
import org.apache.axis2.jaxws.description.EndpointInterfaceDescription;
import org.apache.axis2.jaxws.description.ServiceDescription;
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.WsdlComposite;
import org.apache.axis2.jaxws.description.impl.URIResolverImpl;
import org.apache.axis2.jaxws.description.xml.handler.HandlerChainsType;
import org.apache.axis2.jaxws.i18n.Messages;
import org.apache.axis2.jaxws.util.ClassLoaderUtils;
import org.apache.axis2.jaxws.util.WSDL4JWrapper;
import org.apache.axis2.jaxws.util.WSDLWrapper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/** @see ../EndpointDescription */
/*
 * TODO: EndpointDescription should be created via AxisService objects and not directly from WSDL
 * IMPORTANT NOTE: Axis2 currently only supports 1 service and 1 port under that service.  When that is
 * fixed, that will probably have an impact on this class.  In particular, I think this should be created
 * somehow from an AxisService/AxisPort combination, and not directly from the WSDL.
 */
class EndpointDescriptionImpl
        implements EndpointDescription, EndpointDescriptionJava, EndpointDescriptionWSDL {

    private ServiceDescriptionImpl parentServiceDescription;
    private AxisService axisService;

    private QName portQName;
    private QName serviceQName;

    // Corresponds to a port that was added dynamically via addPort and is not declared (either in WSDL or annotations)
    private boolean isDynamicPort;

    // If the WSDL is fully specified, we could build the AxisService from the WSDL
    private boolean isAxisServiceBuiltFromWSDL;

    private String serviceImplName;    //class name of the service impl or SEI

    // Note that an EndpointInterfaceDescription will ONLY be set for an Endpoint-based implementation;
    // it will NOT be set for a Provider-based implementation
    private EndpointInterfaceDescription endpointInterfaceDescription;

    // This can be an SEI (on the client or server) or a Service implentation (server only)
    // Note that for clients that are Dispatch, this will be null.  Also note that a client that was initially
    // dispatch (sei = null) could later do a getPort(sei), at which time the original EndpointDescription will be
    // updated with the SEI information.
    private Class implOrSEIClass;

    //On Client side, there should be One ServiceClient instance per AxisSerivce
    private ServiceClient serviceClient = null;

    //This is the base WebService or WebServiceProvider that we are processing
    DescriptionBuilderComposite composite = null;

    // Set of packages that are needed to marshal/unmashal data (used to set JAXBContext)
    TreeSet<String> packages = null;

    // The JAX-WS Handler port information corresponding to this endpoint
    private PortInfo portInfo;

    private String clientBindingID;
    // The effective endpoint address.  It could be set by the client or come from the WSDL SOAP address
    private String endpointAddress;
    // The endpoint address from the WSDL soap:address extensibility element if present.
    private String wsdlSOAPAddress;

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

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

    // ANNOTATION: @WebService and @WebServiceProvider
    // Only one of these two annotations will be set; they are mutually exclusive
    private WebService webServiceAnnotation;
    private WebServiceProvider webServiceProviderAnnotation;

    //ANNOTATION: @HandlerChain
    private HandlerChain handlerChainAnnotation;
    private HandlerChainsType handlerChainsType;

    // Information common to both WebService and WebServiceProvider annotations
    private String annotation_WsdlLocation;
    private String annotation_ServiceName;
    private String annotation_PortName;
    private String annotation_TargetNamespace;

    // Information only set on WebService annotation
    // ANNOTATION: @WebService
    private String webService_EndpointInterface;
    private String webService_Name;

    // ANNOTATION: @ServiceMode
    // Note this is only valid on a Provider-based endpoint
    private ServiceMode serviceModeAnnotation;
    private Service.Mode serviceModeValue;
    // Default ServiceMode.value per JAXWS Spec 7.1 "javax.xml.ServiceMode" pg 79
    public static final javax.xml.ws.Service.Mode ServiceMode_DEFAULT =
            javax.xml.ws.Service.Mode.PAYLOAD;

    // ANNOTATION: @BindingType
    private BindingType bindingTypeAnnotation;
    private String bindingTypeValue;
    // Default BindingType.value per JAXWS Spec Sec 7.8 "javax.xml.ws.BindingType" pg 83
    // and Sec 1.4 "SOAP Transport and Transfer Bindings" pg 119
    public static final String BindingType_DEFAULT =
            javax.xml.ws.soap.SOAPBinding.SOAP11HTTP_BINDING;

    /**
     * Create an EndpointDescription based on the WSDL port.  Note that per the JAX-WS Spec (Final
     * Release, 4/19/2006 Section 4.2.3 Proxies, page 55)the "namespace component of the port is the
     * target namespace of the WSDL definition document". Note this is currently only used on the
     * client-side (this may change).
     *
     * @param theClass The SEI or Impl class.  This will be NULL for Dispatch clients since they
     *                 don't use an SEI
     */
    EndpointDescriptionImpl(final Class theClass, final QName portName, final ServiceDescriptionImpl parent) {
        this(theClass, portName, false, parent);
    }

    EndpointDescriptionImpl(final Class theClass, final QName portName, final boolean dynamicPort,
                            final ServiceDescriptionImpl parent) {
        // TODO: This and the other constructor will (eventually) take the same args, so the logic needs to be combined
        // TODO: If there is WSDL, could compare the namespace of the defn against the portQName.namespace
        this.parentServiceDescription = parent;
        this.implOrSEIClass = theClass;
        // REVIEW: setting these should probably be done in the getters!  It needs to be done before we try to select a
        //         port to use if one wasn't specified because we'll try to get to the annotations to get the PortType
        if (this.implOrSEIClass != null) {
            webServiceAnnotation = (WebService)implOrSEIClass.getAnnotation(WebService.class);
            webServiceProviderAnnotation =
                    (WebServiceProvider)implOrSEIClass.getAnnotation(WebServiceProvider.class);
        }
        this.isDynamicPort = dynamicPort;
        if (DescriptionUtils.isEmpty(portName)) {
            // If the port name is null, then per JAX-WS 2.0 spec p. 55, the runtime is responsible for selecting the port.
            this.portQName = selectPortToUse();
        } else {
            this.portQName = portName;
        }
        // At this point, there must be a port QName set, either as passed in, or determined from the WSDL and/or annotations.
        // If not, that is an error.
        if (this.portQName == null) {
            if (log.isDebugEnabled()) {
                log.debug("PortQName was null and could not be determined by runtime.  Class: " +
                        theClass + "; ServiceDescription: " + parent);
            }
            throw ExceptionFactory.makeWebServiceException(
                    "EndpointDescription: portQName could not be determined for class " + theClass);
        }

        // TODO: Refactor this with the consideration of no WSDL/Generic Service/Annotated SEI
        setupAxisService();
        addToAxisService();

        buildDescriptionHierachy();
        addAnonymousAxisOperations();

        // This will set the serviceClient field after adding the AxisService to the AxisConfig
        getServiceClient();
        // Give the configuration builder a chance to finalize configuration for this service
        try {
            getServiceDescriptionImpl().getClientConfigurationFactory()
                    .completeAxis2Configuration(axisService);
        } catch (DeploymentException e) {
            // TODO RAS & NLS
            if (log.isDebugEnabled()) {
                log.debug(
                        "Caught DeploymentException attempting to complete configuration on AxisService: "
                                + axisService + " for ServiceDesription: " + parent, e);
            }
            throw ExceptionFactory.makeWebServiceException(
                    "Unable to complete configuration due to exception " + e, e);
        } catch (Exception e) {
            // TODO RAS & NLS
            if (log.isDebugEnabled()) {
                log.debug("Caught Exception attempting to complete configuration on AxisService: "
                        + axisService + " for ServiceDesription: " + parent, e);
            }
            throw ExceptionFactory.makeWebServiceException(
                    "Unable to complete configuration due to exception " + e, e);
        }
    }

    /**
     * Create an EndpointDescription based on the DescriptionBuilderComposite. Note that per the
     * JAX-WS Spec (Final Release, 4/19/2006 Section 4.2.3 Proxies, page 55)the "namespace component
     * of the port is the target namespace of the WSDL definition document".
     *
     * @param theClass The SEI or Impl class.  This will be NULL for Dispatch clients since they
     *                 don't use an SEI
     */
    EndpointDescriptionImpl(final ServiceDescriptionImpl parent, final String serviceImplName) {

        // TODO: This and the other constructor will (eventually) take the same args, so the logic needs to be combined
        // TODO: If there is WSDL, could compare the namespace of the defn against the portQName.namespace
        this.parentServiceDescription = parent;
        this.serviceImplName = serviceImplName;
        this.implOrSEIClass = null;

        composite = getServiceDescriptionImpl().getDescriptionBuilderComposite();
        if (composite == null) {
            throw ExceptionFactory.makeWebServiceException(
                    "EndpointDescription.EndpointDescription: parents DBC is null");
        }

        //Set the base level of annotation that we are processing...currently
        // a 'WebService' or a 'WebServiceProvider'
        if (composite.getWebServiceAnnot() != null) {
            webServiceAnnotation = composite.getWebServiceAnnot();
        } else {
            webServiceProviderAnnotation = composite.getWebServiceProviderAnnot();
        }

        // REVIEW: Maybe this should be an error if the name has already been set and it doesn't match
        // Note that on the client side, the service QN should be set; on the server side it will not be.
        if (DescriptionUtils.isEmpty(getServiceDescription().getServiceQName())) {
            getServiceDescriptionImpl().setServiceQName(getServiceQName());
        }
        //Call the getter to insure the qualified port name is set.
        getPortQName();

        // TODO: Refactor this with the consideration of no WSDL/Generic Service/Annotated SEI
        setupAxisServiceFromDBL();
        addToAxisService();    //Add a reference to this EndpointDescription to the AxisService

        //TODO: Need to remove operations from AxisService that have 'exclude = true
        //      then call 'validateOperations' to verify that WSDL and AxisService match up,
        //      Remember that this will only happen when we generate an AxisService from existing
        //		WSDL and then need to perform further processing because we have annotations as well
        //		If there is no WSDL, we would never process the Method to begin with.

        buildDescriptionHierachy();

        WsdlComposite wsdlComposite = null;

        String bindingType = getBindingType();

        boolean isSOAP11 =
                (bindingType.equals(javax.xml.ws.soap.SOAPBinding.SOAP11HTTP_BINDING) ||
                        bindingType.equals(javax.xml.ws.soap.SOAPBinding.SOAP11HTTP_MTOM_BINDING))
                        ? true : false;


        // Determine if we need to generate WSDL
        // First, make sure that this is only a SOAP 1.1 based binding, per JAXWS spec. we cannot
        // generate WSDL if the binding type is not SOAP 1.1 based.
        // Then, assuming the composite does not contain a
        // Wsdl Definition, go ahead and generate it
        // REVIEW: I think this should this be isSOAP11 so the generators are only called for
        //         SOAP11; i.e. NOT for SOAP12 or XML/HTTP bindings.
        if (isSOAP11){
            if (
                    (isEndpointBased() &&
                            DescriptionUtils.isEmpty(getAnnoWebServiceEndpointInterface()))
                            ||
                            (!isEndpointBased())
                    ) {
                //This is either an implicit SEI, or a WebService Provider

                wsdlComposite = generateWSDL(composite);

            } else if (isEndpointBased()) {
                //This impl class specifies an SEI...this is a special case. There is a bug
                //in the tooling that allows for the wsdllocation to be specifed on either the
                //impl. class, or the SEI, or both. So, we need to look for the wsdl as follows:
                //			1. If the Wsdl exists on the SEI, then check for it on the impl.
                //			2. If it is not found in either location, in that order, then generate

                DescriptionBuilderComposite seic =
                        getServiceDescriptionImpl().getDBCMap()
                                .get(composite.getWebServiceAnnot().endpointInterface());

                //Only generate WSDL if a definition doesn't already exist
                if (seic.getWsdlDefinition() == null) {
                    wsdlComposite = generateWSDL(composite);
                }
            }

        } else if (composite.getWsdlDefinition() == null) {
            //This is a SOAP12 binding that does not contain a WSDL definition, log a WARNING
            log.warn("This implementation does not contain a WSDL definition and is not a SOAP 1.1 based binding. "
                    + "Per JAXWS spec. - a WSDL definition cannot be generated for this implementation. Name: "
                    + composite.getClassName());
        }

        if (isSOAP11){

            //Save the WSDL Location and the WsdlDefinition, value depends on whether wsdl was generated
            Parameter wsdlLocationParameter = new Parameter();
            wsdlLocationParameter.setName(MDQConstants.WSDL_LOCATION);

            Parameter wsdlDefParameter = new Parameter();
            wsdlDefParameter.setName(MDQConstants.WSDL_DEFINITION);

            Parameter wsdlCompositeParameter = new Parameter();
            wsdlCompositeParameter.setName(MDQConstants.WSDL_COMPOSITE);

            if (wsdlComposite != null) {

                //We have a wsdl composite, so set these values for the generated wsdl
                wsdlCompositeParameter.setValue(wsdlComposite);
                wsdlLocationParameter.setValue(wsdlComposite.getWsdlFileName());
                wsdlDefParameter.setValue(
                        getServiceDescriptionImpl().getGeneratedWsdlWrapper().getDefinition());
            } else if (getServiceDescriptionImpl().getWSDLWrapper() != null) {
                //No wsdl composite because wsdl already exists
                wsdlLocationParameter.setValue(getAnnoWebServiceWSDLLocation());
                wsdlDefParameter.setValue(getServiceDescriptionImpl().getWSDLWrapper().getDefinition());
            } else {
                //There is no wsdl composite and there is NOT a wsdl definition
                wsdlLocationParameter.setValue(null);
                wsdlDefParameter.setValue(null);

            }

            try {
                if (wsdlComposite != null) {
                    axisService.addParameter(wsdlCompositeParameter);
                }
                axisService.addParameter(wsdlDefParameter);
                axisService.addParameter(wsdlLocationParameter);
            } catch (Exception e) {
                throw ExceptionFactory.makeWebServiceException(
                        "EndpointDescription: Unable to add parameters to AxisService");
            }
        }
        else {
            // Need to account for SOAP 1.2 WSDL when supplied with application
            Parameter wsdlDefParameter = new Parameter();
            wsdlDefParameter.setName(MDQConstants.WSDL_DEFINITION);
            Parameter wsdlLocationParameter = new Parameter();
            wsdlLocationParameter.setName(MDQConstants.WSDL_LOCATION);
            if (getServiceDescriptionImpl().getWSDLWrapper() != null) {
                wsdlLocationParameter.setValue(getAnnoWebServiceWSDLLocation());
                wsdlDefParameter.setValue(getServiceDescriptionImpl().getWSDLWrapper()
                    .getDefinition());
            }
            // No WSDL supplied and we do not generate for non-SOAP 1.1/HTTP
            // endpoints
            else {
                wsdlLocationParameter.setValue(null);
                wsdlDefParameter.setValue(null);
            }
            try {
                axisService.addParameter(wsdlDefParameter);
                axisService.addParameter(wsdlLocationParameter);

            } catch (Exception e) {
                throw ExceptionFactory
                    .makeWebServiceException("EndpointDescription: Unable to add parameters to AxisService");
            }
        }
    }

    /**
     * Create from an annotated implementation or SEI class. Note this is
     * currently used only on the server-side (this probably won't change).
     *
     * @param theClass An implemntation or SEI class
     * @param portName May be null; if so the annotation is used
     * @param parent
     */
    EndpointDescriptionImpl(final Class theClass, final QName portName, final AxisService axisService,
                            final ServiceDescriptionImpl parent) {
        this.parentServiceDescription = parent;
        this.portQName = portName;
        this.implOrSEIClass = theClass;
        this.axisService = axisService;

        addToAxisService();

        buildEndpointDescriptionFromAnnotations();

        // The anonymous AxisOperations are currently NOT added here.  The reason
        // is that (for now) this is a SERVER-SIDE code path, and the anonymous operations
        // are only needed on the client side.
    }

    private void addToAxisService() {
        // Add a reference to this EndpointDescription object to the AxisService
        if (axisService != null) {
            Parameter parameter = new Parameter();
            parameter.setName(EndpointDescription.AXIS_SERVICE_PARAMETER);
            parameter.setValue(this);
            // TODO: What to do if AxisFault
            try {
                axisService.addParameter(parameter);
            } catch (AxisFault e) {
                // TODO: Throwing wrong exception
                e.printStackTrace();
                throw new UnsupportedOperationException("Can't add AxisService param: " + e);
            }
        }
    }

    private void buildEndpointDescriptionFromAnnotations() {
        // TODO: The comments below are not quite correct; this method is used on BOTH the
        //       client and server.  On the client the class is always an SEI.  On the server it
        //		 is always a service impl which may be a provider or endpoint based;
        //		 endpoint based may reference an SEI class

        // The Service Implementation class could be either Provider-based or Endpoint-based.  The
        // annotations that are present are similar but different.  Conformance requirements
        // per JAX-WS
        // - A Provider based implementation MUST carry the @WebServiceProvider annotation
        //   per section 5.1 javax.xml.ws.Provider on page 63
        // - An Endpoint based implementation MUST carry the @WebService annotation per JSR-181
        //   (reference TBD) and JAX-WS (reference TBD)
        // - An Endpoint based implementation @WebService annotation MAY reference an endpoint
        //   interface
        // - The @WebService and @WebServiceProvider annotations can not appear in the same class per
        //   JAX-WS section 7.7 on page 82.

        // Verify that one (and only one) of the required annotations is present.
        // TODO: Add tests to verify this error checking


        if (!getServiceDescriptionImpl().isDBCMap()) {

            webServiceAnnotation = (WebService)implOrSEIClass.getAnnotation(WebService.class);
            webServiceProviderAnnotation =
                    (WebServiceProvider)implOrSEIClass.getAnnotation(WebServiceProvider.class);

            if (webServiceAnnotation == null && webServiceProviderAnnotation == null) {
                // TODO: NLS
                throw ExceptionFactory.makeWebServiceException(
                        "Either WebService or WebServiceProvider annotation must be present on " +
                                implOrSEIClass);
            } else if (webServiceAnnotation != null && webServiceProviderAnnotation != null) {
                // TODO: NLS
                throw ExceptionFactory.makeWebServiceException(
                        "Both WebService or WebServiceProvider annotations cannot be presenton " +
                                implOrSEIClass);
            }
        }
        // If portName was specified, set it.  Otherwise, we will get it from the appropriate
        // annotation when the getter is called.
        // TODO: If the portName is specified, should we verify it against the annotation?
        // TODO: Add tests: null portName, !null portName, portName != annotation value
        // TODO: Get portName from annotation if it is null.

        // If this is an Endpoint-based service implementation (i.e. not a
        // Provider-based one), then create the EndpointInterfaceDescription to contain
        // the operations on the endpoint.  Provider-based endpoints don't have operations
        // associated with them, so they don't have an EndpointInterfaceDescription.
        if (webServiceAnnotation != null) {
            // If this impl class references an SEI, then use that SEI to create the EndpointInterfaceDesc.
            // TODO: Add support for service impl endpoints that don't reference an SEI; remember
            //       that this is also called with just an SEI interface from svcDesc.updateWithSEI()
            String seiClassName = getAnnoWebServiceEndpointInterface();

            if (!getServiceDescriptionImpl().isDBCMap()) {
                Class seiClass = null;
                if (DescriptionUtils.isEmpty(seiClassName)) {
                    // For now, just build the EndpointInterfaceDesc based on the class itself.
                    // TODO: The EID ctor doesn't correctly handle anything but an SEI at this
                    //       point; e.g. it doesn't publish the correct methods of just an impl.
                    seiClass = implOrSEIClass;
                } else {
                    try {
                        // TODO: Using Class forName() is probably not the best long-term way to get the SEI class from the annotation
                        seiClass = ClassLoaderUtils.forName(seiClassName, false,
                                                            ClassLoaderUtils.getContextClassLoader());
                        // Catch Throwable as ClassLoader can throw an NoClassDefFoundError that
                        // does not extend Exception, so lets catch everything that extends Throwable
                        // rather than just Exception.
                    } catch (Throwable e) {
                        // TODO: Throwing wrong exception
                        e.printStackTrace();
                        throw new UnsupportedOperationException("Can't create SEI class: " + e);
                    }
                }
                endpointInterfaceDescription = new EndpointInterfaceDescriptionImpl(seiClass, this);
            } else {
                //TODO: Determine if we need logic here to determine implied SEI or not. This logic
                //		may be handled by EndpointInterfaceDescription

                if (DescriptionUtils.isEmpty(getAnnoWebServiceEndpointInterface())) {

                    //TODO: Build the EndpointInterfaceDesc based on the class itself
                    endpointInterfaceDescription =
                            new EndpointInterfaceDescriptionImpl(composite, true, this);

                } else {
                    //Otherwise, build the EID based on the SEI composite
                    endpointInterfaceDescription = new EndpointInterfaceDescriptionImpl(
                            getServiceDescriptionImpl().getDBCMap().get(seiClassName),
                            false,
                            this);
                }
            }
        } else {
            if (log.isDebugEnabled()) {
                log.debug("WebServiceProvider without WSDL encountered");
            }
            // REVIEW: Currently this is only supported for HTTP Bindings; SOAPBindings
            //     for providers currently require that there be WSDL.
            String bindingType = getBindingType();
            if (javax.xml.ws.http.HTTPBinding.HTTP_BINDING.equals(bindingType)) {
                endpointInterfaceDescription = new EndpointInterfaceDescriptionImpl(composite, this);
            }
        }
    }

    public QName getPortQName() {
        // REVIEW: Implement WSDL/Annotation merge? May be OK as is; not sure how would know WHICH port Qname to get out of the WSDL if
        //       we didn't use annotations.
        if (portQName == null) {
            // The name was not set by the constructors, so get it from the
            // appropriate annotation.
            String name = getAnnoWebServicePortName();
            String tns = getAnnoWebServiceTargetNamespace();

            // TODO: Check for name &/| tns null or empty string and add tests for same
            portQName = new QName(tns, name);
        }
        return portQName;
    }

    public QName getServiceQName() {
        if (serviceQName == null) {
            // If the service name has been set on the Service, use that.  Otherwise
            // get the name off the annotations
            QName serviceDescQName = getServiceDescription().getServiceQName();
            if (!DescriptionUtils.isEmpty(serviceDescQName)) {
                serviceQName = serviceDescQName;
            } else {
                String localPart = getAnnoWebServiceServiceName();
                String tns = getAnnoWebServiceTargetNamespace();
                serviceQName = new QName(tns, localPart);
            }
        }
        return serviceQName;
    }

    public ServiceDescription getServiceDescription() {
        return parentServiceDescription;
    }

    ServiceDescriptionImpl getServiceDescriptionImpl() {
        return parentServiceDescription;
    }

    public EndpointInterfaceDescription getEndpointInterfaceDescription() {
        return endpointInterfaceDescription;
    }

    public AxisService getAxisService() {
        return axisService;
    }

    boolean isDynamicPort() {
        return isDynamicPort;
    }

    void updateWithSEI(final Class sei) {
        // Updating with an SEI is only valid for declared ports; it is not valid for dynamic ports.
        if (isDynamicPort()) {
            // TODO: RAS and NLS
            throw ExceptionFactory.makeWebServiceException(
                    "Can not update an SEI on a dynamic port.  PortQName:" + portQName);
        }
        if (sei == null) {
            // TODO: RAS and NLS
            throw ExceptionFactory.makeWebServiceException(
                    "EndpointDescription.updateWithSEI was passed a null SEI.  PortQName:" +
                            portQName);
        }

        if (endpointInterfaceDescription != null) {
            // The EndpointInterfaceDescription was created previously based on the port declaration (i.e. WSDL)
            // so update that with information from the SEI annotations
            ((EndpointInterfaceDescriptionImpl)endpointInterfaceDescription).updateWithSEI(sei);
        } else {
            // An EndpointInterfaceDescription does not exist yet.  This currently happens in the case where there is
            // NO WSDL provided and a Dispatch client is created for prior to a getPort being done for that port.
            // There was no WSDL to create the EndpointInterfaceDescription from and there was no annotated SEI to
            // use at that time.  Now we have an annotated SEI, so create the EndpointInterfaceDescription now.
            endpointInterfaceDescription = new EndpointInterfaceDescriptionImpl(sei, this);
        }
        return;
    }

    private void setupAxisService() {
        // TODO: Need to use MetaDataQuery validator to merge WSDL (if any) and annotations (if any)
        // Build up the AxisService.  Note that if this is a dynamic port, then we don't use the
        // WSDL to build up the AxisService since the port added to the Service by the client is not
        // one that will be present in the WSDL.  A null class passed in as the SEI indicates this
        // is a dispatch client.
        if (!isDynamicPort && getServiceDescriptionImpl().getWSDLWrapper() != null) {
            isAxisServiceBuiltFromWSDL = buildAxisServiceFromWSDL();
        } else {
            buildAxisServiceFromAnnotations();
        }

        if (axisService == null) {
            // TODO: RAS & NLS
            throw ExceptionFactory.makeWebServiceException("Unable to create AxisService for "
                    + createAxisServiceName());
        }

        // Save the Service QName as a parameter.
        Parameter serviceNameParameter = new Parameter();
        serviceNameParameter.setName(WSDL11ToAllAxisServicesBuilder.WSDL_SERVICE_QNAME);
        serviceNameParameter.setValue(getServiceDescription().getServiceQName());

        // Save the Port name.  Note: Axis does not expect a QName since the namespace for the port is the ns from the WSDL definition
        Parameter portParameter = new Parameter();
        portParameter.setName(WSDL11ToAllAxisServicesBuilder.WSDL_PORT);
        portParameter.setValue(portQName.getLocalPart());

        try {
            axisService.addParameter(serviceNameParameter);
            axisService.addParameter(portParameter);
        }
        catch (AxisFault e) {
            // TODO RAS
            e.printStackTrace();
        }
    }

    /*
     * This setups and builds the AxisService using only the DescriptionBuilderCompositeList
     *
     */
    private void setupAxisServiceFromDBL() {
        // TODO: Need to use MetaDataQuery validator to merge WSDL (if any) and annotations (if any)
        // Build up the AxisService.  Note that if this is a dispatch client, then we don't use the
        // WSDL to build up the AxisService since the port added to the Service by the client is not
        // one that will be present in the WSDL.  A null class passed in as the SEI indicates this
        // is a dispatch client.

        // If WSDL is present, it may be full or only partial.  If we can create the AxisService from
        // the WSDL, that WSDL is fully specified.  Otherwise, it is "partial WSDL".  In that case
        // we use annotaions to build the AxisService
        isAxisServiceBuiltFromWSDL = false;
        if (getServiceDescriptionImpl().getWSDLWrapper() != null) {
            isAxisServiceBuiltFromWSDL = buildAxisServiceFromWSDL();

        }

        if (!isAxisServiceBuiltFromWSDL) {
            //generateWSDL(composite);
            buildAxisServiceFromAnnotations();
        }

        if (axisService == null) {
            // TODO: RAS & NLS
            throw ExceptionFactory.makeWebServiceException("Unable to create AxisService for "
                    + createAxisServiceName());
        }

        //Save the Port Type name
        Parameter portTypeNameParameter = new Parameter();
        portTypeNameParameter.setName(MDQConstants.WSDL_PORTTYPE_NAME);
        portTypeNameParameter.setValue(getName());

        // Save the Service QName as a parameter.
        Parameter serviceNameParameter = new Parameter();
        serviceNameParameter.setName(MDQConstants.WSDL_SERVICE_QNAME);
        serviceNameParameter.setValue(getServiceDescription().getServiceQName());

        // Save the Port name.  Note: Axis does not expect a QName since the namespace
        //   for the port is the ns from the WSDL definition
        Parameter portParameter = new Parameter();
        portParameter.setName(MDQConstants.WSDL_PORT);
        portParameter.setValue(getPortQName().getLocalPart());

        //Save the fully qualified class name for the serviceImpl
        Parameter serviceClassNameParameter = new Parameter();
        serviceClassNameParameter.setName(MDQConstants.SERVICE_CLASS);
        serviceClassNameParameter
                .setValue(DescriptionUtils.javifyClassName(composite.getClassName()));

        try {
            axisService.addParameter(portTypeNameParameter);
            axisService.addParameter(serviceNameParameter);
            axisService.addParameter(portParameter);
            axisService.addParameter(serviceClassNameParameter);
        }
        catch (AxisFault e) {
            // TODO RAS
            e.printStackTrace();
        }
    }

    private boolean buildAxisServiceFromWSDL() {
        boolean isBuiltFromWSDL = false;
        try {

            // TODO: Change this to use WSDLToAxisServiceBuilder superclass
            // Note that the axis service builder takes only the localpart of the port qname.
            // TODO:: This should check that the namespace of the definition matches the namespace of the portQName per JAXRPC spec

            WSDLWrapper wrapper = getServiceDescriptionImpl().getWSDLWrapper();
            WSDL11ToAxisServiceBuilder serviceBuilder =
                    new WSDL11ToAxisServiceBuilder(
                            wrapper.getDefinition(),
                            getServiceDescription().getServiceQName(),
                            getPortQName().getLocalPart());

            //TODO: Temporary, please change the following log.info to log.debug
            log.info("Building AxisService from wsdl: " + wrapper.getWSDLLocation());

            if (getServiceDescriptionImpl().isDBCMap()) {
                //this.class.getClass().getClassLoader();
                URIResolverImpl uriResolver =
                        new URIResolverImpl(composite.getClassLoader());
                serviceBuilder.setCustomResolver(uriResolver);
            } else {
                ClassLoader classLoader = (ClassLoader)AccessController.doPrivileged(new
                        PrivilegedAction() {
                            public Object run() {
                                return Thread.currentThread().getContextClassLoader();
                            }
                        });
                URIResolverImpl uriResolver = new URIResolverImpl(classLoader);
                serviceBuilder.setCustomResolver(uriResolver);
            }

            // TODO: Currently this only builds the client-side AxisService;
            // it needs to do client and server somehow.
            // Patterned after AxisService.createClientSideAxisService
            if (getServiceDescriptionImpl().isServerSide()) {
                serviceBuilder.setServerSide(true);
            } else {
                serviceBuilder.setServerSide(false);
            }

            axisService = serviceBuilder.populateService();
            axisService.setName(createAxisServiceName());
            isBuiltFromWSDL = true;

        } catch (AxisFault e) {
            // REVIEW: If we couldn't use the WSDL, should we fail instead of continuing to process using annotations?
            //         Note that if we choose to fail, we need to distinguish the partial WSDL case (which can not fail)
            // TODO: RAS/NLS  Need to update the message with the appropriate inserts
//    		log.warn(Messages.getMessage("warnAxisFault", e.toString()), e);
            String wsdlLocation = (getServiceDescriptionImpl().getWSDLLocation() != null) ?
                    getServiceDescriptionImpl().getWSDLLocation().toString() : null;
            String implClassName = null;
            if (getServiceDescriptionImpl().isDBCMap()) {
                implClassName = composite.getClassName();
            } else {
                implClassName = (implOrSEIClass != null) ? implOrSEIClass.getName() : null;
            }
            log.warn(
                    "The WSDL file could not be used due to an exception.  The WSDL will be ignored and annotations will be used.  Implementaiton class: "
                            + implClassName + "; WSDL Location: " + wsdlLocation + "; Exception: " +
                            e.toString(), e);
            isBuiltFromWSDL = false;
            return isBuiltFromWSDL;
        }
        return isBuiltFromWSDL;
    }

    private void buildAxisServiceFromAnnotations() {
        // TODO: Refactor this to create from annotations.
        String serviceName = null;
        if (portQName != null) {
            serviceName = createAxisServiceName();
        } else {
            // REVIEW: Can the portQName ever be null?
            // Make this service name unique.  The Axis2 engine assumes that a service it can not find is a client-side service.
            serviceName = ServiceClient.ANON_SERVICE + this.hashCode() + System.currentTimeMillis();
        }
        axisService = new AxisService(serviceName);

        //TODO: Set other things on AxisService here, this function may have to be
        //      moved to after we create all the AxisOperations
    }

    private void buildDescriptionHierachy() {
        // Build up the Description Hierachy.  Note that if this is a dynamic port, then we don't use the
        // WSDL to build up the hierachy since the port added to the Service by the client is not
        // one that will be present in the WSDL.

        //First, check to see if we can build this with the DBC List
        //TODO: When MDQ input is the only possible input, then we can remove the check for
        //      the DBC list, until that time the code in here may appear somewhat redundant
        if (getServiceDescriptionImpl().isDBCMap()) {
            if (!isDynamicPort && isWSDLFullySpecified()) {
                buildEndpointDescriptionFromWSDL();
            } else {
                buildEndpointDescriptionFromAnnotations();
            }
        } else {
            //Still processing annotations from the class
            // This path was not updated
            if (!isDynamicPort && isWSDLFullySpecified()) {
                buildEndpointDescriptionFromWSDL();
            } else if (implOrSEIClass != null) {
                // Create the rest of the description hierachy from annotations on the class.
                // If there is no SEI class, then this is a Distpach case, and we currently
                // don't create the rest of the description hierachy (since it is not an SEI and thus
                // not operation-based client.
                buildEndpointDescriptionFromAnnotations();
            }
        }
    }

    private void buildEndpointDescriptionFromWSDL() {
        Definition wsdlDefinition = getServiceDescriptionImpl().getWSDLWrapper().getDefinition();
        javax.wsdl.Service wsdlService =
                wsdlDefinition.getService(getServiceDescription().getServiceQName());
        if (wsdlService == null) {
            throw ExceptionFactory.makeWebServiceException(
                    Messages.getMessage("serviceDescErr2", createAxisServiceName()));
        }

        Map wsdlPorts = wsdlService.getPorts();
        boolean wsdlPortFound = false;
        if (wsdlPorts != null && wsdlPorts.size() > 0) {
            Iterator wsdlPortIterator = wsdlPorts.values().iterator();

            while (wsdlPortIterator.hasNext() && !wsdlPortFound) {
                Port wsdlPort = (Port)wsdlPortIterator.next();
                // Note the namespace is not included on the WSDL Port.
                if (wsdlPort.getName().equals(portQName.getLocalPart())) {

                    // Build the EndpointInterface based on the specified SEI if there is one
                    // or on the service impl class (i.e. an implicit SEI).
                    if (getServiceDescriptionImpl().isDBCMap()) {
                        String seiClassName = getAnnoWebServiceEndpointInterface();
                        if (DescriptionUtils.isEmpty(seiClassName)) {
                            // No SEI specified, so use the service impl as an implicit SEI
                            endpointInterfaceDescription =
                                    new EndpointInterfaceDescriptionImpl(composite, true, this);
                        } else {
                            // Otherwise, build the EID based on the SEI composite
                            endpointInterfaceDescription = new EndpointInterfaceDescriptionImpl(
                                    getServiceDescriptionImpl().getDBCMap().get(seiClassName),
                                    false,
                                    this);
                        }

                    } else {
                        // Create the Endpoint Interface Description based on the WSDL.
                        endpointInterfaceDescription = new EndpointInterfaceDescriptionImpl(this);

                        // Update the EndpointInterfaceDescription created with WSDL with information from the
                        // annotations in the SEI
                        ((EndpointInterfaceDescriptionImpl)endpointInterfaceDescription)
                                .updateWithSEI(implOrSEIClass);
                    }
                    wsdlPortFound = true;
                }
            }
        }

        if (!wsdlPortFound) {
            // TODO: NLS and RAS
            throw ExceptionFactory.makeWebServiceException(
                    "WSDL Port not found for port " + portQName.getLocalPart());
        }
    }

    /**
     * Adds the anonymous axis operations to the AxisService.  Note that this is only needed on the
     * client side, and they are currently used in two cases (1) For Dispatch clients (which don't
     * use SEIs and thus don't use operations) (2) TEMPORARLIY for Services created without WSDL
     * (and thus which have no AxisOperations created) See the AxisInvocationController invoke
     * methods for more details.
     * <p/>
     * Based on ServiceClient.createAnonymouService
     */
    private void addAnonymousAxisOperations() {
        if (axisService != null) {
            OutOnlyAxisOperation outOnlyOperation =
                    new OutOnlyAxisOperation(ServiceClient.ANON_OUT_ONLY_OP);
            axisService.addOperation(outOnlyOperation);

            OutInAxisOperation outInOperation =
                    new OutInAxisOperation(ServiceClient.ANON_OUT_IN_OP);
            axisService.addOperation(outInOperation);
        }
    }

    public ServiceClient getServiceClient() {
        try {
            if (serviceClient == null) {
                ConfigurationContext configCtx = getServiceDescription().getAxisConfigContext();
                AxisService axisSvc = getAxisService();
                AxisConfiguration axisCfg = configCtx.getAxisConfiguration();
                if (axisCfg.getService(axisSvc.getName()) != null) {
                    axisSvc.setName(axisSvc.getName() + this.hashCode());
                }
                serviceClient = new ServiceClient(configCtx, axisSvc);
            }
        } catch (AxisFault e) {
            throw ExceptionFactory.makeWebServiceException(
                    Messages.getMessage("serviceClientCreateError"), e);
        }
        return serviceClient;
    }

    //This should eventually be deprecated in favor 'createAxisServiceNameFromDBL
    private String createAxisServiceName() {
        String portName = null;
        if (portQName != null) {
            portName = portQName.getLocalPart();
        } else {
            portName = "NoPortNameSpecified";

        }
        return getServiceDescription().getServiceQName().getLocalPart() + "." + portName;
    }

    public boolean isWSDLFullySpecified() {
        return isAxisServiceBuiltFromWSDL;
    }

    public boolean isProviderBased() {
        return webServiceProviderAnnotation != null;
    }

    public boolean isEndpointBased() {
        return webServiceAnnotation != null;
    }

    // ===========================================
    // ANNOTATION: WebService and WebServiceProvider
    // ===========================================

    public String getAnnoWebServiceWSDLLocation() {
        if (annotation_WsdlLocation == null) {

            if (getAnnoWebService() != null) {
                annotation_WsdlLocation = getAnnoWebService().wsdlLocation();

                //If this is not an implicit SEI, then make sure that its not on the SEI
                if (getServiceDescriptionImpl().isDBCMap()) {
                    if (!DescriptionUtils.isEmpty(getAnnoWebServiceEndpointInterface())) {

                        DescriptionBuilderComposite seic =
                                getServiceDescriptionImpl().getDBCMap()
                                        .get(composite.getWebServiceAnnot().endpointInterface());
                        if (!DescriptionUtils.isEmpty(seic.getWebServiceAnnot().wsdlLocation())) {
                            annotation_WsdlLocation = seic.getWebServiceAnnot().wsdlLocation();
                        }
                    }
                }
            } else if (getAnnoWebServiceProvider() != null
                    && !DescriptionUtils.isEmpty(getAnnoWebServiceProvider().wsdlLocation())) {
                annotation_WsdlLocation = getAnnoWebServiceProvider().wsdlLocation();
            } else {
                // There is no default value per JSR-181 MR Sec 4.1 pg 16
                annotation_WsdlLocation = "";
            }
        }
        return annotation_WsdlLocation;
    }

    public String getAnnoWebServiceServiceName() {
        if (annotation_ServiceName == null) {
            if (getAnnoWebService() != null
                    && !DescriptionUtils.isEmpty(getAnnoWebService().serviceName())) {
                annotation_ServiceName = getAnnoWebService().serviceName();
            } else if (getAnnoWebServiceProvider() != null
                    && !DescriptionUtils.isEmpty(getAnnoWebServiceProvider().serviceName())) {
                annotation_ServiceName = getAnnoWebServiceProvider().serviceName();
            } else {
                // Default value is the "simple name" of the class or interface + "Service"
                // Per JSR-181 MR Sec 4.1, pg 15
                if (getServiceDescriptionImpl().isDBCMap()) {
                    annotation_ServiceName = DescriptionUtils
                            .getSimpleJavaClassName(composite.getClassName()) + "Service";
                } else {
                    annotation_ServiceName =
                            DescriptionUtils.getSimpleJavaClassName(implOrSEIClass) + "Service";
                }
            }
        }
        return annotation_ServiceName;
    }

    public String getAnnoWebServicePortName() {
        if (annotation_PortName == null) {
            if (getAnnoWebService() != null
                    && !DescriptionUtils.isEmpty(getAnnoWebService().portName())) {
                annotation_PortName = getAnnoWebService().portName();
            } else if (getAnnoWebServiceProvider() != null
                    && !DescriptionUtils.isEmpty(getAnnoWebServiceProvider().portName())) {
                annotation_PortName = getAnnoWebServiceProvider().portName();
            } else {
                // Default the value
                if (isProviderBased()) {
                    // This is the @WebServiceProvider annotation path
                    // Default value is not specified in JSR-224, but we can assume it is
                    // similar to the default in the WebService case, however there is no
                    // name attribute for a WebServiceProvider.  So in this case we use
                    // the default value for WebService.name per JSR-181 MR sec 4.1 pg 15.
                    // Note that this is really the same thing as the call to getWebServiceName()
                    // in the WebService case; it is done sepertely just to be clear there is no
                    // name element on the WebServiceProvider annotation

                    annotation_PortName = (getServiceDescriptionImpl().isDBCMap()) ?
                            DescriptionUtils.getSimpleJavaClassName(composite.getClassName()) +
                                    "Port"
                            : DescriptionUtils.getSimpleJavaClassName(implOrSEIClass) + "Port";
                } else {
                    // This is the @WebService annotation path
                    // Default value is the @WebService.name of the class or interface + "Port"
                    // Per JSR-181 MR Sec 4.1, pg 15
                    annotation_PortName = getAnnoWebServiceName() + "Port";
                }
            }
        }
        return annotation_PortName;
    }

    public String getAnnoWebServiceTargetNamespace() {
        if (annotation_TargetNamespace == null) {
            if (getAnnoWebService() != null
                    && !DescriptionUtils.isEmpty(getAnnoWebService().targetNamespace())) {
                annotation_TargetNamespace = getAnnoWebService().targetNamespace();
            } else if (getAnnoWebServiceProvider() != null
                    && !DescriptionUtils.isEmpty(getAnnoWebServiceProvider().targetNamespace())) {
                annotation_TargetNamespace = getAnnoWebServiceProvider().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.
                // FIXME: Hardcoded protocol for namespace
                if (getServiceDescriptionImpl().isDBCMap()) {
                    annotation_TargetNamespace =
                            DescriptionUtils.makeNamespaceFromPackageName(
                                    DescriptionUtils.getJavaPackageName(composite.getClassName()),
                                    "http");
                } else {
                    annotation_TargetNamespace =
                            DescriptionUtils.makeNamespaceFromPackageName(
                                    DescriptionUtils.getJavaPackageName(implOrSEIClass), "http");
                }

            }
        }
        return annotation_TargetNamespace;
    }

    // ===========================================
    // ANNOTATION: WebServiceProvider
    // ===========================================

    public WebServiceProvider getAnnoWebServiceProvider() {
        return webServiceProviderAnnotation;
    }

    // ===========================================
    // ANNOTATION: WebService
    // ===========================================

    public WebService getAnnoWebService() {
        return webServiceAnnotation;
    }

    public String getAnnoWebServiceEndpointInterface() {
        // TODO: Validation: Not allowed on WebServiceProvider
        if (webService_EndpointInterface == null) {
            if (!isProviderBased() && getAnnoWebService() != null
                    && !DescriptionUtils.isEmpty(getAnnoWebService().endpointInterface())) {
                webService_EndpointInterface = getAnnoWebService().endpointInterface();
            } else {
                // This element is not valid on a WebServiceProvider annotation
                // REVIEW: Is this a correct thing to return if this is called against a WebServiceProvier
                //         which does not support this element?
                webService_EndpointInterface = "";
            }
        }
        return webService_EndpointInterface;
    }

    public String getAnnoWebServiceName() {
        // TODO: Validation: Not allowed on WebServiceProvider

        //TODO: Per JSR109 v1.2 Sec. 5.3.2.1
        //      If not specified then we can use the default value as specified in JSR 181
        //		(but only if it is unique within the module)...or If the name is
        //		not specified in the Service Implementation Bean then fully
        //		qualified name of the Bean class is used to guarantee uniqueness
        //		If the above is not unique then fully qualified name of the
        //		Bean class is used to guarantee uniqueness

        if (webService_Name == null) {
            if (!isProviderBased()) {
                if (getAnnoWebService() != null
                        && !DescriptionUtils.isEmpty(getAnnoWebService().name())) {
                    webService_Name = getAnnoWebService().name();
                } else {
                    if (getServiceDescriptionImpl().isDBCMap()) {
                        //The name is the simple name of the class or interface
                        webService_Name =
                                DescriptionUtils.getSimpleJavaClassName(composite.getClassName());
                    } else {
                        // Default per JSR-181 Sec 4.1, pg 15
                        webService_Name = DescriptionUtils.getSimpleJavaClassName(implOrSEIClass);
                    }
                }
            } else {
                // This element is not valid on a WebServiceProvider annotation
                // REVIEW: Is this a correct thing to return if this is called against a WebServiceProvier
                //         which does not support this element?
                webService_Name = "";
            }
        }
        return webService_Name;
    }

    // ===========================================
    // ANNOTATION: ServiceMode
    // ===========================================
    public ServiceMode getAnnoServiceMode() {

        if (serviceModeAnnotation == null) {
            if (getServiceDescriptionImpl().isDBCMap()) {
                serviceModeAnnotation = composite.getServiceModeAnnot();
            } else {
                if (implOrSEIClass != null) {
                    serviceModeAnnotation =
                            (ServiceMode)implOrSEIClass.getAnnotation(ServiceMode.class);
                }
            }
        }
        return serviceModeAnnotation;
    }

    public Service.Mode getServiceMode() {
        // REVIEW: WSDL/Anno Merge
        return getAnnoServiceModeValue();
    }

    public Service.Mode getAnnoServiceModeValue() {
        // This annotation is only valid on Provider-based endpoints.
        if (isProviderBased() && serviceModeValue == null) {
            if (getAnnoServiceMode() != null) {
                serviceModeValue = getAnnoServiceMode().value();
            } else {
                serviceModeValue = ServiceMode_DEFAULT;
            }
        }
        return serviceModeValue;
    }

    // ===========================================
    // ANNOTATION: BindingType
    // ===========================================

    public BindingType getAnnoBindingType() {
        if (bindingTypeAnnotation == null) {
            if (getServiceDescriptionImpl().isDBCMap()) {
                bindingTypeAnnotation = composite.getBindingTypeAnnot();
            } else {
                if (implOrSEIClass != null) {
                    bindingTypeAnnotation =
                            (BindingType)implOrSEIClass.getAnnotation(BindingType.class);
                }
            }
        }
        return bindingTypeAnnotation;
    }

    public String getBindingType() {
        // REVIEW: Implement WSDL/Anno merge?
        return getAnnoBindingTypeValue();
    }

    public String getAnnoBindingTypeValue() {
        if (bindingTypeValue == null) {
            if (getAnnoBindingType() != null &&
                    !DescriptionUtils.isEmpty(getAnnoBindingType().value())) {
                bindingTypeValue = getAnnoBindingType().value();
            } else {
                // No BindingType annotation present or value was empty; use default value
                bindingTypeValue = BindingType_DEFAULT;
            }
        }
        return bindingTypeValue;
    }

    // ===========================================
    // ANNOTATION: HandlerChain
    // ===========================================

    public void setHandlerChain(final HandlerChainsType handlerChain) {
        handlerChainsType = handlerChain;
    }

    /**
     * Returns a schema derived java class containing the the handler configuration filel
     *
     * @return HandlerChainsType This is the top-level element for the Handler configuration file
     */
    public HandlerChainsType getHandlerChain() {

        if (handlerChainsType == null) {
            getAnnoHandlerChainAnnotation();
            if (handlerChainAnnotation != null) {
                String handlerFileName = handlerChainAnnotation.file();

                // TODO RAS & NLS
                if (log.isDebugEnabled()) {
                    log.debug("EndpointDescriptionImpl.getHandlerChain: fileName: "
                            + handlerFileName
                            + " className: "
                            + composite.getClassName());
                }

                String className = getServiceDescriptionImpl().isDBCMap() ?
                        composite.getClassName() : implOrSEIClass.getName();

                ClassLoader classLoader = getServiceDescriptionImpl().isDBCMap() ?
                        composite.getClassLoader() : this.getClass().getClassLoader();

                InputStream is = DescriptionUtils.openHandlerConfigStream(
                        handlerFileName,
                        className,
                        classLoader);

                if(is == null) {
                    log.warn("Unable to load handlers from file: " + handlerFileName);
                } else {
                    handlerChainsType = DescriptionUtils.loadHandlerChains(is);
                }
            }
        }
        return handlerChainsType;
    }

    public HandlerChain getAnnoHandlerChainAnnotation() {
        if (this.handlerChainAnnotation == null) {
            if (getServiceDescriptionImpl().isDBCMap()) {
                /*
                 * Per JSR-181 The @HandlerChain annotation MAY be present on
                 * the endpoint interface and service implementation bean. The
                 * service implementations bean's @HandlerChain is used if
                 * @HandlerChain is present on both. So, if we do find the
                 * annotation on this impl, then don't worry about else
                 * Otherwise, check to see if the SEI might be annotated with
                 * @HandlerChain
                 */

                handlerChainAnnotation = composite.getHandlerChainAnnot();
                if (handlerChainAnnotation == null) {

                    // If this is NOT an implicit SEI, then check for the
                    // annotation on the SEI
                    if (!DescriptionUtils.isEmpty(getAnnoWebServiceEndpointInterface())) {

                        DescriptionBuilderComposite seic = getServiceDescriptionImpl().getDBCMap()
                                        .get(composite.getWebServiceAnnot().endpointInterface());
                        if (seic != null) {
                            handlerChainAnnotation = seic.getHandlerChainAnnot();
                        }
                        // TODO else clause for if to throw exception when seic == null
                    }
                }
            } else {
                if (implOrSEIClass != null) {
                    handlerChainAnnotation =
                            (HandlerChain)implOrSEIClass.getAnnotation(HandlerChain.class);
                }
            }
        }

        return handlerChainAnnotation;
    }

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

    public javax.wsdl.Service getWSDLService() {
        Definition defn = getWSDLDefinition();
        if (defn != null) {
            return defn.getService(getServiceQName());
        } else {
            return null;
        }
    }

    public Port getWSDLPort() {
        javax.wsdl.Service service = getWSDLService();
        if (service != null) {
            return service.getPort(getPortQName().getLocalPart());
        } else {
            return null;
        }
    }

    public Binding getWSDLBinding() {
        Binding wsdlBinding = null;
        Port wsdlPort = getWSDLPort();
        Definition wsdlDef = getWSDLDefinition();
        if (wsdlPort != null && wsdlDef != null) {
            wsdlBinding = wsdlPort.getBinding();
        }
        return wsdlBinding;
    }

    public String getWSDLBindingType() {
        String wsdlBindingType = null;
        Binding wsdlBinding = getWSDLBinding();
        if (wsdlBinding != null) {
            // If a WSDL binding was found, we need to find the proper extensibility
            // element and return the namespace.  The namespace will be different
            // for SOAP 1.1 vs. SOAP 1.2 bindings and HTTP.
            // TODO: What do we do if no extensibility element exists?
            List<ExtensibilityElement> elements = wsdlBinding.getExtensibilityElements();
            Iterator<ExtensibilityElement> itr = elements.iterator();
            while (itr.hasNext()) {
                ExtensibilityElement e = itr.next();
                if (javax.wsdl.extensions.soap.SOAPBinding.class.isAssignableFrom(e.getClass())) {
                    javax.wsdl.extensions.soap.SOAPBinding soapBnd =
                            (javax.wsdl.extensions.soap.SOAPBinding)e;
                    wsdlBindingType = soapBnd.getElementType().getNamespaceURI();
                    break;
                } else if (SOAP12Binding.class.isAssignableFrom(e.getClass())) {
                    SOAP12Binding soapBnd = (SOAP12Binding)e;
                    wsdlBindingType = soapBnd.getElementType().getNamespaceURI();
                    break;
                } else if (HTTPBinding.class.isAssignableFrom(e.getClass())) {
                    HTTPBinding httpBnd = (HTTPBinding)e;
                    wsdlBindingType = httpBnd.getElementType().getNamespaceURI();
                    break;
                }
            }
        }
        return wsdlBindingType;
    }

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

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

    public PortInfo getPortInfo() {
        if (portInfo == null) {
            portInfo = new PortInfoImpl(getServiceQName(), getPortQName(), getBindingType());
        }
        return portInfo;
    }

    public void setClientBindingID(final String clientBindingID) {

        if (clientBindingID == null) {
            this.clientBindingID = DEFAULT_CLIENT_BINDING_ID;
        } else if (validateClientBindingID(clientBindingID)) {
            this.clientBindingID = clientBindingID;
        } else {
            throw ExceptionFactory.makeWebServiceException(
                    Messages.getMessage("addPortErr0", getPortQName().toString()));
        }
    }

    private boolean validateClientBindingID(final String bindingId) {
        boolean isValid = true;
        if (bindingId != null && !(bindingId.equals(SOAPBinding.SOAP11HTTP_BINDING) ||
                bindingId.equals(javax.xml.ws.http.HTTPBinding.HTTP_BINDING) ||
                bindingId.equals(SOAPBinding.SOAP12HTTP_BINDING) ||
                bindingId.equals(SOAPBinding.SOAP11HTTP_MTOM_BINDING) ||
                bindingId.equals(SOAPBinding.SOAP12HTTP_MTOM_BINDING))) {
            throw ExceptionFactory.makeWebServiceException(
                    Messages.getMessage("addPortErr0", getPortQName().toString()));
        }
        return isValid;
    }

    public String getClientBindingID() {
        if (clientBindingID == null) {
            if (getWSDLDefinition() != null) {
                clientBindingID = getWSDLBindingType();
            } else {
                clientBindingID = DEFAULT_CLIENT_BINDING_ID;
            }
        }
        return clientBindingID;
    }

    public void setEndpointAddress(final String endpointAddress) {
        // REVIEW: Should this be called whenever BindingProvider.ENDPOINT_ADDRESS_PROPERTY is set by the client?
        if (!DescriptionUtils.isEmpty(endpointAddress)) {
            this.endpointAddress = endpointAddress;
        } else {
            // Since a port can be added without setting an endpoint address, this is not an error.
            if (log.isDebugEnabled()) {
                log.debug("A null or empty endpoint address was attempted to be set",
                          new Throwable("Stack Traceback"));
            }
        }
    }

    public String getEndpointAddress() {
        if (endpointAddress == null) {
            // If the endpointAddress has not been set explicitly by a call to setEndpointAddress()
            // then try to get it from the WSDL
            endpointAddress = getWSDLSOAPAddress();
        }
        return endpointAddress;
    }

    /**
     * Return the SOAP Address from the WSDL for this port.
     *
     * @return The SOAP Address from the WSDL for this port or null.
     */
    public String getWSDLSOAPAddress() {
        if (wsdlSOAPAddress == null) {
            Port wsdlPort = getWSDLPort();
            if (wsdlPort != null) {
                // The port is in the WSDL, so see if it has a SOAP address extensibility element specified.
                List extElementList = wsdlPort.getExtensibilityElements();
                for (Object listElement : extElementList) {
                    ExtensibilityElement extElement = (ExtensibilityElement)listElement;
                    if (isSOAPAddressElement(extElement)) {
                        String soapAddress = getSOAPAddressFromElement(extElement);
                        if (!DescriptionUtils.isEmpty(soapAddress)) {
                            wsdlSOAPAddress = soapAddress;
                        }
                    }
                }
            }
        }
        return wsdlSOAPAddress;
    }

    /**
     * Determine if the WSDL Extensibility element corresponds to the SOAP Address element.
     *
     * @param exElement
     * @return
     */
    static boolean isSOAPAddressElement(final ExtensibilityElement exElement) {
        boolean isAddress = false;
        if (exElement != null) {
            isAddress = (SOAP_11_ADDRESS_ELEMENT.equals(exElement.getElementType())
                    ||
                    (SOAP_12_ADDRESS_ELEMENT.equals(exElement.getElementType())));
        }
        return isAddress;
    }

    static String getSOAPAddressFromElement(final ExtensibilityElement extElement) {
        String returnAddress = null;

        if (extElement != null) {
            if (SOAP_11_ADDRESS_ELEMENT.equals(extElement.getElementType())) {
                returnAddress = ((SOAPAddress)extElement).getLocationURI();
            } else if (SOAP_12_ADDRESS_ELEMENT.equals(extElement.getElementType())) {
                returnAddress = ((SOAP12Address)extElement).getLocationURI();
            }
        }

        return returnAddress;
    }

    /**
     * Selects a port to use in the case where a portQName was not specified by the client on the
     * Service.getPort(Class) call.  If WSDL is present, then an appropriate port is looked for
     * under the service element, and an exception is thrown if none can be found.  If WSDL is not
     * present, then the selected port is simply the one determined by annotations.
     *
     * @return A QName representing the port that is to be used.
     */
    private QName selectPortToUse() {
        QName portToUse = null;
        // If WSDL Service for this port is present, then we'll find an appropriate port defined in there and set
        // the name accordingly.  If no WSDL is present, the the PortQName getter will use annotations to set the value.
        if (getWSDLService() != null) {
            portToUse = selectWSDLPortToUse();
        } else {
            // No WSDL, so the port to use is the one defined by the annotations.
            portToUse = getPortQName();
        }
        return portToUse;
    }

    /**
     * Look through the WSDL Service for a port that should be used.  If none can be found, then
     * throw an exception.
     *
     * @param wsdlService
     * @return A QName representing the port from the WSDL that should be used.
     */
    private QName selectWSDLPortToUse() {
        QName wsdlPortToUse = null;

        // To select which WSDL Port to use, we do the following
        // 1) Find the subset of all ports under the service that use the PortType represented by the SEI
        // 2) From the subset in (1) find all those ports that specify a SOAP Address
        // 3) Use the first port from (2)
        // REVIEW: Should we be looking at the binding type or something else to determin which subset of ports to use;
        //         i.e. instead of just finding ports that specify a SOAP Address?

        // Per JSR-181,
        // - The portType name corresponds to the WebService.name annotation value, which is
        //   returned by getName()
        // - The portType namespace corresponds to the WebService.targetNamespace annotation, which
        //   is returned by getTargetNamespace()
        String portTypeLP = getName();
        String portTypeTNS = getTargetNamespace();
        QName portTypeQN = new QName(portTypeTNS, portTypeLP);

        ServiceDescriptionWSDL serviceDescWSDL = (ServiceDescriptionWSDL)getServiceDescription();

        List<Port> wsdlPortsUsingPortType = serviceDescWSDL.getWSDLPortsUsingPortType(portTypeQN);
        List<Port> wsdlPortsUsingSOAPAddresses =
                serviceDescWSDL.getWSDLPortsUsingSOAPAddress(wsdlPortsUsingPortType);
        if (wsdlPortsUsingSOAPAddresses != null && !wsdlPortsUsingSOAPAddresses.isEmpty()) {
            // We return the first port that uses the particluar PortType and has a SOAP address.
            // HOWEVER, that is not necessarily the first one in the WSDL that meets that criteria!
            // The problem is that WSDL4J Service.getPorts(), which is used to get a Map of ports under the service
            // DOES NOT return the ports in the order they are defined in the WSDL.
            // Therefore, we can't necessarily predict which one we'll get back as the "first" one in the collection.
            // REVIEW: Note the above comment; is there anything more predictible and determinstic we can do?
            Port portToUse = (Port)wsdlPortsUsingSOAPAddresses.toArray()[0];
            String portLocalPart = portToUse.getName();
            String portNamespace = serviceDescWSDL.getWSDLService().getQName().getNamespaceURI();
            wsdlPortToUse = new QName(portNamespace, portLocalPart);
        }

        return wsdlPortToUse;
    }

    private WsdlComposite generateWSDL(final DescriptionBuilderComposite dbc) {

        WsdlComposite wsdlComposite = null;
        Definition defn = dbc.getWsdlDefinition();
        if (defn == null || !isAxisServiceBuiltFromWSDL) {

            //Invoke the callback for generating the wsdl
            if (dbc.getCustomWsdlGenerator() != null) {
                String implName = null;
                if (axisService == null) {
                    implName = DescriptionUtils.javifyClassName(composite.getClassName());
                } else {
                    implName = (String)axisService.getParameterValue(MDQConstants.SERVICE_CLASS);
                }
                wsdlComposite =
                        dbc.getCustomWsdlGenerator().generateWsdl(implName, getBindingType());

                if (wsdlComposite != null) {
                    wsdlComposite.setWsdlFileName(
                            (this.getAnnoWebServiceServiceName() + ".wsdl").toLowerCase());

                    Definition wsdlDef = wsdlComposite.getRootWsdlDefinition();

                    try {
                        WSDL4JWrapper wsdl4jWrapper = new WSDL4JWrapper(dbc.getWsdlURL(), wsdlDef);
                        getServiceDescriptionImpl().setGeneratedWsdlWrapper(wsdl4jWrapper);
                    } catch (Exception e) {
                        throw ExceptionFactory.makeWebServiceException(
                                "EndpointDescriptionImpl: WSDLException thrown when attempting to instantiate WSDL4JWrapper ");
                    }
                } else {
                    // REVIEW:Determine if we should always throw an exception on this, or at this point
                    //throw ExceptionFactory.makeWebServiceException("EndpointDescriptionImpl: Unable to find custom WSDL generator");
                    if (log.isDebugEnabled()) {
                        log.debug(
                                "The custom WSDL generator returned null, so no generated WSDL is available");
                    }

                }
            } else {
                // REVIEW: This used to throw an exception, but it seems we shouldn't require
                // a wsdl generator be provided.
//                throw ExceptionFactory.makeWebServiceException("EndpointDescriptionImpl: Unable to find custom WSDL generator");
                if (log.isDebugEnabled()) {
                    log.debug(
                            "No custom WSDL generator was supplied, so WSDL can not be generated");
                }
            }
        }
        return wsdlComposite;
    }

    @Override
    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("Endpoint Address: " + getEndpointAddress());
            //
            string.append(newline);
            string.append("ServiceQName: " + getServiceQName());
            string.append(sameline);
            string.append("PortQName: " + getPortQName());
            string.append(sameline);
            string.append("TargetNamespace: " + getTargetNamespace());
            //
            string.append(newline);
            string.append("Service Mode: " + getServiceMode());
            string.append(sameline);
            string.append("Binding Type: " + getBindingType());
            string.append(sameline);
            string.append("Client Binding Type: " + getClientBindingID());
            //
            string.append(newline);
            string.append("Is provider-based: " + (isProviderBased() == true));
            string.append(sameline);
            string.append("Is proxy-based: " + (isEndpointBased() == true));
            string.append(sameline);
            string.append("Is WSDL fully specified: " + (isWSDLFullySpecified() == true));
            //
            string.append(newline);
            string.append("AxisService: " + getAxisService());
            //
            string.append(newline);
            EndpointInterfaceDescription endpointInterfaceDesc = getEndpointInterfaceDescription();
            if (endpointInterfaceDesc != null) {
                string.append("EndpointInterfaceDescription: " + endpointInterfaceDesc.toString());
            } else {
                string.append("EndpointInterfaceDescription is null.");
            }
        }
        catch (Throwable t) {
            string.append(newline);
            string.append("Complete debug information not currently available for " +
                    "EndpointDescription");
            return string.toString();
        }
        return string.toString();
    }
}


