All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jvnet.jax_ws_commons.spring.SpringService Maven / Gradle / Ivy

package org.jvnet.jax_ws_commons.spring;

import com.sun.istack.NotNull;
import com.sun.xml.ws.api.BindingID;
import com.sun.xml.ws.api.WSBinding;
import com.sun.xml.ws.api.pipe.TubelineAssembler;
import com.sun.xml.ws.api.pipe.TubelineAssemblerFactory;
import com.sun.xml.ws.api.server.Container;
import com.sun.xml.ws.api.server.InstanceResolver;
import com.sun.xml.ws.api.server.Invoker;
import com.sun.xml.ws.api.server.SDDocumentSource;
import com.sun.xml.ws.api.server.WSEndpoint;
import com.sun.xml.ws.api.server.Module;
import com.sun.xml.ws.api.server.BoundEndpoint;
import com.sun.xml.ws.binding.BindingImpl;
import com.sun.xml.ws.server.EndpointFactory;
import com.sun.xml.ws.server.ServerRtException;
import com.sun.xml.ws.util.xml.XmlUtil;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.web.context.ServletContextAware;
import org.xml.sax.EntityResolver;

import javax.servlet.ServletContext;
import javax.xml.namespace.QName;
import javax.xml.ws.BindingType;
import javax.xml.ws.WebServiceFeature;
import javax.xml.ws.handler.Handler;
import javax.xml.ws.http.HTTPBinding;
import javax.xml.ws.soap.SOAPBinding;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;

/**
 * Endpoint. A service object and the infrastructure around it.
 *
 * @org.apache.xbean.XBean element="service" rootElement="true"
 * @author Kohsuke Kawaguchi
 */
// javadoc for this class is used to auto-generate documentation.
public class SpringService implements FactoryBean, ServletContextAware, InitializingBean {

    @NotNull
    private Class implType;

    // everything else can be null
    private Invoker invoker;
    private QName serviceName;
    private QName portName;
    private Container container;

    /**
     * Source for the service's primary WSDL.
     * Set by {@link #afterPropertiesSet()}.
     */
    private SDDocumentSource primaryWsdl;

    /**
     * Resource for the service's primary WSDL.
     *
     * @see #setPrimaryWsdl(Object)
     */
    private Object primaryWSDLResource;

    /**
     * Sources for the service's metadata.
     * Set by {@link #afterPropertiesSet()}.
     */
    private Collection metadata;

    /**
     * Resources for the service's metadata.
     *
     * @see #setMetadata(java.util.Collection)
     */
    private Collection metadataResources;

    /**
     * Entity resolver to use for resolving XML resources.
     *
     * @see #setResolver(org.xml.sax.EntityResolver)
     */
    private EntityResolver resolver;

    /**
     * Either {@link TubelineAssembler} or {@link TubelineAssemblerFactory}.
     */
    private Object assembler;


    // binding.

    // either everything is null, in which case we default to SOAP 1.1 + features from annotation

    // ... or a WSBinding configured externally
    private WSBinding binding;

    // ... or a BindingID and features
    private BindingID bindingID;
    private List features;


    /**
     * Technically speaking, handlers belong to
     * {@link WSBinding} and as such it should be configured there,
     * but it's just more convenient to let people do so at this object,
     * because often people use a stock binding ID constant
     * instead of a configured {@link WSBinding} bean.
     */
    private List handlers;

    private ServletContext servletContext;

    /**
     * Set automatically by Spring if JAX-WS is used inside web container.
     */
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    ///**
    // * @org.apache.xbean.Property alias="clazz"
    // */
    // I wanted to use alias="class", but @class is reserved in Spring, apparently
    /**
     * Fully qualified class name of the SEI class. Required.
     */
    public void setImpl(Class implType) {
        this.implType = implType;
    }

    /**
     * Sets the bean that implements the web service methods.
     */
    public void setBean(Object sei) {
        this.invoker = InstanceResolver.createSingleton(sei).createInvoker();
        if(this.implType==null)
            // sei could be a AOP proxy, so getClass() is not always reliable.
            // so if set explicitly via setImpl, don't override that.
            this.implType = sei.getClass();
    }

    /**
     * Sets {@link Invoker} for this endpoint.
     * Defaults to {@link InstanceResolver#createDefault(Class) the standard invoker}.
     */
    public void setInvoker(Invoker invoker) {
        this.invoker = invoker;
    }

    /**
     * Sets the {@link TubelineAssembler} or {@link TubelineAssemblerFactory} instance.
     * 

* This is an advanced configuration option for those who would like to control * what processing JAX-WS runtime performs. The default value is {@code null}, * in which case the {@link TubelineAssemblerFactory} is looked up from the META-INF/services. */ public void setAssembler(Object assembler) { if(assembler instanceof TubelineAssembler || assembler instanceof TubelineAssemblerFactory) this.assembler = assembler; else throw new IllegalArgumentException("Invalid type for assembler "+assembler); } /** * Sets the service name of this endpoint. * Defaults to the name inferred from the impl attribute. */ public void setServiceName(QName serviceName) { this.serviceName = serviceName; } /** * Sets the port name of this endpoint. * Defaults to the name inferred from the impl attribute. */ public void setPortName(QName portName) { this.portName = portName; } /** * Sets the custom {@link Container}. Optional. */ // TODO: how to set the default container? public void setContainer(Container container) { this.container = container; } /** * Accepts an externally configured {@link WSBinding} * for advanced users. */ // is there a better way to do this in Spring? // http://opensource.atlassian.com/projects/spring/browse/SPR-2528?page=all // says it doesn't support method overloading, so that's out. public void setBinding(WSBinding binding) { this.binding = binding; } /** * Sets the binding ID, such as {@value SOAPBinding#SOAP11HTTP_BINDING} * or {@value SOAPBinding#SOAP12HTTP_BINDING}. * *

* If none is specified, {@link BindingType} annotation on SEI is consulted. * If that fails, {@link SOAPBinding#SOAP11HTTP_BINDING}. * * @see SOAPBinding#SOAP11HTTP_BINDING * @see SOAPBinding#SOAP12HTTP_BINDING * @see HTTPBinding#HTTP_BINDING */ public void setBindingID(String id) { this.bindingID = BindingID.parse(id); } /** * {@link WebServiceFeature}s that are activated in this endpoint. */ public void setFeatures(List features) { this.features = features; } /** * {@link Handler}s for this endpoint. * Note that the order is significant. * *

* If there's just one handler and that handler is declared elsewhere, * you can use this as a nested attribute like handlers="#myHandler". * Or otherwise a nested <bean> or <ref> tag can be used to * specify multiple handlers. */ public void setHandlers(List handlers) { this.handlers = handlers; } /** * Optional WSDL for this endpoint. *

* Defaults to the WSDL discovered in META-INF/wsdl, *

* It can be either {@link String}, {@link URL}, or {@link SDDocumentSource}. *

* If primaryWsdl is a String, * {@link ServletContext} (if available) and {@link ClassLoader} * are searched for this path, then failing that, it's treated as an * absolute {@link URL}. */ public void setPrimaryWsdl(Object primaryWsdl) throws IOException { this.primaryWSDLResource = primaryWsdl; } /** * Optional metadata for this endpoint. *

* The collection can contain {@link String}, {@link URL}, or {@link SDDocumentSource} * elements. *

* If element is a String, * {@link ServletContext} (if available) and {@link ClassLoader} * are searched for this path, then failing that, it's treated as an * absolute {@link URL}. */ public void setMetadata(Collection metadata) { this.metadataResources = metadata; } /** * Sets the {@link EntityResolver} to be used for resolving schemas/WSDLs * that are referenced. Optional. * *

* If omitted, the default catalog resolver is created by looking at * /WEB-INF/jax-ws-catalog.xml (if we run as a servlet) or * /META-INF/jax-ws-catalog.xml (otherwise.) */ public void setResolver(EntityResolver resolver) { this.resolver = resolver; } /** * Lazily created {@link WSEndpoint} instance. */ private WSEndpoint endpoint; public WSEndpoint getObject() throws Exception { if(endpoint==null) { if(binding==null) { if(bindingID==null) bindingID = BindingID.parse(implType); if(features==null || features.isEmpty()) binding = BindingImpl.create(bindingID); else binding = BindingImpl.create(bindingID, features.toArray(new WebServiceFeature[features.size()])); } else { if(bindingID!=null) throw new IllegalStateException("Both bindingID and binding are configured"); if(features!=null) throw new IllegalStateException("Both features and binding are configured"); } // configure handlers. doing this here ensures // that we are not doing this more than once. if(handlers!=null) { List chain = binding.getHandlerChain(); chain.addAll(handlers); binding.setHandlerChain(chain); } if(primaryWsdl==null) { // attempt to find it on the impl class. EndpointFactory.verifyImplementorClass(implType); String wsdlLocation = EndpointFactory.getWsdlLocation(implType); if (wsdlLocation != null) primaryWsdl = convertStringToSource(wsdlLocation); } // resolver defaulting. EntityResolver resolver = this.resolver; if(resolver==null) { if(servletContext!=null) { resolver = XmlUtil.createEntityResolver(servletContext.getResource("/WEB-INF/jax-ws-catalog.xml")); } else { resolver = XmlUtil.createEntityResolver(getClass().getClassLoader().getResource("/META-INF/jax-ws-catalog.xml")); } } endpoint = WSEndpoint.create(implType,false,invoker,serviceName, portName,new ContainerWrapper(),binding,primaryWsdl,metadata,resolver,true); } return endpoint; } /** * Called automatically by Spring after all properties have been set, including * {@link #servletContext}. This implementation creates * SDDocumentSources from the {@link #primaryWSDLResource} and * {@link #metadataResources} properties, if provided. * *

See {@link #setMetadata(java.util.Collection)} and * {@link #setPrimaryWsdl(Object)} for conversion rules. * * @throws Exception if an error occurs while creating * SDDocumentSources from the {@link #primaryWSDLResource} and * {@link #metadataResources} properties * * @see #resolveSDDocumentSource(Object) */ public void afterPropertiesSet() throws Exception { if (this.primaryWSDLResource != null) { this.primaryWsdl = this.resolveSDDocumentSource(this.primaryWSDLResource); } if (this.metadataResources != null) { List tempList = new ArrayList(this.metadataResources.size()); for (Object resource : this.metadataResources) { tempList.add(this.resolveSDDocumentSource(resource)); } this.metadata = tempList; } } /** * Resolves a resource ({@link String}, {@link URL}, or {@link SDDocumentSource}) * to a {@link SDDocumentSource}. *

* See {@link #convertStringToSource(String)} for processing rules relating * to a String argument. * * @param resource the String, URL, * or SDDocumentSource to resolve * * @return a SDDocumentSource for the provided resource * * @throws IllegalArgumentException if resource is not an * instance of String, URL, or * SDDocumentSource * * @see #convertStringToSource(String) * @see SDDocumentSource#create(java.net.URL) */ private SDDocumentSource resolveSDDocumentSource(Object resource) { SDDocumentSource source; if (resource instanceof String) { source = this.convertStringToSource((String) resource); } else if (resource instanceof URL) { source = SDDocumentSource.create((URL) resource); } else if (resource instanceof SDDocumentSource) { source = (SDDocumentSource) resource; } else { throw new IllegalArgumentException("Unknown type " + resource); } return source; } /** * Converts {@link String} into {@link SDDocumentSource}. *

* If resourceLocation is a String, * {@link ServletContext} (if available) and {@link ClassLoader} * are searched for this path, then failing that, it's treated as an * absolute {@link URL}. * * @throws ServerRtException if resourceLocation cannot be * resolved through {@link ServletContext} (if available), {@link ClassLoader}, * or as an absolute {@link java.net.URL}. */ private SDDocumentSource convertStringToSource(String resourceLocation) { URL url = null; if (servletContext != null) { // in the servlet environment, consult ServletContext so that we can load // WEB-INF/wsdl/... and so on. try { url = servletContext.getResource(resourceLocation); } catch (MalformedURLException e) { // ignore it and try the next method } } if (url == null) { // also check a resource in classloader. ClassLoader cl = implType.getClassLoader(); url = cl.getResource(resourceLocation); } if (url == null) { try { url = new URL(resourceLocation); } catch (MalformedURLException e) { // ignore it throw exception later } } if (url == null) { throw new ServerRtException("cannot.load.wsdl", resourceLocation); } return SDDocumentSource.create(url); } public boolean isSingleton() { return true; } public Class getObjectType() { return WSEndpoint.class; } private class ContainerWrapper extends Container { public T getSPI(Class spiType) { // allow specified TubelineAssembler to be used if(spiType==TubelineAssemblerFactory.class) { if(assembler instanceof TubelineAssemblerFactory) return spiType.cast(assembler); if(assembler instanceof TubelineAssembler) { return spiType.cast(new TubelineAssemblerFactory() { public TubelineAssembler doCreate(BindingID bindingId) { return (TubelineAssembler)assembler; } }); } } if(spiType==ServletContext.class) { return spiType.cast(servletContext); } if(container!=null) { // delegate to the specified container T t = container.getSPI(spiType); if(t!=null) return t; } if(spiType==Module.class) { // fall back default implementation return spiType.cast(module); } return null; } private final Module module = new Module() { private final List endpoints = new ArrayList(); public @NotNull List getBoundEndpoints() { return endpoints; } }; } }