/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2017 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://oss.oracle.com/licenses/CDDL+GPL-1.1
 * or LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.sun.xml.ws.server;

import com.sun.istack.NotNull;
import com.sun.istack.Nullable;
import com.sun.xml.stream.buffer.MutableXMLStreamBuffer;
import com.sun.xml.ws.api.BindingID;
import com.sun.xml.ws.api.WSBinding;
import com.sun.xml.ws.api.WSFeatureList;
import com.sun.xml.ws.api.databinding.DatabindingConfig;
import com.sun.xml.ws.api.databinding.DatabindingFactory;
import com.sun.xml.ws.api.databinding.MetadataReader;
import com.sun.xml.ws.api.databinding.WSDLGenInfo;
import com.sun.xml.ws.api.model.SEIModel;
import com.sun.xml.ws.api.model.wsdl.WSDLModel;
import com.sun.xml.ws.api.model.wsdl.WSDLPort;
import com.sun.xml.ws.api.model.wsdl.WSDLService;
import com.sun.xml.ws.api.policy.PolicyResolver;
import com.sun.xml.ws.api.policy.PolicyResolverFactory;
import com.sun.xml.ws.api.server.AsyncProvider;
import com.sun.xml.ws.api.server.Container;
import com.sun.xml.ws.api.server.ContainerResolver;
import com.sun.xml.ws.api.server.InstanceResolver;
import com.sun.xml.ws.api.server.Invoker;
import com.sun.xml.ws.api.server.SDDocument;
import com.sun.xml.ws.api.server.SDDocumentSource;
import com.sun.xml.ws.api.server.WSEndpoint;
import com.sun.xml.ws.api.streaming.XMLStreamReaderFactory;
import com.sun.xml.ws.api.wsdl.parser.WSDLParserExtension;
import com.sun.xml.ws.api.wsdl.parser.XMLEntityResolver;
import com.sun.xml.ws.api.wsdl.parser.XMLEntityResolver.Parser;
import com.sun.xml.ws.api.wsdl.writer.WSDLGeneratorExtension;
import com.sun.xml.ws.binding.BindingImpl;
import com.sun.xml.ws.binding.SOAPBindingImpl;
import com.sun.xml.ws.binding.WebServiceFeatureList;
import com.sun.xml.ws.model.AbstractSEIModelImpl;
import com.sun.xml.ws.model.ReflectAnnotationReader;
import com.sun.xml.ws.model.RuntimeModeler;
import com.sun.xml.ws.model.SOAPSEIModel;
import com.sun.xml.ws.policy.PolicyMap;
import com.sun.xml.ws.policy.jaxws.PolicyUtil;
import com.sun.xml.ws.resources.ServerMessages;
import com.sun.xml.ws.server.provider.ProviderInvokerTube;
import com.sun.xml.ws.server.sei.SEIInvokerTube;
import com.sun.xml.ws.util.HandlerAnnotationInfo;
import com.sun.xml.ws.util.HandlerAnnotationProcessor;
import com.sun.xml.ws.util.ServiceConfigurationError;
import com.sun.xml.ws.util.ServiceFinder;
import com.sun.xml.ws.util.xml.XmlUtil;
import com.sun.xml.ws.wsdl.parser.RuntimeWSDLParser;

import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.jws.WebService;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.ws.Provider;
import javax.xml.ws.WebServiceException;
import javax.xml.ws.WebServiceFeature;
import javax.xml.ws.WebServiceProvider;
import javax.xml.ws.soap.SOAPBinding;

import java.io.IOException;
import java.net.URL;
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;

/**
 * Entry point to the JAX-WS RI server-side runtime.
 *
 * @author Kohsuke Kawaguchi
 * @author Jitendra Kotamraju
 */
public class EndpointFactory {
	private static final EndpointFactory instance = new EndpointFactory();
	
	public static EndpointFactory getInstance() {
		return instance;
	}
	
    /**
     * Implements {@link WSEndpoint#create}.
     *
     * No need to take WebServiceContext implementation. When InvokerPipe is
     * instantiated, it calls InstanceResolver to set up a WebServiceContext.
     * We shall only take delegate to getUserPrincipal and isUserInRole from adapter.
     *
     * <p>
     * Nobody else should be calling this method.
     */
    public static <T> WSEndpoint<T> createEndpoint(
        Class<T> implType, boolean processHandlerAnnotation, @Nullable Invoker invoker,
        @Nullable QName serviceName, @Nullable QName portName,
        @Nullable Container container, @Nullable WSBinding binding,
        @Nullable SDDocumentSource primaryWsdl,
        @Nullable Collection<? extends SDDocumentSource> metadata,
        EntityResolver resolver, boolean isTransportSynchronous) {
    	return createEndpoint(implType, processHandlerAnnotation, invoker, serviceName,
    			portName, container, binding, primaryWsdl, metadata, resolver, isTransportSynchronous, true);
    }
    
    public static <T> WSEndpoint<T> createEndpoint(
            Class<T> implType, boolean processHandlerAnnotation, @Nullable Invoker invoker,
            @Nullable QName serviceName, @Nullable QName portName,
            @Nullable Container container, @Nullable WSBinding binding,
            @Nullable SDDocumentSource primaryWsdl,
            @Nullable Collection<? extends SDDocumentSource> metadata,
            EntityResolver resolver, boolean isTransportSynchronous, boolean isStandard) {
    	EndpointFactory factory = container != null ? container.getSPI(EndpointFactory.class) : null;
    	if (factory == null)
    		factory = EndpointFactory.getInstance();

    	return factory.create(
                implType,processHandlerAnnotation, invoker,serviceName,portName,container,binding,primaryWsdl,metadata,resolver,isTransportSynchronous,isStandard);
    }
    
    /**
     * Implements {@link WSEndpoint#create}.
     *
     * No need to take WebServiceContext implementation. When InvokerPipe is
     * instantiated, it calls InstanceResolver to set up a WebServiceContext.
     * We shall only take delegate to getUserPrincipal and isUserInRole from adapter.
     *
     * <p>
     * Nobody else should be calling this method.
     */
    public <T> WSEndpoint<T> create(
            Class<T> implType, boolean processHandlerAnnotation, @Nullable Invoker invoker,
            @Nullable QName serviceName, @Nullable QName portName,
            @Nullable Container container, @Nullable WSBinding binding,
            @Nullable SDDocumentSource primaryWsdl,
            @Nullable Collection<? extends SDDocumentSource> metadata,
            EntityResolver resolver, boolean isTransportSynchronous) {
    	return create(implType, processHandlerAnnotation, invoker, serviceName,
    			portName, container, binding, primaryWsdl, metadata, resolver, isTransportSynchronous,
    			true);
    	
    }

    public <T> WSEndpoint<T> create(
        Class<T> implType, boolean processHandlerAnnotation, @Nullable Invoker invoker,
        @Nullable QName serviceName, @Nullable QName portName,
        @Nullable Container container, @Nullable WSBinding binding,
        @Nullable SDDocumentSource primaryWsdl,
        @Nullable Collection<? extends SDDocumentSource> metadata,
        EntityResolver resolver, boolean isTransportSynchronous, boolean isStandard) {

        if(implType ==null)
            throw new IllegalArgumentException();

        MetadataReader metadataReader = getExternalMetadatReader(implType, binding);

        if (isStandard) {
            verifyImplementorClass(implType, metadataReader);
        }

        if (invoker == null) {
            invoker = InstanceResolver.createDefault(implType).createInvoker();
        }

        // Performance analysis indicates that reading and parsing imported schemas is
        // a major component of Endpoint creation time.  Therefore, modify SDDocumentSource
        // handling to delay iterating collection as long as possible.
        Collection<SDDocumentSource> md = new CollectionCollection<SDDocumentSource>();
        if(primaryWsdl!=null) {
            if(metadata!=null) {
                Iterator<? extends SDDocumentSource> it = metadata.iterator();
                if (it.hasNext() && primaryWsdl.equals(it.next()))
                    md.addAll(metadata);
                else {
                    md.add(primaryWsdl);
                    md.addAll(metadata);
                }
            } else
                md.add(primaryWsdl);
        } else if(metadata!=null)
            md.addAll(metadata);

        if(container==null)
            container = ContainerResolver.getInstance().getContainer();

        if(serviceName==null)
            serviceName = getDefaultServiceName(implType, metadataReader);

        if(portName==null)
            portName = getDefaultPortName(serviceName,implType, metadataReader);

        {// error check
            String serviceNS = serviceName.getNamespaceURI();
            String portNS = portName.getNamespaceURI();
            if (!serviceNS.equals(portNS)) {
                throw new ServerRtException("wrong.tns.for.port",portNS, serviceNS);
            }
        }

        // setting a default binding
        if (binding == null)
            binding = BindingImpl.create(BindingID.parse(implType));

        if ( isStandard && primaryWsdl != null) {
            verifyPrimaryWSDL(primaryWsdl, serviceName);
        }

        QName portTypeName = null;
        if (isStandard && implType.getAnnotation(WebServiceProvider.class)==null) {
            portTypeName = RuntimeModeler.getPortTypeName(implType, metadataReader);
        }

        // Categorises the documents as WSDL, Schema etc
        Collection<SDDocumentImpl> docList = categoriseMetadata(md.iterator(), serviceName, portTypeName);
        // Finds the primary WSDL and makes sure that metadata doesn't have
        // two concrete or abstract WSDLs
        SDDocumentImpl primaryDoc = primaryWsdl != null ? SDDocumentImpl.create(primaryWsdl,serviceName,portTypeName) : findPrimary(docList);

        EndpointAwareTube terminal;
        WSDLPort wsdlPort = null;
        AbstractSEIModelImpl seiModel = null;
        // create WSDL model
        if (primaryDoc != null) {
            wsdlPort = getWSDLPort(primaryDoc, docList, serviceName, portName, container, resolver);
        }

        WebServiceFeatureList features=((BindingImpl)binding).getFeatures();
        if (isStandard) {
        	features.parseAnnotations(implType);
        }
        PolicyMap policyMap = null;
        // create terminal pipe that invokes the application
        if (isUseProviderTube(implType, isStandard)) {
            //TODO incase of Provider, provide a way to User for complete control of the message processing by giving
            // ability to turn off the WSDL/Policy based features and its associated tubes.

            //Even in case of Provider, merge all features configured via WSDL/Policy or deployment configuration
            Iterable<WebServiceFeature> configFtrs;
            if(wsdlPort != null) {
                 policyMap = wsdlPort.getOwner().getParent().getPolicyMap();
                 //Merge features from WSDL and other policy configuration
                configFtrs = wsdlPort.getFeatures();
            } else {
                //No WSDL, so try to merge features from Policy configuration
                policyMap = PolicyResolverFactory.create().resolve(
                        new PolicyResolver.ServerContext(null, container, implType, false));
                configFtrs = PolicyUtil.getPortScopedFeatures(policyMap,serviceName,portName);
            }
            features.mergeFeatures(configFtrs, true);
            terminal = createProviderInvokerTube(implType, binding, invoker, container);
        } else {
            // Create runtime model for non Provider endpoints
            seiModel = createSEIModel(wsdlPort, implType, serviceName, portName, binding, primaryDoc);
            if(binding instanceof SOAPBindingImpl){
                //set portKnownHeaders on Binding, so that they can be used for MU processing
                ((SOAPBindingImpl)binding).setPortKnownHeaders(
                        ((SOAPSEIModel)seiModel).getKnownHeaders());
            }
            // Generate WSDL for SEI endpoints(not for Provider endpoints)
            if (primaryDoc == null) {
                primaryDoc = generateWSDL(binding, seiModel, docList, container, implType);
                // create WSDL model
                wsdlPort = getWSDLPort(primaryDoc, docList, serviceName, portName, container, resolver);
                seiModel.freeze(wsdlPort);
            }
            policyMap = wsdlPort.getOwner().getParent().getPolicyMap();
            // New Features might have been added in WSDL through Policy.
            //Merge features from WSDL and other policy configuration
            // This sets only the wsdl features that are not already set(enabled/disabled)
            features.mergeFeatures(wsdlPort.getFeatures(), true);
            terminal = createSEIInvokerTube(seiModel,invoker,binding);
        }

        // Process @HandlerChain, if handler-chain is not set via Deployment Descriptor
        if (processHandlerAnnotation) {
            processHandlerAnnotation(binding, implType, serviceName, portName);
        }
        // Selects only required metadata for this endpoint from the passed-in metadata
        if (primaryDoc != null) {
            docList = findMetadataClosure(primaryDoc, docList, resolver);
        }
        
        ServiceDefinitionImpl serviceDefiniton = (primaryDoc != null) ? new ServiceDefinitionImpl(docList, primaryDoc) : null;

        return create(serviceName, portName, binding, container, seiModel, wsdlPort, implType, serviceDefiniton, 
        		terminal, isTransportSynchronous, policyMap);
    }
    
    protected <T> WSEndpoint<T> create(QName serviceName, QName portName, WSBinding binding, Container container, SEIModel seiModel, WSDLPort wsdlPort, Class<T> implType, ServiceDefinitionImpl serviceDefinition, EndpointAwareTube terminal, boolean isTransportSynchronous, PolicyMap policyMap) {
        return new WSEndpointImpl<T>(serviceName, portName, binding, container, seiModel, 
        		wsdlPort, implType, serviceDefinition, terminal, isTransportSynchronous, policyMap);
    }

    protected boolean isUseProviderTube(Class<?> implType, boolean isStandard) {
    	return !isStandard || implType.getAnnotation(WebServiceProvider.class)!=null;
    }
    
    protected EndpointAwareTube createSEIInvokerTube(AbstractSEIModelImpl seiModel, Invoker invoker, WSBinding binding) {
    	return new SEIInvokerTube(seiModel,invoker,binding);
    }
    
    protected <T> EndpointAwareTube createProviderInvokerTube(final Class<T> implType, final WSBinding binding,
                                                              final Invoker invoker, final Container container) {
    	return ProviderInvokerTube.create(implType, binding, invoker, container);
    }   
    /**
     * Goes through the original metadata documents and collects the required ones.
     * This done traversing from primary WSDL and its imports until it builds a
     * complete set of documents(transitive closure) for the endpoint.
     *
     * @param primaryDoc primary WSDL doc
     * @param docList complete metadata
     * @return new metadata that doesn't contain extraneous documents.
     */
    private static Collection<SDDocumentImpl> findMetadataClosure(
            final SDDocumentImpl primaryDoc, final Collection<SDDocumentImpl> docList, final EntityResolver resolver) {
        return new AbstractCollection<SDDocumentImpl>() {
            @Override
            public Iterator<SDDocumentImpl> iterator() {
                // create a map for old metadata
                Map<String, SDDocumentImpl> oldMap = new HashMap<String, SDDocumentImpl>();
                Iterator<SDDocumentImpl> oldDocs = docList.iterator();

                // create a map for new metadata
                Map<String, SDDocumentImpl> newMap = new HashMap<String, SDDocumentImpl>();
                newMap.put(primaryDoc.getSystemId().toString(), primaryDoc);

                List<String> remaining = new ArrayList<String>();
                remaining.addAll(primaryDoc.getImports());
                while(!remaining.isEmpty()) {
                    String url = remaining.remove(0);
                    SDDocumentImpl doc = oldMap.get(url);
                    if (doc == null) {
                        while (oldDocs.hasNext()) {
                            SDDocumentImpl old = oldDocs.next();
                            String id = old.getSystemId().toString();
                            oldMap.put(id, old);
                            if (id.equals(url)) {
                                doc = old;
                                break;
                            }
                        }
                        
                        if (doc == null) {
                            // old metadata doesn't have this imported doc, may be external
                                if (resolver != null) {
                                        try {
                                                InputSource source = resolver.resolveEntity(null, url);
                                                if (source != null) {                                           
                                                        MutableXMLStreamBuffer xsb = new MutableXMLStreamBuffer();
                                                        XMLStreamReader reader = XmlUtil.newXMLInputFactory(true).createXMLStreamReader(source.getByteStream());
                                                        xsb.createFromXMLStreamReader(reader);
            
                                                        SDDocumentSource sdocSource = SDDocumentImpl.create(new URL(url), xsb);
                                                        doc = SDDocumentImpl.create(sdocSource, null, null);
                                                } 
                                        } catch (Exception ex) {
                                                ex.printStackTrace();
                                        }
                                } 
                        }
                    }
                    // Check if new metadata already contains this doc
                    if (doc != null && !newMap.containsKey(url)) {
                        newMap.put(url, doc);
                        remaining.addAll(doc.getImports());
                    }
                }
                
                return newMap.values().iterator();
            }

            @Override
            public int size() {
                int size = 0;
                Iterator<SDDocumentImpl> it = iterator();
                while (it.hasNext()) {
                    it.next();
                    size++;
                }
                return size;
            }

            @Override
            public void clear() {
                throw new UnsupportedOperationException();
            }

            @Override
            public boolean isEmpty() {
                return docList.isEmpty();
            }
        };
    }

    private static <T> void processHandlerAnnotation(WSBinding binding, Class<T> implType, QName serviceName, QName portName) {
        HandlerAnnotationInfo chainInfo =
                HandlerAnnotationProcessor.buildHandlerInfo(
                        implType, serviceName, portName, binding);
        if (chainInfo != null) {
            binding.setHandlerChain(chainInfo.getHandlers());
            if (binding instanceof SOAPBinding) {
                ((SOAPBinding) binding).setRoles(chainInfo.getRoles());
            }
        }

    }

    /**
     * Verifies if the endpoint implementor class has @WebService or @WebServiceProvider
     * annotation
     *
     * @return
     *       true if it is a Provider or AsyncProvider endpoint
     *       false otherwise
     * @throws java.lang.IllegalArgumentException
     *      If it doesn't have any one of @WebService or @WebServiceProvider
     *      If it has both @WebService and @WebServiceProvider annotations
     */
    public static boolean verifyImplementorClass(Class<?> clz) {
        return verifyImplementorClass(clz, null);
    }

    /**
     * Verifies if the endpoint implementor class has @WebService or @WebServiceProvider
     * annotation; passing MetadataReader instance allows to read annotations from
     * xml descriptor instead of class's annotations
     *
     * @return
     *       true if it is a Provider or AsyncProvider endpoint
     *       false otherwise
     * @throws java.lang.IllegalArgumentException
     *      If it doesn't have any one of @WebService or @WebServiceProvider
     *      If it has both @WebService and @WebServiceProvider annotations
     */
    public static boolean verifyImplementorClass(Class<?> clz, MetadataReader metadataReader) {

        if (metadataReader == null) {
            metadataReader = new ReflectAnnotationReader();
        }

        WebServiceProvider wsProvider = metadataReader.getAnnotation(WebServiceProvider.class, clz);
        WebService ws = metadataReader.getAnnotation(WebService.class, clz);
        if (wsProvider == null && ws == null) {
            throw new IllegalArgumentException(clz +" has neither @WebService nor @WebServiceProvider annotation");
        }
        if (wsProvider != null && ws != null) {
            throw new IllegalArgumentException(clz +" has both @WebService and @WebServiceProvider annotations");
        }
        if (wsProvider != null) {
            if (Provider.class.isAssignableFrom(clz) || AsyncProvider.class.isAssignableFrom(clz)) {
                return true;
            }
            throw new IllegalArgumentException(clz +" doesn't implement Provider or AsyncProvider interface");
        }
        return false;
    }


    private static AbstractSEIModelImpl createSEIModel(WSDLPort wsdlPort,
                                                       Class<?> implType, @NotNull QName serviceName, @NotNull QName portName, WSBinding binding,
                                                       SDDocumentSource primaryWsdl) {
		DatabindingFactory fac = DatabindingFactory.newInstance();
		DatabindingConfig config = new DatabindingConfig();
		config.setEndpointClass(implType);
		config.getMappingInfo().setServiceName(serviceName);
		config.setWsdlPort(wsdlPort);
		config.setWSBinding(binding);
		config.setClassLoader(implType.getClassLoader());
		config.getMappingInfo().setPortName(portName);
		if (primaryWsdl != null) config.setWsdlURL(primaryWsdl.getSystemId());
        config.setMetadataReader(getExternalMetadatReader(implType, binding));

		com.sun.xml.ws.db.DatabindingImpl rt = (com.sun.xml.ws.db.DatabindingImpl)fac.createRuntime(config);
		return (AbstractSEIModelImpl) rt.getModel();    	
    }

    public static MetadataReader getExternalMetadatReader(Class<?> implType, WSBinding binding) {
        com.oracle.webservices.api.databinding.ExternalMetadataFeature ef = binding.getFeature(
                com.oracle.webservices.api.databinding.ExternalMetadataFeature.class);
        // TODO-Miran: would it be necessary to disable secure xml processing?
        if (ef != null)
            return ef.getMetadataReader(implType.getClassLoader(), false);
        return null;
    }

    /**
     *Set the mtom enable setting from wsdl model (mtom policy assertion) on to @link WSBinding} if DD has
     * not already set it on BindingID. Also check conflicts.
     */
    /*
    private static void applyEffectiveMtomSetting(WSDLBoundPortType wsdlBinding, WSBinding binding){
        if(wsdlBinding.isMTOMEnabled()){
            BindingID bindingId = binding.getBindingId();
            if(bindingId.isMTOMEnabled() == null){
                binding.setMTOMEnabled(true);
            }else if (bindingId.isMTOMEnabled() != null && bindingId.isMTOMEnabled() == Boolean.FALSE){
                //TODO: i18N
                throw new ServerRtException("Deployment failed! Mtom policy assertion in WSDL is enabled whereas the deplyment descriptor setting wants to disable it!");
            }
        }
    }
    */
    /**
     * If service name is not already set via DD or programmatically, it uses
     * annotations {@link WebServiceProvider}, {@link WebService} on implementorClass to get PortName.
     *
     * @return non-null service name
     */
    public static @NotNull QName getDefaultServiceName(Class<?> implType) {
        return getDefaultServiceName(implType, null);
    }

    public static @NotNull QName getDefaultServiceName(Class<?> implType, MetadataReader metadataReader) {
    	return getDefaultServiceName(implType, true, metadataReader);
    }
    
    public static @NotNull QName getDefaultServiceName(Class<?> implType, boolean isStandard) {
        return getDefaultServiceName(implType, isStandard, null);
    }

    public static @NotNull QName getDefaultServiceName(Class<?> implType, boolean isStandard, MetadataReader metadataReader) {
        if (metadataReader == null) {
            metadataReader = new ReflectAnnotationReader();
        }
        QName serviceName;
        WebServiceProvider wsProvider = metadataReader.getAnnotation(WebServiceProvider.class, implType);
        if (wsProvider!=null) {
            String tns = wsProvider.targetNamespace();
            String local = wsProvider.serviceName();
            serviceName = new QName(tns, local);
        } else {
            serviceName = RuntimeModeler.getServiceName(implType, metadataReader, isStandard);
        }
        assert serviceName != null;
        return serviceName;
    }

    /**
     * If portName is not already set via DD or programmatically, it uses
     * annotations on implementorClass to get PortName.
     *
     * @return non-null port name
     */
    public static @NotNull QName getDefaultPortName(QName serviceName, Class<?> implType) {
        return getDefaultPortName(serviceName, implType, null);
    }

    public static @NotNull QName getDefaultPortName(QName serviceName, Class<?> implType, MetadataReader metadataReader) {
    	return getDefaultPortName(serviceName, implType, true, metadataReader);
    }
    
    public static @NotNull QName getDefaultPortName(QName serviceName, Class<?> implType, boolean isStandard) {
        return getDefaultPortName(serviceName, implType, isStandard, null);
    }

    public static @NotNull QName getDefaultPortName(QName serviceName, Class<?> implType, boolean isStandard, MetadataReader metadataReader) {
        if (metadataReader == null) {
            metadataReader = new ReflectAnnotationReader();
        }
        QName portName;
        WebServiceProvider wsProvider = metadataReader.getAnnotation(WebServiceProvider.class, implType);
        if (wsProvider!=null) {
            String tns = wsProvider.targetNamespace();
            String local = wsProvider.portName();
            portName = new QName(tns, local);
        } else {
            portName = RuntimeModeler.getPortName(implType, metadataReader, serviceName.getNamespaceURI(), isStandard);
        }
        assert portName != null;
        return portName;
    }

    /**
     * Returns the wsdl from @WebService, or @WebServiceProvider annotation using
     * wsdlLocation element.
     *
     * @param implType
     *      endpoint implementation class
     *      make sure that you called {@link #verifyImplementorClass} on it.
     * @return wsdl if there is wsdlLocation, else null
     */
    public static @Nullable String getWsdlLocation(Class<?> implType) {
        return getWsdlLocation(implType, new ReflectAnnotationReader());
    }

    /**
     * Returns the wsdl from @WebService, or @WebServiceProvider annotation using
     * wsdlLocation element.
     *
     * @param implType
     *      endpoint implementation class
     *      make sure that you called {@link #verifyImplementorClass} on it.
     * @return wsdl if there is wsdlLocation, else null
     */
    public static @Nullable String getWsdlLocation(Class<?> implType, MetadataReader metadataReader) {

        if (metadataReader == null) {
            metadataReader = new ReflectAnnotationReader();
        }

        WebService ws = metadataReader.getAnnotation(WebService.class, implType);
        if (ws != null) {
            return nullIfEmpty(ws.wsdlLocation());
        } else {
            WebServiceProvider wsProvider = implType.getAnnotation(WebServiceProvider.class);
            assert wsProvider != null;
            return nullIfEmpty(wsProvider.wsdlLocation());
        }
    }

    private static String nullIfEmpty(String string) {
        if (string.length() < 1) {
            string = null;
        }
        return string;
    }

    /**
     * Generates the WSDL and XML Schema for the endpoint if necessary
     * It generates WSDL only for SOAP1.1, and for XSOAP1.2 bindings
     */
    private static SDDocumentImpl generateWSDL(WSBinding binding, AbstractSEIModelImpl seiModel, Collection<SDDocumentImpl> docs,
                                               Container container, Class implType) {
        BindingID bindingId = binding.getBindingId();
        if (!bindingId.canGenerateWSDL()) {
            throw new ServerRtException("can.not.generate.wsdl", bindingId);
        }

        if (bindingId.toString().equals(SOAPBindingImpl.X_SOAP12HTTP_BINDING)) {
            String msg = ServerMessages.GENERATE_NON_STANDARD_WSDL();
            logger.warning(msg);
        }

        // Generate WSDL and schema documents using runtime model
        WSDLGenResolver wsdlResolver = new WSDLGenResolver(docs,seiModel.getServiceQName(),seiModel.getPortTypeName());
        WSDLGenInfo wsdlGenInfo = new WSDLGenInfo(); 
        wsdlGenInfo.setWsdlResolver(wsdlResolver);
        wsdlGenInfo.setContainer(container);
        wsdlGenInfo.setExtensions(ServiceFinder.find(WSDLGeneratorExtension.class).toArray());
        wsdlGenInfo.setInlineSchemas(false);
        wsdlGenInfo.setSecureXmlProcessingDisabled(isSecureXmlProcessingDisabled(binding.getFeatures()));
        seiModel.getDatabinding().generateWSDL(wsdlGenInfo);
//        WSDLGenerator wsdlGen = new WSDLGenerator(seiModel, wsdlResolver, binding, container, implType, false,
//                ServiceFinder.find(WSDLGeneratorExtension.class).toArray());
//        wsdlGen.doGeneration();
        return wsdlResolver.updateDocs();
    }

    private static boolean isSecureXmlProcessingDisabled(WSFeatureList featureList) {
        // TODO-Miran: would it be necessary to disable secure xml processing?
        return false;
    }

    /**
     * Builds {@link SDDocumentImpl} from {@link SDDocumentSource}.
     */
    private static Collection<SDDocumentImpl> categoriseMetadata(
        final Iterator<SDDocumentSource> src, final QName serviceName, final QName portTypeName) {
        
        return new AbstractCollection<SDDocumentImpl>() {
            private final Collection<SDDocumentImpl> theConverted = new ArrayList<SDDocumentImpl>();

            @Override
            public boolean add(SDDocumentImpl arg0) {
                return theConverted.add(arg0);
            }

            @Override
            public Iterator<SDDocumentImpl> iterator() {
                return new Iterator<SDDocumentImpl>() {
                    private Iterator<SDDocumentImpl> convIt = theConverted.iterator();
                    @Override
                    public boolean hasNext() {
                        if (convIt != null && convIt.hasNext())
                            return true;
                        return src.hasNext();
                    }

                    @Override
                    public SDDocumentImpl next() {
                        if (convIt != null && convIt.hasNext())
                            return convIt.next();
                        convIt = null;
                        if (!src.hasNext())
                            throw new NoSuchElementException();
                        SDDocumentImpl next = SDDocumentImpl.create(src.next(),serviceName,portTypeName);
                        theConverted.add(next);
                        return next;
                    }

                    @Override
                    public void remove() {
                        throw new UnsupportedOperationException();
                    }
                };
            }

            @Override
            public int size() {
                throw new UnsupportedOperationException();
            }

            @Override
            public boolean isEmpty() {
                if (!theConverted.isEmpty())
                    return false;
                return !src.hasNext();
            }
        };
    }

    /**
     * Verifies whether the given primaryWsdl contains the given serviceName.
     * If the WSDL doesn't have the service, it throws an WebServiceException.
     */
    private static void verifyPrimaryWSDL(@NotNull SDDocumentSource primaryWsdl, @NotNull QName serviceName) {
        SDDocumentImpl primaryDoc = SDDocumentImpl.create(primaryWsdl,serviceName,null);
        if (!(primaryDoc instanceof SDDocument.WSDL)) {
            throw new WebServiceException(primaryWsdl.getSystemId()+
                    " is not a WSDL. But it is passed as a primary WSDL");
        }
        SDDocument.WSDL wsdlDoc = (SDDocument.WSDL)primaryDoc;
        if (!wsdlDoc.hasService()) {
            if(wsdlDoc.getAllServices().isEmpty())
                throw new WebServiceException("Not a primary WSDL="+primaryWsdl.getSystemId()+
                        " since it doesn't have Service "+serviceName);
            else
                throw new WebServiceException("WSDL "+primaryDoc.getSystemId()
                        +" has the following services "+wsdlDoc.getAllServices()
                        +" but not "+serviceName+". Maybe you forgot to specify a serviceName and/or targetNamespace in @WebService/@WebServiceProvider?");
        }
    }

    /**
     * Finds the primary WSDL document from the list of metadata documents. If
     * there are two metadata documents that qualify for primary, it throws an
     * exception. If there are two metadata documents that qualify for porttype,
     * it throws an exception.
     *
     * @return primay wsdl document, null if is not there in the docList
     *
     */
    private static @Nullable SDDocumentImpl findPrimary(@NotNull Collection<SDDocumentImpl> docList) {
        SDDocumentImpl primaryDoc = null;
        boolean foundConcrete = false;
        boolean foundAbstract = false;
        for(SDDocumentImpl doc : docList) {
            if (doc instanceof SDDocument.WSDL) {
                SDDocument.WSDL wsdlDoc = (SDDocument.WSDL)doc;
                if (wsdlDoc.hasService()) {
                    primaryDoc = doc;
                    if (foundConcrete) {
                        throw new ServerRtException("duplicate.primary.wsdl", doc.getSystemId() );
                    }
                    foundConcrete = true;
                }
                if (wsdlDoc.hasPortType()) {
                    if (foundAbstract) {
                        throw new ServerRtException("duplicate.abstract.wsdl", doc.getSystemId());
                    }
                    foundAbstract = true;
                }
            }
        }
        return primaryDoc;
    }

    /**
     * Parses the primary WSDL and returns the {@link WSDLPort} for the given service and port names
     *
     * @param primaryWsdl Primary WSDL
     * @param metadata it may contain imported WSDL and schema documents
     * @param serviceName service name in wsdl
     * @param portName port name in WSDL
     * @param container container in which this service is running
     * @return non-null wsdl port object
     */
    private static @NotNull WSDLPort getWSDLPort(SDDocumentSource primaryWsdl, Collection<? extends SDDocumentSource> metadata,
                                                     @NotNull QName serviceName, @NotNull QName portName, Container container,
                                                     EntityResolver resolver) {
        URL wsdlUrl = primaryWsdl.getSystemId();
        try {
            // TODO: delegate to another entity resolver
            WSDLModel wsdlDoc = RuntimeWSDLParser.parse(
                new Parser(primaryWsdl), new EntityResolverImpl(metadata, resolver),
                    false, container, ServiceFinder.find(WSDLParserExtension.class).toArray());
            if(wsdlDoc.getServices().size() == 0) {
                throw new ServerRtException(ServerMessages.localizableRUNTIME_PARSER_WSDL_NOSERVICE_IN_WSDLMODEL(wsdlUrl));
            }
            WSDLService wsdlService = wsdlDoc.getService(serviceName);
            if (wsdlService == null) {
                throw new ServerRtException(ServerMessages.localizableRUNTIME_PARSER_WSDL_INCORRECTSERVICE(serviceName,wsdlUrl));
            }
            WSDLPort wsdlPort = wsdlService.get(portName);
            if (wsdlPort == null) {
                throw new ServerRtException(ServerMessages.localizableRUNTIME_PARSER_WSDL_INCORRECTSERVICEPORT(serviceName, portName, wsdlUrl));
            }
            return wsdlPort;
        } catch (IOException e) {
            throw new ServerRtException("runtime.parser.wsdl", wsdlUrl,e);
        } catch (XMLStreamException e) {
            throw new ServerRtException("runtime.saxparser.exception", e.getMessage(), e.getLocation(), e);
        } catch (SAXException e) {
            throw new ServerRtException("runtime.parser.wsdl", wsdlUrl,e);
        } catch (ServiceConfigurationError e) {
            throw new ServerRtException("runtime.parser.wsdl", wsdlUrl,e);
        }
    }

    /**
     * {@link XMLEntityResolver} that can resolve to {@link SDDocumentSource}s.
     */
    private static final class EntityResolverImpl implements XMLEntityResolver {
        private Iterator<? extends SDDocumentSource> origMetadata;
        private Map<String,SDDocumentSource> metadata = new ConcurrentHashMap<String,SDDocumentSource>();
        private EntityResolver resolver;

        public EntityResolverImpl(Collection<? extends SDDocumentSource> metadata, EntityResolver resolver) {
            this.origMetadata = metadata.iterator();
            this.resolver = resolver;
        }

        public Parser resolveEntity (String publicId, String systemId) throws IOException, XMLStreamException {
            if (systemId != null) {
                SDDocumentSource doc = metadata.get(systemId);
                if (doc != null)
                    return new Parser(doc);
                synchronized(this) {
                    while(origMetadata.hasNext()) {
                        doc = origMetadata.next();
                        String extForm = doc.getSystemId().toExternalForm();
                        this.metadata.put(extForm,doc);
                        if (systemId.equals(extForm))
                            return new Parser(doc);
                    }
                }
            }
            if (resolver != null) {
                try {
                    InputSource source = resolver.resolveEntity(publicId, systemId);
                    if (source != null) {
                        Parser p = new Parser(null, XMLStreamReaderFactory.create(source, true));
                        return p;
                    }
                } catch (SAXException e) {
                    throw new XMLStreamException(e);
                }
            }
            return null;
        }

    }

    private static final Logger logger = Logger.getLogger(
        com.sun.xml.ws.util.Constants.LoggingDomain + ".server.endpoint");
    
    private static class CollectionCollection<T> extends AbstractCollection<T> {

        private final Collection<Collection<? extends T>> cols = new ArrayList<Collection<? extends T>>();
        
        @Override
        public Iterator<T> iterator() {
            final Iterator<Collection<? extends T>> colIt = cols.iterator();
            return new Iterator<T>() {
                private Iterator<? extends T> current = null;
                
                @Override
                public boolean hasNext() {
                    if (current == null || !current.hasNext()) {
                        do {
                            if (!colIt.hasNext())
                                return false;
                            current = colIt.next().iterator();
                        } while (!current.hasNext());
                        return true;
                    }
                    return true;
                }

                @Override
                public T next() {
                    if (!hasNext())
                        throw new NoSuchElementException();
                    return current.next();
                }

                @Override
                public void remove() {
                    if (current == null)
                        throw new IllegalStateException();
                    current.remove();
                }
            };
        }

        @Override
        public int size() {
            int size = 0;
            for (Collection<? extends T> c : cols)
                size += c.size();
            return size;
        }

        @Override
        public boolean add(T arg0) {
            return cols.add(Collections.singleton(arg0));
        }

        @Override
        public boolean addAll(Collection<? extends T> arg0) {
            return cols.add(arg0);
        }

        @Override
        public void clear() {
            cols.clear();
        }

        @Override
        public boolean isEmpty() {
            return !iterator().hasNext();
        }
    }
}
