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

org.springframework.remoting.jaxrpc.JaxRpcPortClientInterceptor Maven / Gradle / Ivy

There is a newer version: 5.3.34
Show newest version
/*
 * Copyright 2002-2007 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.jaxrpc;

import java.lang.reflect.InvocationTargetException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;

import javax.xml.namespace.QName;
import javax.xml.rpc.Call;
import javax.xml.rpc.JAXRPCException;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceException;
import javax.xml.rpc.Stub;

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

import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.remoting.RemoteLookupFailureException;
import org.springframework.remoting.RemoteProxyFailureException;
import org.springframework.remoting.rmi.RmiClientInterceptorUtils;
import org.springframework.util.CollectionUtils;

/**
 * Interceptor for accessing a specific port of a JAX-RPC service.
 * Uses either {@link LocalJaxRpcServiceFactory}'s facilities underneath,
 * or takes an explicit reference to an existing JAX-RPC Service instance
 * (e.g. obtained via {@link org.springframework.jndi.JndiObjectFactoryBean}).
 *
 * 

Allows to set JAX-RPC's standard stub properties directly, via the * "username", "password", "endpointAddress" and "maintainSession" properties. * For typical usage, it is not necessary to specify those. * *

In standard JAX-RPC style, this invoker is used with an RMI service interface. * Alternatively, this invoker can also proxy a JAX-RPC service with a matching * non-RMI business interface, that is, an interface that declares the service methods * without RemoteExceptions. In the latter case, RemoteExceptions thrown by JAX-RPC * will automatically get converted to Spring's unchecked RemoteAccessException. * *

Setting "serviceInterface" is usually sufficient: The invoker will automatically * use JAX-RPC "dynamic invocations" via the Call API in this case, no matter whether * the specified interface is an RMI or non-RMI interface. Alternatively, a corresponding * JAX-RPC port interface can be specified as "portInterface", which will turn this * invoker into "static invocation" mode (operating on a standard JAX-RPC port stub). * * @author Juergen Hoeller * @since 15.12.2003 * @see #setPortName * @see #setServiceInterface * @see #setPortInterface * @see javax.xml.rpc.Service#createCall * @see javax.xml.rpc.Service#getPort * @see org.springframework.remoting.RemoteAccessException * @see org.springframework.jndi.JndiObjectFactoryBean */ public class JaxRpcPortClientInterceptor extends LocalJaxRpcServiceFactory implements MethodInterceptor, InitializingBean { private Service jaxRpcService; private Service serviceToUse; private String portName; private String username; private String password; private String endpointAddress; private boolean maintainSession; /** Map of custom properties, keyed by property name (String) */ private final Map customPropertyMap = new HashMap(); private Class serviceInterface; private Class portInterface; private boolean lookupServiceOnStartup = true; private boolean refreshServiceAfterConnectFailure = false; private QName portQName; private Remote portStub; private final Object preparationMonitor = new Object(); /** * Set a reference to an existing JAX-RPC Service instance, * for example obtained via {@link org.springframework.jndi.JndiObjectFactoryBean}. * If not set, LocalJaxRpcServiceFactory's properties have to be specified. * @see #setServiceFactoryClass * @see #setWsdlDocumentUrl * @see #setNamespaceUri * @see #setServiceName * @see org.springframework.jndi.JndiObjectFactoryBean */ public void setJaxRpcService(Service jaxRpcService) { this.jaxRpcService = jaxRpcService; } /** * Return a reference to an existing JAX-RPC Service instance, if any. */ public Service getJaxRpcService() { return this.jaxRpcService; } /** * 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 or call. * @see javax.xml.rpc.Stub#USERNAME_PROPERTY * @see javax.xml.rpc.Call#USERNAME_PROPERTY */ public void setUsername(String username) { this.username = username; } /** * Return the username to specify on the stub or call. */ public String getUsername() { return this.username; } /** * Set the password to specify on the stub or call. * @see javax.xml.rpc.Stub#PASSWORD_PROPERTY * @see javax.xml.rpc.Call#PASSWORD_PROPERTY */ public void setPassword(String password) { this.password = password; } /** * Return the password to specify on the stub or call. */ public String getPassword() { return this.password; } /** * Set the endpoint address to specify on the stub or call. * @see javax.xml.rpc.Stub#ENDPOINT_ADDRESS_PROPERTY * @see javax.xml.rpc.Call#setTargetEndpointAddress */ public void setEndpointAddress(String endpointAddress) { this.endpointAddress = endpointAddress; } /** * Return the endpoint address to specify on the stub or call. */ public String getEndpointAddress() { return this.endpointAddress; } /** * Set the maintain session flag to specify on the stub or call. * @see javax.xml.rpc.Stub#SESSION_MAINTAIN_PROPERTY * @see javax.xml.rpc.Call#SESSION_MAINTAIN_PROPERTY */ public void setMaintainSession(boolean maintainSession) { this.maintainSession = maintainSession; } /** * Return the maintain session flag to specify on the stub or call. */ public boolean isMaintainSession() { return this.maintainSession; } /** * Set custom properties to be set on the stub or call. *

Can be populated with a String "value" (parsed via PropertiesEditor) * or a "props" element in XML bean definitions. * @see javax.xml.rpc.Stub#_setProperty * @see javax.xml.rpc.Call#setProperty */ public void setCustomProperties(Properties customProperties) { CollectionUtils.mergePropertiesIntoMap(customProperties, this.customPropertyMap); } /** * Set custom properties to be set on the stub or call. *

Can be populated with a "map" or "props" element in XML bean definitions. * @see javax.xml.rpc.Stub#_setProperty * @see javax.xml.rpc.Call#setProperty */ public void setCustomPropertyMap(Map customProperties) { if (customProperties != null) { Iterator it = customProperties.entrySet().iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); if (!(entry.getKey() instanceof String)) { throw new IllegalArgumentException( "Illegal property key [" + entry.getKey() + "]: only Strings allowed"); } addCustomProperty((String) entry.getKey(), entry.getValue()); } } } /** * Allow Map access to the custom properties to be set on the stub * or call, with the option to add or override specific entries. *

Useful for specifying entries directly, for example via * "customPropertyMap[myKey]". This is particularly useful for * adding or overriding entries in child bean definitions. */ public Map getCustomPropertyMap() { return this.customPropertyMap; } /** * Add a custom property to this JAX-RPC Stub/Call. * @param name the name of the attribute to expose * @param value the attribute value to expose * @see javax.xml.rpc.Stub#_setProperty * @see javax.xml.rpc.Call#setProperty */ public void addCustomProperty(String name, Object value) { this.customPropertyMap.put(name, value); } /** * Set the interface of the service that this factory should create a proxy for. * This will typically be a non-RMI business interface, although you can also * use an RMI port interface as recommended by JAX-RPC here. *

Calls on the specified service interface will either be translated to the * underlying RMI port interface (in case of a "portInterface" being specified) * or to dynamic calls (using the JAX-RPC Dynamic Invocation Interface). *

The dynamic call mechanism has the advantage that you don't need to * maintain an RMI port interface in addition to an existing non-RMI business * interface. In terms of configuration, specifying the business interface * as "serviceInterface" will be enough; this interceptor will automatically * use dynamic calls in such a scenario. * @see javax.xml.rpc.Service#createCall * @see #setPortInterface */ 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 the JAX-RPC port interface to use. Only needs to be set if a JAX-RPC * port stub should be used instead of the dynamic call mechanism. * See the javadoc of the "serviceInterface" property for more details. *

The interface must be suitable for a JAX-RPC port, that is, it must be * an RMI service interface (that extends java.rmi.Remote). *

NOTE: Check whether your JAX-RPC provider returns thread-safe * port stubs. If not, use the dynamic call mechanism instead, which will * always be thread-safe. In particular, do not use JAX-RPC port stubs * with Apache Axis, whose port stubs are known to be non-thread-safe. * @see javax.xml.rpc.Service#getPort * @see java.rmi.Remote * @see #setServiceInterface */ public void setPortInterface(Class portInterface) { if (portInterface != null && (!portInterface.isInterface() || !Remote.class.isAssignableFrom(portInterface))) { throw new IllegalArgumentException( "portInterface must be an interface derived from [java.rmi.Remote]"); } this.portInterface = portInterface; } /** * Return the JAX-RPC port interface to use. */ public Class getPortInterface() { return this.portInterface; } /** * Set whether to look up the JAX-RPC service on startup. *

Default is "true". Turn this flag off to allow for late start * of the target server. In this case, the JAX-RPC service will be * lazily fetched on first access. */ public void setLookupServiceOnStartup(boolean lookupServiceOnStartup) { this.lookupServiceOnStartup = lookupServiceOnStartup; } /** * Set whether to refresh the JAX-RPC service on connect failure, * that is, whenever a JAX-RPC invocation throws a RemoteException. *

Default is "false", keeping a reference to the JAX-RPC service * in any case, retrying the next invocation on the same service * even in case of failure. Turn this flag on to reinitialize the * entire service in case of connect failures. */ public void setRefreshServiceAfterConnectFailure(boolean refreshServiceAfterConnectFailure) { this.refreshServiceAfterConnectFailure = refreshServiceAfterConnectFailure; } /** * Prepares the JAX-RPC service and port if the "lookupServiceOnStartup" * is turned on (which it is by default). */ public void afterPropertiesSet() throws ServiceException { if (this.lookupServiceOnStartup) { prepare(); } } /** * Create and initialize the JAX-RPC service for the specified port. *

Prepares a JAX-RPC stub if possible (if an RMI interface is available); * falls back to JAX-RPC dynamic calls else. Using dynamic calls can be * enforced through overriding alwaysUseJaxRpcCall to return true. *

postProcessJaxRpcService and postProcessPortStub * hooks are available for customization in subclasses. When using dynamic calls, * each can be post-processed via postProcessJaxRpcCall. *

Note: As of Spring 2.1, this method will always throw * RemoteLookupFailureException and not declare ServiceException anymore. * @throws ServiceException in case of service initialization failure * @throws RemoteLookupFailureException if port stub creation failed * @see #alwaysUseJaxRpcCall * @see #postProcessJaxRpcService * @see #postProcessPortStub * @see #postProcessJaxRpcCall */ public void prepare() throws ServiceException, RemoteLookupFailureException { if (getPortName() == null) { throw new IllegalArgumentException("Property 'portName' is required"); } synchronized (this.preparationMonitor) { this.serviceToUse = null; // Cache the QName for the port. this.portQName = getQName(getPortName()); Service service = getJaxRpcService(); if (service == null) { service = createJaxRpcService(); } else { postProcessJaxRpcService(service); } Class portInterface = getPortInterface(); if (portInterface != null && !alwaysUseJaxRpcCall()) { // JAX-RPC-compliant port interface -> using JAX-RPC stub for port. if (logger.isDebugEnabled()) { logger.debug("Creating JAX-RPC proxy for JAX-RPC port [" + this.portQName + "], using port interface [" + portInterface.getName() + "]"); } Remote remoteObj = service.getPort(this.portQName, portInterface); if (logger.isDebugEnabled()) { Class serviceInterface = getServiceInterface(); if (serviceInterface != null) { boolean isImpl = serviceInterface.isInstance(remoteObj); logger.debug("Using service interface [" + serviceInterface.getName() + "] for JAX-RPC port [" + this.portQName + "] - " + (!isImpl ? "not" : "") + " directly implemented"); } } if (!(remoteObj instanceof Stub)) { throw new RemoteLookupFailureException("Port stub of class [" + remoteObj.getClass().getName() + "] is not a valid JAX-RPC stub: it does not implement interface [javax.xml.rpc.Stub]"); } Stub stub = (Stub) remoteObj; // Apply properties to JAX-RPC stub. preparePortStub(stub); // Allow for custom post-processing in subclasses. postProcessPortStub(stub); this.portStub = remoteObj; } else { // No JAX-RPC-compliant port interface -> using JAX-RPC dynamic calls. if (logger.isDebugEnabled()) { logger.debug("Using JAX-RPC dynamic calls for JAX-RPC port [" + this.portQName + "]"); } } this.serviceToUse = service; } } /** * Return whether to always use JAX-RPC dynamic calls. * Called by afterPropertiesSet. *

Default is "false"; if an RMI interface is specified as "portInterface" * or "serviceInterface", it will be used to create a JAX-RPC port stub. *

Can be overridden to enforce the use of the JAX-RPC Call API, * for example if there is a need to customize at the Call level. * This just necessary if you you want to use an RMI interface as * "serviceInterface", though; in case of only a non-RMI interface being * available, this interceptor will fall back to the Call API anyway. * @see #postProcessJaxRpcCall */ protected boolean alwaysUseJaxRpcCall() { return false; } /** * Reset the prepared service of this interceptor, * allowing for reinitialization on next access. */ protected void reset() { synchronized (this.preparationMonitor) { this.serviceToUse = null; } } /** * Return whether this client interceptor has already been prepared, * i.e. has already looked up the JAX-RPC service and port. */ protected boolean isPrepared() { synchronized (this.preparationMonitor) { return (this.serviceToUse != null); } } /** * Return the prepared QName for the port. * @see #setPortName * @see #getQName */ protected QName getPortQName() { return this.portQName; } /** * Prepare the given JAX-RPC port stub, applying properties to it. * Called by afterPropertiesSet. *

Just applied when actually creating a JAX-RPC port stub, * in case of a specified JAX-RPC-compliant port interface. * Else, JAX-RPC dynamic calls will be used. * @param stub the current JAX-RPC port stub * @see #afterPropertiesSet * @see #setUsername * @see #setPassword * @see #setEndpointAddress * @see #setMaintainSession * @see #setCustomProperties * @see #setPortInterface * @see #prepareJaxRpcCall */ protected void preparePortStub(Stub stub) { String username = getUsername(); if (username != null) { stub._setProperty(Stub.USERNAME_PROPERTY, username); } String password = getPassword(); if (password != null) { stub._setProperty(Stub.PASSWORD_PROPERTY, password); } String endpointAddress = getEndpointAddress(); if (endpointAddress != null) { stub._setProperty(Stub.ENDPOINT_ADDRESS_PROPERTY, endpointAddress); } if (isMaintainSession()) { stub._setProperty(Stub.SESSION_MAINTAIN_PROPERTY, Boolean.TRUE); } if (this.customPropertyMap != null) { for (Iterator it = this.customPropertyMap.keySet().iterator(); it.hasNext();) { String key = (String) it.next(); stub._setProperty(key, this.customPropertyMap.get(key)); } } } /** * Post-process the given JAX-RPC port stub. * Default implementation is empty. Called by prepare. *

Just applied when actually creating a JAX-RPC port stub, * in case of a specified JAX-RPC-compliant port interface. * Else, JAX-RPC dynamic calls will be used. * @param stub the current JAX-RPC port stub * (can be cast to an implementation-specific class if necessary) * @see #prepare * @see #setPortInterface * @see #postProcessJaxRpcCall */ protected void postProcessPortStub(Stub stub) { } /** * Return the underlying JAX-RPC port stub that this interceptor delegates to * for each method invocation on the proxy. */ protected Remote getPortStub() { return this.portStub; } /** * Translates the method invocation into a JAX-RPC service invocation. *

Prepares the service on the fly, if necessary, in case of lazy * lookup or a connect failure having happened. * @see #prepare() * @see #doInvoke */ public Object invoke(MethodInvocation invocation) throws Throwable { if (AopUtils.isToStringMethod(invocation.getMethod())) { return "JAX-RPC proxy for port [" + getPortName() + "] of service [" + getServiceName() + "]"; } // Lazily prepare service and stub if appropriate. if (!this.lookupServiceOnStartup || this.refreshServiceAfterConnectFailure) { synchronized (this.preparationMonitor) { if (!isPrepared()) { try { prepare(); } catch (ServiceException ex) { throw new RemoteLookupFailureException("Preparation of JAX-RPC service failed", ex); } } } } else { if (!isPrepared()) { throw new IllegalStateException("JaxRpcClientInterceptor is not properly initialized - " + "invoke 'prepare' before attempting any operations"); } } return doInvoke(invocation); } /** * Perform a JAX-RPC service invocation based on the given method invocation. *

Uses traditional RMI stub invocation if a JAX-RPC port stub is available; * falls back to JAX-RPC dynamic calls else. * @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, java.rmi.Remote) * @see #performJaxRpcCall(org.aopalliance.intercept.MethodInvocation, javax.xml.rpc.Service) */ protected Object doInvoke(MethodInvocation invocation) throws Throwable { Remote stub = getPortStub(); if (stub != null) { // JAX-RPC port stub available -> traditional RMI stub invocation. if (logger.isTraceEnabled()) { logger.trace("Invoking operation '" + invocation.getMethod().getName() + "' on JAX-RPC port stub"); } return doInvoke(invocation, stub); } else { // No JAX-RPC stub -> using JAX-RPC dynamic calls. if (logger.isTraceEnabled()) { logger.trace("Invoking operation '" + invocation.getMethod().getName() + "' as JAX-RPC dynamic call"); } return performJaxRpcCall(invocation); } } /** * Perform a JAX-RPC service invocation based 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() * @see #doInvoke(org.aopalliance.intercept.MethodInvocation, java.rmi.Remote) * @see #performJaxRpcCall */ protected Object doInvoke(MethodInvocation invocation, Remote portStub) throws Throwable { try { return RmiClientInterceptorUtils.doInvoke(invocation, portStub); } catch (InvocationTargetException ex) { Throwable targetEx = ex.getTargetException(); if (targetEx instanceof RemoteException) { RemoteException rex = (RemoteException) targetEx; boolean isConnectFailure = isConnectFailure(rex); if (isConnectFailure && this.refreshServiceAfterConnectFailure) { reset(); } throw RmiClientInterceptorUtils.convertRmiAccessException( invocation.getMethod(), rex, isConnectFailure, portQName.toString()); } else if (targetEx instanceof JAXRPCException) { throw new RemoteProxyFailureException("Invalid call on JAX-RPC port stub", targetEx); } else { throw targetEx; } } } /** * @deprecated as of Spring 2.0.3, in favor of the performJaxRpcCall * variant with an explicit Service argument * @see #performJaxRpcCall(org.aopalliance.intercept.MethodInvocation, javax.xml.rpc.Service) */ protected Object performJaxRpcCall(MethodInvocation invocation) throws Throwable { return performJaxRpcCall(invocation, this.serviceToUse); } /** * Perform a JAX-RPC dynamic call for the given AOP method invocation. * Delegates to prepareJaxRpcCall and * postProcessJaxRpcCall for setting up the call object. *

Default implementation uses method name as JAX-RPC operation name * and method arguments as arguments for the JAX-RPC call. Can be * overridden in subclasses for custom operation names and/or arguments. * @param invocation the current AOP MethodInvocation that should * be converted to a JAX-RPC call * @return the return value of the invocation, if any * @throws Throwable the exception thrown by the invocation, if any * @see #getPortQName * @see #prepareJaxRpcCall * @see #postProcessJaxRpcCall */ protected Object performJaxRpcCall(MethodInvocation invocation, Service service) throws Throwable { QName portQName = getPortQName(); // Create JAX-RPC call object, using the method name as operation name. // Synchronized because of non-thread-safe Axis implementation! Call call = null; synchronized (service) { call = service.createCall(portQName, invocation.getMethod().getName()); } // Apply properties to JAX-RPC stub. prepareJaxRpcCall(call); // Allow for custom post-processing in subclasses. postProcessJaxRpcCall(call, invocation); // Perform actual invocation. try { return call.invoke(invocation.getArguments()); } catch (RemoteException ex) { boolean isConnectFailure = isConnectFailure(ex); if (isConnectFailure && this.refreshServiceAfterConnectFailure) { reset(); } throw RmiClientInterceptorUtils.convertRmiAccessException( invocation.getMethod(), ex, isConnectFailure, portQName.toString()); } catch (JAXRPCException ex) { throw new RemoteProxyFailureException("Invalid JAX-RPC call configuration", ex); } } /** * Prepare the given JAX-RPC call, applying properties to it. * Called by invoke. *

Just applied when actually using JAX-RPC dynamic calls, * i.e. if no JAX-RPC-compliant port interface was specified. * Else, a JAX-RPC port stub will be used. * @param call the current JAX-RPC call object * @see #invoke * @see #setUsername * @see #setPassword * @see #setEndpointAddress * @see #setMaintainSession * @see #setCustomProperties * @see #setPortInterface * @see #preparePortStub */ protected void prepareJaxRpcCall(Call call) { String username = getUsername(); if (username != null) { call.setProperty(Call.USERNAME_PROPERTY, username); } String password = getPassword(); if (password != null) { call.setProperty(Call.PASSWORD_PROPERTY, password); } String endpointAddress = getEndpointAddress(); if (endpointAddress != null) { call.setTargetEndpointAddress(endpointAddress); } if (isMaintainSession()) { call.setProperty(Call.SESSION_MAINTAIN_PROPERTY, Boolean.TRUE); } if (this.customPropertyMap != null) { for (Iterator it = this.customPropertyMap.keySet().iterator(); it.hasNext();) { String key = (String) it.next(); call.setProperty(key, this.customPropertyMap.get(key)); } } } /** * Post-process the given JAX-RPC call. * Default implementation is empty. Called by invoke. *

Just applied when actually using JAX-RPC dynamic calls, * that is, if no JAX-RPC-compliant port interface was specified. * Else, a JAX-RPC port stub will be used. * @param call the current JAX-RPC call object * (can be cast to an implementation-specific class if necessary) * @param invocation the current AOP MethodInvocation that the call was * created for (can be used to check method name, method parameters * and/or passed-in arguments) * @see #invoke * @see #setPortInterface * @see #postProcessPortStub */ protected void postProcessJaxRpcCall(Call call, MethodInvocation invocation) { } /** * Determine whether the given RMI exception indicates a connect failure. *

The default implementation always returns true, * assuming that the JAX-RPC provider only throws RemoteException * in case of connect failures. * @param ex the RMI exception to check * @return whether the exception should be treated as connect failure * @see org.springframework.remoting.rmi.RmiClientInterceptorUtils#isConnectFailure */ protected boolean isConnectFailure(RemoteException ex) { return true; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy