Open Source Repository

Home /spring/spring-web-3.0.5 | Repository Home



org/springframework/remoting/jaxws/JaxWsPortClientInterceptor.java
/*
 * Copyright 2002-2010 the original author or authors.
 *
 * Licensed 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.springframework.remoting.jaxws;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import javax.jws.WebService;
import javax.xml.namespace.QName;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.ProtocolException;
import javax.xml.ws.Service;
import javax.xml.ws.WebServiceException;
import javax.xml.ws.WebServiceFeature;
import javax.xml.ws.soap.SOAPFaultException;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.remoting.RemoteAccessException;
import org.springframework.remoting.RemoteConnectFailureException;
import org.springframework.remoting.RemoteLookupFailureException;
import org.springframework.remoting.RemoteProxyFailureException;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

/**
 {@link org.aopalliance.intercept.MethodInterceptor} for accessing a specific
 * port of a JAX-WS service. Compatible with JAX-WS 2.0, 2.1 and 2.2.
 *
 <p>Uses either {@link LocalJaxWsServiceFactory}'s facilities underneath,
 * or takes an explicit reference to an existing JAX-WS Service instance
 * (e.g. obtained via {@link org.springframework.jndi.JndiObjectFactoryBean}).
 *
 @author Juergen Hoeller
 @since 2.5
 @see #setPortName
 @see #setServiceInterface
 @see javax.xml.ws.Service#getPort
 @see org.springframework.remoting.RemoteAccessException
 @see org.springframework.jndi.JndiObjectFactoryBean
 */
public class JaxWsPortClientInterceptor extends LocalJaxWsServiceFactory
    implements MethodInterceptor, BeanClassLoaderAware, InitializingBean {

  private Service jaxWsService;

  private String portName;

  private String username;

  private String password;

  private String endpointAddress;

  private boolean maintainSession;

  private boolean useSoapAction;

  private String soapActionUri;

  private Map<String, Object> customProperties;

  private Object[] webServiceFeatures;

  private Class<?> serviceInterface;

  private boolean lookupServiceOnStartup = true;

  private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();

  private QName portQName;

  private Object portStub;

  private final Object preparationMonitor = new Object();


  /**
   * Set a reference to an existing JAX-WS Service instance,
   * for example obtained via {@link org.springframework.jndi.JndiObjectFactoryBean}.
   * If not set, {@link LocalJaxWsServiceFactory}'s properties have to be specified.
   @see #setWsdlDocumentUrl
   @see #setNamespaceUri
   @see #setServiceName
   @see org.springframework.jndi.JndiObjectFactoryBean
   */
  public void setJaxWsService(Service jaxWsService) {
    this.jaxWsService = jaxWsService;
  }

  /**
   * Return a reference to an existing JAX-WS Service instance, if any.
   */
  public Service getJaxWsService() {
    return this.jaxWsService;
  }

  /**
   * Set the name of the port.
   * Corresponds to the "wsdl:port" name.
   */
  public void setPortName(String portName) {
    this.portName = portName;
  }

  /**
   * Return the name of the port.
   */
  public String getPortName() {
    return this.portName;
  }

  /**
   * Set the username to specify on the stub.
   @see javax.xml.ws.BindingProvider#USERNAME_PROPERTY
   */
  public void setUsername(String username) {
    this.username = username;
  }

  /**
   * Return the username to specify on the stub.
   */
  public String getUsername() {
    return this.username;
  }

  /**
   * Set the password to specify on the stub.
   @see javax.xml.ws.BindingProvider#PASSWORD_PROPERTY
   */
  public void setPassword(String password) {
    this.password = password;
  }

  /**
   * Return the password to specify on the stub.
   */
  public String getPassword() {
    return this.password;
  }

  /**
   * Set the endpoint address to specify on the stub.
   @see javax.xml.ws.BindingProvider#ENDPOINT_ADDRESS_PROPERTY
   */
  public void setEndpointAddress(String endpointAddress) {
    this.endpointAddress = endpointAddress;
  }

  /**
   * Return the endpoint address to specify on the stub.
   */
  public String getEndpointAddress() {
    return this.endpointAddress;
  }

  /**
   * Set the "session.maintain" flag to specify on the stub.
   @see javax.xml.ws.BindingProvider#SESSION_MAINTAIN_PROPERTY
   */
  public void setMaintainSession(boolean maintainSession) {
    this.maintainSession = maintainSession;
  }

  /**
   * Return the "session.maintain" flag to specify on the stub.
   */
  public boolean isMaintainSession() {
    return this.maintainSession;
  }

  /**
   * Set the "soapaction.use" flag to specify on the stub.
   @see javax.xml.ws.BindingProvider#SOAPACTION_USE_PROPERTY
   */
  public void setUseSoapAction(boolean useSoapAction) {
    this.useSoapAction = useSoapAction;
  }

  /**
   * Return the "soapaction.use" flag to specify on the stub.
   */
  public boolean isUseSoapAction() {
    return this.useSoapAction;
  }

  /**
   * Set the SOAP action URI to specify on the stub.
   @see javax.xml.ws.BindingProvider#SOAPACTION_URI_PROPERTY
   */
  public void setSoapActionUri(String soapActionUri) {
    this.soapActionUri = soapActionUri;
  }

  /**
   * Return the SOAP action URI to specify on the stub.
   */
  public String getSoapActionUri() {
    return this.soapActionUri;
  }

  /**
   * Set custom properties to be set on the stub.
   <p>Can be populated with a String "value" (parsed via PropertiesEditor)
   * or a "props" element in XML bean definitions.
   @see javax.xml.ws.BindingProvider#getRequestContext()
   */
  public void setCustomProperties(Map<String, Object> customProperties) {
    this.customProperties = customProperties;
  }

  /**
   * Allow Map access to the custom properties to be set on the stub,
   * with the option to add or override specific entries.
   <p>Useful for specifying entries directly, for example via
   * "customProperties[myKey]". This is particularly useful for
   * adding or overriding entries in child bean definitions.
   */
  public Map<String, Object> getCustomProperties() {
    if (this.customProperties == null) {
      this.customProperties = new HashMap<String, Object>();
    }
    return this.customProperties;
  }

  /**
   * Add a custom property to this JAX-WS BindingProvider.
   @param name the name of the attribute to expose
   @param value the attribute value to expose
   @see javax.xml.ws.BindingProvider#getRequestContext()
   */
  public void addCustomProperty(String name, Object value) {
    getCustomProperties().put(name, value);
  }

  /**
   * Allows for providing JAX-WS 2.1 WebServiceFeature specifications:
   * in the form of actual {@link javax.xml.ws.WebServiceFeature} objects,
   * WebServiceFeature Class references, or WebServiceFeature class names.
   */
  public void setWebServiceFeatures(Object[] webServiceFeatures) {
    this.webServiceFeatures = webServiceFeatures;
  }

  /**
   * Set the interface of the service that this factory should create a proxy for.
   */
  public void setServiceInterface(Class<?> serviceInterface) {
    if (serviceInterface != null && !serviceInterface.isInterface()) {
      throw new IllegalArgumentException("'serviceInterface' must be an interface");
    }
    this.serviceInterface = serviceInterface;
  }

  /**
   * Return the interface of the service that this factory should create a proxy for.
   */
  public Class<?> getServiceInterface() {
    return this.serviceInterface;
  }

  /**
   * Set whether to look up the JAX-WS service on startup.
   <p>Default is "true". Turn this flag off to allow for late start
   * of the target server. In this case, the JAX-WS service will be
   * lazily fetched on first access.
   */
  public void setLookupServiceOnStartup(boolean lookupServiceOnStartup) {
    this.lookupServiceOnStartup = lookupServiceOnStartup;
  }

  /**
   * Set the bean ClassLoader to use for this interceptor:
   * for resolving WebServiceFeature class names as specified through
   {@link #setWebServiceFeatures}, and also for building a client
   * proxy in the {@link JaxWsPortProxyFactoryBean} subclass.
   */
  public void setBeanClassLoader(ClassLoader classLoader) {
    this.beanClassLoader = classLoader;
  }

  /**
   * Return the bean ClassLoader to use for this interceptor.
   */
  protected ClassLoader getBeanClassLoader() {
    return this.beanClassLoader;
  }


  public void afterPropertiesSet() {
    if (this.lookupServiceOnStartup) {
      prepare();
    }
  }

  /**
   * Initialize the JAX-WS port for this interceptor.
   */
  public void prepare() {
    Class<?> ifc = getServiceInterface();
    if (ifc == null) {
      throw new IllegalArgumentException("Property 'serviceInterface' is required");
    }
    WebService ann = ifc.getAnnotation(WebService.class);
    if (ann != null) {
      applyDefaultsFromAnnotation(ann);
    }
    Service serviceToUse = getJaxWsService();
    if (serviceToUse == null) {
      serviceToUse = createJaxWsService();
    }
    this.portQName = getQName(getPortName() != null ? getPortName() : getServiceInterface().getName());
    Object stub = getPortStub(serviceToUse, (getPortName() != null this.portQName : null));
    preparePortStub(stub);
    this.portStub = stub;
  }

  /**
   * Initialize this client interceptor's properties from the given WebService annotation,
   * if necessary and possible (i.e. if "wsdlDocumentUrl", "namespaceUri", "serviceName"
   * and "portName" haven't been set but corresponding values are declared at the
   * annotation level of the specified service interface).
   @param ann the WebService annotation found on the specified service interface
   */
  protected void applyDefaultsFromAnnotation(WebService ann) {
    if (getWsdlDocumentUrl() == null) {
      String wsdl = ann.wsdlLocation();
      if (StringUtils.hasText(wsdl)) {
        try {
          setWsdlDocumentUrl(new URL(wsdl));
        }
        catch (MalformedURLException ex) {
          throw new IllegalStateException(
              "Encountered invalid @Service wsdlLocation value [" + wsdl + "]", ex);
        }
      }
    }
    if (getNamespaceUri() == null) {
      String ns = ann.targetNamespace();
      if (StringUtils.hasText(ns)) {
        setNamespaceUri(ns);
      }
    }
    if (getServiceName() == null) {
      String sn = ann.serviceName();
      if (StringUtils.hasText(sn)) {
        setServiceName(sn);
      }
    }
    if (getPortName() == null) {
      String pn = ann.portName();
      if (StringUtils.hasText(pn)) {
        setPortName(pn);
      }
    }
  }

  /**
   * Return whether this client interceptor has already been prepared,
   * i.e. has already looked up the JAX-WS service and port.
   */
  protected boolean isPrepared() {
    synchronized (this.preparationMonitor) {
      return (this.portStub != null);
    }
  }

  /**
   * Return the prepared QName for the port.
   @see #setPortName
   @see #getQName
   */
  protected final QName getPortQName() {
    return this.portQName;
  }

  /**
   * Obtain the port stub from the given JAX-WS Service.
   @param service the Service object to obtain the port from
   @param portQName the name of the desired port, if specified
   @return the corresponding port object as returned from
   <code>Service.getPort(...)</code>
   */
  protected Object getPortStub(Service service, QName portQName) {
    if (this.webServiceFeatures != null) {
      return new FeaturePortProvider().getPortStub(service, portQName, this.webServiceFeatures);
    }
    else {
      return (portQName != null ? service.getPort(portQName, getServiceInterface()) :
          service.getPort(getServiceInterface()));
    }
  }

  /**
   * Prepare the given JAX-WS port stub, applying properties to it.
   * Called by {@link #prepare}.
   @param stub the current JAX-WS port stub
   @see #setUsername
   @see #setPassword
   @see #setEndpointAddress
   @see #setMaintainSession
   @see #setCustomProperties
   */
  protected void preparePortStub(Object stub) {
    Map<String, Object> stubProperties = new HashMap<String, Object>();
    String username = getUsername();
    if (username != null) {
      stubProperties.put(BindingProvider.USERNAME_PROPERTY, username);
    }
    String password = getPassword();
    if (password != null) {
      stubProperties.put(BindingProvider.PASSWORD_PROPERTY, password);
    }
    String endpointAddress = getEndpointAddress();
    if (endpointAddress != null) {
      stubProperties.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpointAddress);
    }
    if (isMaintainSession()) {
      stubProperties.put(BindingProvider.SESSION_MAINTAIN_PROPERTY, Boolean.TRUE);
    }
    if (isUseSoapAction()) {
      stubProperties.put(BindingProvider.SOAPACTION_USE_PROPERTY, Boolean.TRUE);
    }
    String soapActionUri = getSoapActionUri();
    if (soapActionUri != null) {
      stubProperties.put(BindingProvider.SOAPACTION_URI_PROPERTY, soapActionUri);
    }
    stubProperties.putAll(getCustomProperties());
    if (!stubProperties.isEmpty()) {
      if (!(stub instanceof BindingProvider)) {
        throw new RemoteLookupFailureException("Port stub of class [" + stub.getClass().getName() +
            "] is not a customizable JAX-WS stub: it does not implement interface [javax.xml.ws.BindingProvider]");
      }
      ((BindingProviderstub).getRequestContext().putAll(stubProperties);
    }
  }

  /**
   * Return the underlying JAX-WS port stub that this interceptor delegates to
   * for each method invocation on the proxy.
   */
  protected Object getPortStub() {
    return this.portStub;
  }


  public Object invoke(MethodInvocation invocationthrows Throwable {
    if (AopUtils.isToStringMethod(invocation.getMethod())) {
      return "JAX-WS proxy for port [" + getPortName() "] of service [" + getServiceName() "]";
    }
    // Lazily prepare service and stub if necessary.
    synchronized (this.preparationMonitor) {
      if (!isPrepared()) {
        prepare();
      }
    }
    return doInvoke(invocation);
  }

  /**
   * Perform a JAX-WS service invocation based on the given method invocation.
   @param invocation the AOP method invocation
   @return the invocation result, if any
   @throws Throwable in case of invocation failure
   @see #getPortStub()
   @see #doInvoke(org.aopalliance.intercept.MethodInvocation, Object)
   */
  protected Object doInvoke(MethodInvocation invocationthrows Throwable {
    try {
      return doInvoke(invocation, getPortStub());
    }
    catch (SOAPFaultException ex) {
      throw new JaxWsSoapFaultException(ex);
    }
    catch (ProtocolException ex) {
      throw new RemoteConnectFailureException(
          "Could not connect to remote service [" + getEndpointAddress() "]", ex);
    }
    catch (WebServiceException ex) {
      throw new RemoteAccessException(
          "Could not access remote service at [" + getEndpointAddress() "]", ex);
    }
  }

  /**
   * Perform a JAX-WS service invocation on the given port stub.
   @param invocation the AOP method invocation
   @param portStub the RMI port stub to invoke
   @return the invocation result, if any
   @throws Throwable in case of invocation failure
   @see #getPortStub()
   */
  protected Object doInvoke(MethodInvocation invocation, Object portStubthrows Throwable {
    Method method = invocation.getMethod();
    try {
      return method.invoke(portStub, invocation.getArguments());
    }
    catch (InvocationTargetException ex) {
      throw ex.getTargetException();
    }
    catch (Throwable ex) {
      throw new RemoteProxyFailureException("Invocation of stub method failed: " + method, ex);
    }
  }

  /**
   * Inner class in order to avoid a hard-coded JAX-WS 2.1 dependency.
   * JAX-WS 2.0, as used in Java EE 5, didn't have WebServiceFeatures yet...
   */
  private class FeaturePortProvider {

    public Object getPortStub(Service service, QName portQName, Object[] features) {
      WebServiceFeature[] wsFeatures = new WebServiceFeature[features.length];
      for (int i = 0; i < features.length; i++) {
        wsFeatures[i= convertWebServiceFeature(features[i]);
      }
      return (portQName != null ? service.getPort(portQName, getServiceInterface(), wsFeatures:
          service.getPort(getServiceInterface(), wsFeatures));
    }

    private WebServiceFeature convertWebServiceFeature(Object feature) {
      Assert.notNull(feature, "WebServiceFeature specification object must not be null");
      if (feature instanceof WebServiceFeature) {
        return (WebServiceFeaturefeature;
      }
      else if (feature instanceof Class) {
        return (WebServiceFeatureBeanUtils.instantiate((Class<?>feature);
      }
      else if (feature instanceof String) {
        try {
          Class<?> featureClass = getBeanClassLoader().loadClass((Stringfeature);
          return (WebServiceFeatureBeanUtils.instantiate(featureClass);
        }
        catch (ClassNotFoundException ex) {
          throw new IllegalArgumentException("Could not load WebServiceFeature class [" + feature + "]");
        }
      }
      else {
        throw new IllegalArgumentException("Unknown WebServiceFeature specification type: " + feature.getClass());
      }
    }
  }

}