/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.cxf.jaxws;

import java.security.AccessController;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Executor;
import java.util.logging.Logger;

import javax.wsdl.Definition;
import javax.xml.namespace.QName;
import javax.xml.transform.Source;
import javax.xml.ws.Binding;
import javax.xml.ws.EndpointReference;
import javax.xml.ws.WebServiceException;
import javax.xml.ws.WebServiceFeature;
import javax.xml.ws.WebServicePermission;
import javax.xml.ws.handler.Handler;
import javax.xml.ws.http.HTTPBinding;
import javax.xml.ws.wsaddressing.W3CEndpointReference;
import javax.xml.ws.wsaddressing.W3CEndpointReferenceBuilder;

import org.w3c.dom.Element;

import org.apache.cxf.Bus;
import org.apache.cxf.BusFactory;
import org.apache.cxf.binding.BindingConfiguration;
import org.apache.cxf.common.classloader.ClassLoaderUtils;
import org.apache.cxf.common.classloader.ClassLoaderUtils.ClassLoaderHolder;
import org.apache.cxf.common.logging.LogUtils;
import org.apache.cxf.common.util.ClassHelper;
import org.apache.cxf.common.util.ModCountCopyOnWriteArrayList;
import org.apache.cxf.common.util.SystemPropertyAction;
import org.apache.cxf.configuration.Configurable;
import org.apache.cxf.configuration.Configurer;
import org.apache.cxf.databinding.DataBinding;
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.endpoint.ServerImpl;
import org.apache.cxf.feature.Feature;
import org.apache.cxf.frontend.WSDLGetUtils;
import org.apache.cxf.helpers.CastUtils;
import org.apache.cxf.interceptor.Interceptor;
import org.apache.cxf.interceptor.InterceptorProvider;
import org.apache.cxf.jaxws.support.JaxWsEndpointImpl;
import org.apache.cxf.jaxws.support.JaxWsImplementorInfo;
import org.apache.cxf.jaxws.support.JaxWsServiceFactoryBean;
import org.apache.cxf.message.Message;
import org.apache.cxf.service.Service;
import org.apache.cxf.service.invoker.Invoker;
import org.apache.cxf.service.model.EndpointInfo;
import org.apache.cxf.wsdl.WSDLManager;
import org.apache.cxf.wsdl11.WSDLServiceBuilder;

public class EndpointImpl extends javax.xml.ws.Endpoint 
    implements InterceptorProvider, Configurable {
    /**
     * This property controls whether the 'publishEndpoint' permission is checked 
     * using only the AccessController (i.e. when SecurityManager is not installed).
     * By default this check is not done as the system property is not set.
     */
    public static final String CHECK_PUBLISH_ENDPOINT_PERMISSON_PROPERTY =
        "org.apache.cxf.jaxws.checkPublishEndpointPermission";

    public static final String CHECK_PUBLISH_ENDPOINT_PERMISSON_PROPERTY_WITH_SECURITY_MANAGER =
        "org.apache.cxf.jaxws.checkPublishEndpointPermissionWithSecurityManager";
    
    private static final WebServicePermission PUBLISH_PERMISSION =
        new WebServicePermission("publishEndpoint");
    private static final Logger LOG = LogUtils.getL7dLogger(EndpointImpl.class);
    
    private Bus bus;
    private Object implementor;
    private Server server;
    private JaxWsServerFactoryBean serverFactory;
    private JaxWsServiceFactoryBean serviceFactory;
    private Service service;
    private Map<String, Object> properties;
    private List<Source> metadata;
    private Invoker invoker;
    private Executor executor;
    private String bindingUri;
    private String wsdlLocation;
    private String address;
    private String publishedEndpointUrl;
    private QName endpointName;
    private QName serviceName;
    private Class<?> implementorClass;
    
    private List<String> schemaLocations;
    private List<Feature> features;
    private List<Interceptor<? extends Message>> in 
        = new ModCountCopyOnWriteArrayList<Interceptor<? extends Message>>();
    private List<Interceptor<? extends Message>> out 
        = new ModCountCopyOnWriteArrayList<Interceptor<? extends Message>>();
    private List<Interceptor<? extends Message>> outFault  
        = new ModCountCopyOnWriteArrayList<Interceptor<? extends Message>>();
    private List<Interceptor<? extends Message>> inFault
        = new ModCountCopyOnWriteArrayList<Interceptor<? extends Message>>();
    @SuppressWarnings("rawtypes")
    private List<Handler> handlers = new ModCountCopyOnWriteArrayList<Handler>();
    
    /**
     * Flag indicating internal state of this instance.  If true,
     * the instance can have {@link #publish(String, Object)} called
     * and/or settings changed.
     */
    private boolean publishable = true;

    public EndpointImpl(Object implementor) {
        this(BusFactory.getThreadDefaultBus(), implementor);
    }
   
    public EndpointImpl(Bus b, Object implementor, 
                        JaxWsServerFactoryBean sf) {
        this.bus = b;
        this.serverFactory = sf;
        this.implementor = implementor;
    }
    
    /**
     * 
     * @param b
     * @param i The implementor object.
     * @param bindingUri The URI of the Binding being used. Optional.
     * @param wsdl The URL of the WSDL for the service, if different than the URL specified on the
     * WebService annotation. Optional.
     */
    public EndpointImpl(Bus b, Object i, String bindingUri, String wsdl) {
        this(b, i, bindingUri, wsdl, null);
    }
    public EndpointImpl(Bus b, Object i, String bindingUri, String wsdl, WebServiceFeature f[]) {
        bus = b;
        implementor = i;
        this.bindingUri = bindingUri;
        wsdlLocation = wsdl == null ? null : new String(wsdl);
        serverFactory = new JaxWsServerFactoryBean();
        if (f != null) {
            ((JaxWsServiceFactoryBean)serverFactory.getServiceFactory()).setWsFeatures(Arrays.asList(f));
        }
    }
        
    public EndpointImpl(Bus b, Object i, String bindingUri) {
        this(b, i, bindingUri, (String)null);
    }
    public EndpointImpl(Bus b, Object i, String bindingUri, WebServiceFeature features[]) {
        this(b, i, bindingUri, (String)null, features);
    }
   
    public EndpointImpl(Bus bus, Object implementor) {
        this(bus, implementor, (String) null);
    }
    
    public void setBus(Bus b) {
        bus = b;
    }
    public Bus getBus() {
        return bus;
    }
    
    public Binding getBinding() {
        return ((JaxWsEndpointImpl) getEndpoint()).getJaxwsBinding();
    }

    public void setExecutor(Executor executor) {
        this.executor = executor;
    }

    public Executor getExecutor() {
        return executor;
    }

    public Service getService() {
        return service;
    }
    
    public JaxWsServiceFactoryBean getServiceFactory() {
        return serviceFactory;
    }

    
    @Override
    public Object getImplementor() {
        return implementor;
    }

    /**
     * Gets the class of the implementor.
     * @return the class of the implementor object
     */
    public Class<?> getImplementorClass() {
        return implementorClass != null ? implementorClass : ClassHelper.getRealClass(implementor);
    }

    public List<Source> getMetadata() {
        return metadata;
    }

    @Override
    public Map<String, Object> getProperties() {
        if (server != null) {
            return server.getEndpoint();
        }
        if (properties == null) {
            properties = new HashMap<String, Object>();
        }
        return properties;
    }

    @Override
    public boolean isPublished() {
        return server != null;
    }

    /**
     * {@inheritDoc}
     * <p/>
     * This implementation performs no action except to check the publish permission.
     */
    @Override
    public void publish(Object arg0) {
        // Since this does not do anything now, just check the permission
        checkPublishPermission();
    }

    @Override
    public void publish(String addr) {
        doPublish(addr);
    }

    public void setServiceFactory(JaxWsServiceFactoryBean sf) {
        serviceFactory = sf;
    }
    
    public void setMetadata(List<Source> metadata) {
        checkPublishable();
        this.metadata = metadata;
    }

    @Override
    public void setProperties(Map<String, Object> properties) {
        this.properties = properties;
        
        if (server != null) {
            server.getEndpoint().putAll(properties);
        }
    }

    @Override
    public void stop() {
        if (null != server) {
            server.destroy();
            server = null;
        }
    }    
   
    public String getBeanName() {
        return endpointName.toString() + ".jaxws-endpoint";
    }

    public JaxWsServerFactoryBean getServerFactory() {
        return serverFactory;
    }
    
    protected void setServerFactory(JaxWsServerFactoryBean bean) {
        this.serverFactory = bean;
    }
    
    protected void checkProperties() {
        if (properties != null) {
            if (properties.containsKey("javax.xml.ws.wsdl.description")) {
                wsdlLocation = properties.get("javax.xml.ws.wsdl.description").toString();
            }
            if (properties.containsKey(javax.xml.ws.Endpoint.WSDL_PORT)) {
                endpointName = (QName)properties.get(javax.xml.ws.Endpoint.WSDL_PORT);
            }
            if (properties.containsKey(javax.xml.ws.Endpoint.WSDL_SERVICE)) {
                serviceName = (QName)properties.get(javax.xml.ws.Endpoint.WSDL_SERVICE);
            }
        }
    }
    
    /**
     * Performs the publication action by setting up a {@link Server}
     * instance based on this endpoint's configuration.
     *
     * @param addr the optional endpoint address.
     *
     * @throws IllegalStateException if the endpoint cannot be published/republished
     * @throws SecurityException if permission checking is enabled and policy forbids publishing
     * @throws WebServiceException if there is an error publishing the endpoint
     * 
     * @see #checkPublishPermission()
     * @see #checkPublishable()
     * @see #getServer(String)
     */
    protected void doPublish(String addr) {
        checkPublishPermission();
        checkPublishable();
        
        ServerImpl serv = null;
        
        ClassLoaderHolder loader = null;
        try {
            if (bus != null) {
                ClassLoader newLoader = bus.getExtension(ClassLoader.class);
                if (newLoader != null) {
                    loader = ClassLoaderUtils.setThreadContextClassloader(newLoader);
                }
            }
            serv = getServer(addr);
            if (addr != null) {            
                EndpointInfo endpointInfo = serv.getEndpoint().getEndpointInfo();
                if (!endpointInfo.getAddress().contains(addr)) {
                    endpointInfo.setAddress(addr);
                }
                if (publishedEndpointUrl != null) {
                    endpointInfo.setProperty(WSDLGetUtils.PUBLISHED_ENDPOINT_URL, publishedEndpointUrl);
                }
                if (publishedEndpointUrl != null && wsdlLocation != null) {
                    //early update the publishedEndpointUrl so that endpoints in the same app sharing the same wsdl
                    //do not require all of them to be queried for wsdl before the wsdl is finally fully updated
                    Definition def = endpointInfo.getService()
                        .getProperty(WSDLServiceBuilder.WSDL_DEFINITION, Definition.class);
                    if (def == null) {
                        def = bus.getExtension(WSDLManager.class).getDefinition(wsdlLocation);
                    }
                    new WSDLGetUtils().updateWSDLPublishedEndpointAddress(def, endpointInfo);
                }

                if (null != properties) {
                    for (Entry<String, Object> entry : properties.entrySet()) {
                        endpointInfo.setProperty(entry.getKey(), entry.getValue());
                    }
                }

                this.address = endpointInfo.getAddress();
            }
            serv.start();
            publishable = false;
        } catch (Exception ex) {
            try {
                stop();
            } catch (Exception e) {
                // Nothing we can do.
            }
            
            throw new WebServiceException(ex);
        } finally {
            if (loader != null) {
                loader.reset();
            }
        }
    }
    
    public ServerImpl getServer() {
        return getServer(null);
    }
    
    public synchronized ServerImpl getServer(String addr) {
        if (server == null) {
            checkProperties();

            ClassLoaderHolder loader = null;
            try {
                if (bus != null) {
                    ClassLoader newLoader = bus.getExtension(ClassLoader.class);
                    if (newLoader != null) {
                        loader = ClassLoaderUtils.setThreadContextClassloader(newLoader);
                    }
                }
    
                // Initialize the endpointName so we can do configureObject
                QName origEpn = endpointName;
                if (endpointName == null) {
                    JaxWsImplementorInfo implInfo = new JaxWsImplementorInfo(getImplementorClass());
                    endpointName = implInfo.getEndpointName();
                }
                
                if (serviceFactory != null) {
                    serverFactory.setServiceFactory(serviceFactory);
                }
    
                /*if (serviceName != null) {
                    serverFactory.getServiceFactory().setServiceName(serviceName);
                }*/
    
                configureObject(this);
                endpointName = origEpn;
                
                // Set up the server factory
                serverFactory.setAddress(addr);
                serverFactory.setStart(false);
                serverFactory.setEndpointName(endpointName);
                serverFactory.setServiceBean(implementor);
                serverFactory.setBus(bus);
                serverFactory.setFeatures(getFeatures());
                serverFactory.setInvoker(invoker);
                serverFactory.setSchemaLocations(schemaLocations);
                if (serverFactory.getProperties() != null) {
                    serverFactory.getProperties().putAll(properties);
                } else {
                    serverFactory.setProperties(properties);
                }
                
                // Be careful not to override any serverfactory settings as a user might
                // have supplied their own.
                if (getWsdlLocation() != null) {
                    serverFactory.setWsdlURL(getWsdlLocation());
                }
                
                if (bindingUri != null) {
                    serverFactory.setBindingId(bindingUri);
                }
    
                if (serviceName != null) {
                    serverFactory.getServiceFactory().setServiceName(serviceName);
                }
                
                if (implementorClass != null) {
                    serverFactory.setServiceClass(implementorClass);
                }
                
                if (executor != null) {
                    serverFactory.getServiceFactory().setExecutor(executor);
                }
                if (handlers.size() > 0) {
                    serverFactory.addHandlers(handlers);
                }
    
                configureObject(serverFactory);
                
                server = serverFactory.create();
                
                org.apache.cxf.endpoint.Endpoint endpoint = getEndpoint();
                if (in != null) {
                    endpoint.getInInterceptors().addAll(in);
                }
                if (out != null) {
                    endpoint.getOutInterceptors().addAll(out);
                }
                if (inFault != null) {
                    endpoint.getInFaultInterceptors().addAll(inFault);
                }
                if (outFault != null) {
                    endpoint.getOutFaultInterceptors().addAll(outFault);
                }
                
                if (properties != null) {
                    endpoint.putAll(properties);
                }
                
                configureObject(endpoint.getService());
                configureObject(endpoint);
                this.service = endpoint.getService();
                
                if (getWsdlLocation() == null) {
                    //hold onto the wsdl location so cache won't clear till we go away
                    setWsdlLocation(serverFactory.getWsdlURL());
                }
                
                if (serviceName == null) {
                    setServiceName(serverFactory.getServiceFactory().getServiceQName());
                }
                if (endpointName == null) {
                    endpointName = endpoint.getEndpointInfo().getName();
                }
            } finally {
                if (loader != null) {
                    loader.reset();
                }
            }
        }
        return (ServerImpl) server;
    }
    
    org.apache.cxf.endpoint.Endpoint getEndpoint() {
        return getServer(null).getEndpoint();
    }
    
    private void configureObject(Object instance) {
        Configurer configurer = bus.getExtension(Configurer.class);
        if (null != configurer) {
            configurer.configureBean(instance);
        }
    }
    
    protected void checkPublishPermission() {
        SecurityManager sm = System.getSecurityManager();
        boolean checkPublishEndpointPermissionWithSecurityManager
            = Boolean.valueOf(
                      SystemPropertyAction.getProperty(
                                         CHECK_PUBLISH_ENDPOINT_PERMISSON_PROPERTY_WITH_SECURITY_MANAGER, 
                                         "true"));
        if (checkPublishEndpointPermissionWithSecurityManager && sm != null) {
            sm.checkPermission(PUBLISH_PERMISSION);
        } else if (Boolean.getBoolean(CHECK_PUBLISH_ENDPOINT_PERMISSON_PROPERTY)) {
            AccessController.checkPermission(PUBLISH_PERMISSION);
        }
    }
    
    /**
     * Checks the value of {@link #publishable} and throws
     * an {@link IllegalStateException} if the value is {@code false}.
     *
     * @throws IllegalStateException if {@link #publishable} is false
     */
    protected void checkPublishable() {
        if (!this.publishable) {
            throw new IllegalStateException("Cannot invoke method "
                    + "after endpoint has been published.");
        }
    }

    public void publish() {
        publish(getAddress());
    }
    
    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
    
    /**
    * The published endpoint url is used for excplicitely specifying the url of the
    * endpoint that would show up the generated wsdl definition, when the service is
    * brought on line.
    * @return
    */
    public String getPublishedEndpointUrl() {
        return publishedEndpointUrl;
    }
    
    public void setPublishedEndpointUrl(String publishedEndpointUrl) {
        this.publishedEndpointUrl = publishedEndpointUrl;
    }

    public QName getEndpointName() {
        return endpointName;
    }

    public void setEndpointName(QName endpointName) {
        this.endpointName = endpointName;
    }

    public QName getServiceName() {
        return serviceName;
    }

    public void setServiceName(QName serviceName) {
        this.serviceName = serviceName;
    }

    public String getWsdlLocation() {
        return wsdlLocation;
    }

    public void setWsdlLocation(String wsdlLocation) {
        if (wsdlLocation != null) {
            this.wsdlLocation = new String(wsdlLocation);
        } else {
            this.wsdlLocation = null;
        }
    }

    public void setBindingUri(String binding) {
        this.bindingUri = binding;
    }
    
    public String getBindingUri() {
        return this.bindingUri;
    }
    
    public void setDataBinding(DataBinding dataBinding) {
        serverFactory.setDataBinding(dataBinding);
    }
    
    public DataBinding getDataBinding() {
        return serverFactory.getDataBinding();
    }

    public List<Interceptor<? extends Message>> getOutFaultInterceptors() {
        if (server == null) {
            return outFault;
        }
        return new DoubleAddInterceptorList(outFault, server.getEndpoint().getOutFaultInterceptors());
    }

    public List<Interceptor<? extends Message>> getInFaultInterceptors() {
        if (server == null) {
            return inFault;
        }
        return new DoubleAddInterceptorList(inFault, server.getEndpoint().getInFaultInterceptors());
    }

    public List<Interceptor<? extends Message>> getInInterceptors() {
        if (server == null) {
            return in;
        }
        return new DoubleAddInterceptorList(in, server.getEndpoint().getInInterceptors());
    }

    public List<Interceptor<? extends Message>> getOutInterceptors() {
        if (server == null) {
            return out;
        }
        return new DoubleAddInterceptorList(out, server.getEndpoint().getOutInterceptors());
    }
    
    class DoubleAddInterceptorList implements List<Interceptor<? extends Message>> {
        List<Interceptor<? extends Message>> orig;
        List<Interceptor<? extends Message>> other;
        public DoubleAddInterceptorList(List<Interceptor<? extends Message>> a1,
                                        List<Interceptor<? extends Message>> a2) {
            orig = a1;
            other = a2;
        }
        public boolean add(Interceptor<? extends Message> e) {
            other.add(e);
            return orig.add(e);
        }
        public void add(int index, Interceptor<? extends Message> element) {
            other.add(element);
            orig.add(index, element);
        }
        public boolean addAll(Collection<? extends Interceptor<? extends Message>> c) {
            other.addAll(c);
            return orig.addAll(c);
        }
        public boolean addAll(int index, Collection<? extends Interceptor<? extends Message>> c) {
            other.addAll(c);
            return orig.addAll(index, c);
        }
        public void clear() {
            orig.clear();
        }
        public boolean contains(Object o) {
            return orig.contains(o);
        }
        public boolean containsAll(Collection<?> c) {
            return orig.containsAll(c);
        }
        public Interceptor<? extends Message> get(int index) {
            return orig.get(index);
        }
        public int indexOf(Object o) {
            return orig.indexOf(o);
        }
        public boolean isEmpty() {
            return orig.isEmpty();
        }
        public Iterator<Interceptor<? extends Message>> iterator() {
            return orig.iterator();
        }
        public int lastIndexOf(Object o) {
            return orig.lastIndexOf(o);
        }
        public ListIterator<Interceptor<? extends Message>> listIterator() {
            return orig.listIterator();
        }
        public ListIterator<Interceptor<? extends Message>> listIterator(int index) {
            return orig.listIterator(index);
        }
        public boolean remove(Object o) {
            other.remove(o);
            return orig.remove(o);
        }
        public Interceptor<? extends Message> remove(int index) {
            Interceptor<? extends Message> o = orig.remove(index);
            other.remove(o);
            return o;
        }
        public boolean removeAll(Collection<?> c) {
            other.removeAll(c);
            return orig.removeAll(c);
        }
        public boolean retainAll(Collection<?> c) {
            throw new UnsupportedOperationException();
        }
        public Interceptor<? extends Message> set(int index, Interceptor<? extends Message> element) {
            Interceptor<? extends Message> o = orig.set(index, element);
            if (o != null) {
                int idx = other.indexOf(o);
                other.set(idx, element);
            }
            return o;
        }
        public int size() {
            return orig.size();
        }
        public List<Interceptor<? extends Message>> subList(int fromIndex, int toIndex) {
            return orig.subList(fromIndex, toIndex);
        }
        public Object[] toArray() {
            return orig.toArray();
        }
        public <T> T[] toArray(T[] a) {
            return orig.toArray(a);
        }
    }

    public void setInInterceptors(List<Interceptor<? extends Message>> interceptors) {
        in = interceptors;
    }

    public void setInFaultInterceptors(List<Interceptor<? extends Message>> interceptors) {
        inFault = interceptors;
    }

    public void setOutInterceptors(List<Interceptor<? extends Message>> interceptors) {
        out = interceptors;
    }

    public void setOutFaultInterceptors(List<Interceptor<? extends Message>> interceptors) {
        outFault = interceptors;
    }
    public void setHandlers(@SuppressWarnings("rawtypes") List<Handler> h) {
        handlers.clear();
        handlers.addAll(h);
    }
    @SuppressWarnings("rawtypes")
    public List<Handler> getHandlers() {
        return handlers;
    }

    public List<Feature> getFeatures() {
        if (features == null) {
            features = new ArrayList<Feature>();
        }
        return features;
    }

    public void setFeatures(List<? extends Feature> features) {
        this.features = CastUtils.cast(features);
    }

    public Invoker getInvoker() {
        return invoker;
    }

    public void setInvoker(Invoker invoker) {
        this.invoker = invoker;
    }

    public void setImplementorClass(Class<?> implementorClass) {
        this.implementorClass = implementorClass;
    }
    
    public void setTransportId(String transportId) {        
        serverFactory.setTransportId(transportId);
    }
    
    public String getTransportId() {
        return serverFactory.getTransportId();
    }
    
    public void setBindingConfig(BindingConfiguration config) {
        serverFactory.setBindingConfig(config);
    }
    
    public BindingConfiguration getBindingConfig() {
        return serverFactory.getBindingConfig();
    }

    public List<String> getSchemaLocations() {
        return schemaLocations;
    }

    public void setSchemaLocations(List<String> schemaLocations) {
        this.schemaLocations = schemaLocations;
    }
    
    public EndpointReference getEndpointReference(Element... referenceParameters) {
        if (!isPublished()) {
            throw new WebServiceException(new org.apache.cxf.common.i18n.Message("ENDPOINT_NOT_PUBLISHED",
                                                                                 LOG).toString());
        }

        if (getBinding() instanceof HTTPBinding) {        
            throw new UnsupportedOperationException(new org.apache.cxf.common.i18n.Message(
                                                        "GET_ENDPOINTREFERENCE_UNSUPPORTED_BINDING",
                                                        LOG).toString());
        }        
        
        W3CEndpointReferenceBuilder builder = new W3CEndpointReferenceBuilder();
        builder.address(address);
        builder.serviceName(serviceName);
        builder.endpointName(endpointName);
        if (referenceParameters != null) {
            for (Element referenceParameter : referenceParameters) {
                builder.referenceParameter(referenceParameter);
            }
        }
        builder.wsdlDocumentLocation(wsdlLocation);        
        
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        try {
            Thread.currentThread().setContextClassLoader(EndpointReferenceBuilder.class.getClassLoader());
            return builder.build();
        } finally {
            Thread.currentThread().setContextClassLoader(cl);
        }
    }

    public <T extends EndpointReference> T getEndpointReference(Class<T> clazz,
                                                                Element... referenceParameters) {
        if (W3CEndpointReference.class.isAssignableFrom(clazz)) {
            return clazz.cast(getEndpointReference(referenceParameters));
        } else {
            throw new WebServiceException(new org.apache.cxf.common.i18n.Message(
                "ENDPOINTREFERENCE_TYPE_NOT_SUPPORTED", LOG, clazz
                .getName()).toString());
        }
    }

}
