|
Spring Framework example source code file (JaxRpcPortClientInterceptor.java)
The Spring Framework JaxRpcPortClientInterceptor.java source code
/*
* 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.lang.reflect.Method;
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 javax.xml.rpc.soap.SOAPFaultException;
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;
import org.springframework.util.ReflectionUtils;
/**
* {@link org.aopalliance.intercept.MethodInterceptor} 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}).
*
* <p>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.
*
* <p>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.
*
* <p>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, {@link 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.
* <p>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.
* <p>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.
* <p>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.
* <p>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).
* <p>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.
* <p>The interface must be suitable for a JAX-RPC port, that is, it must be
* an RMI service interface (that extends <code>java.rmi.Remote).
* <p>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.
* <p>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.
* <p>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() {
if (this.lookupServiceOnStartup) {
prepare();
}
}
/**
* Create and initialize the JAX-RPC service for the specified port.
* <p>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 {@link #alwaysUseJaxRpcCall} to return <code>true.
* <p>{@link #postProcessJaxRpcService} and {@link #postProcessPortStub}
* hooks are available for customization in subclasses. When using dynamic calls,
* each can be post-processed via {@link #postProcessJaxRpcCall}.
* @throws RemoteLookupFailureException if service initialization or port stub creation failed
*/
public void prepare() throws 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());
try {
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;
}
catch (ServiceException ex) {
throw new RemoteLookupFailureException(
"Failed to initialize service for JAX-RPC port [" + this.portQName + "]", ex);
}
}
}
/**
* Return whether to always use JAX-RPC dynamic calls.
* Called by <code>afterPropertiesSet.
* <p>Default is "false"; if an RMI interface is specified as "portInterface"
* or "serviceInterface", it will be used to create a JAX-RPC port stub.
* <p>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 final QName getPortQName() {
return this.portQName;
}
/**
* Prepare the given JAX-RPC port stub, applying properties to it.
* Called by {@link #afterPropertiesSet}.
* <p>Just applied when actually creating a JAX-RPC port stub, in case of a
* compliant port interface. Else, JAX-RPC dynamic calls will be used.
* @param stub the current JAX-RPC port stub
* @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. Called by {@link #prepare}.
* <p>The default implementation is empty.
* <p>Just applied when actually creating a JAX-RPC port stub, in case of a
* 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 #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.
* <p>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 necessary.
synchronized (this.preparationMonitor) {
if (!isPrepared()) {
prepare();
}
}
return doInvoke(invocation);
}
/**
* Perform a JAX-RPC service invocation based on the given method invocation.
* <p>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();
try {
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, this.serviceToUse);
}
}
catch (RemoteException ex) {
throw handleRemoteException(invocation.getMethod(), ex);
}
catch (SOAPFaultException ex) {
throw new JaxRpcSoapFaultException(ex);
}
catch (JAXRPCException ex) {
throw new RemoteProxyFailureException("Invalid JAX-RPC call configuration", ex);
}
}
/**
* Perform a JAX-RPC 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()
* @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) {
throw ex.getTargetException();
}
}
/**
* Perform a JAX-RPC dynamic call for the given AOP method invocation.
* Delegates to {@link #prepareJaxRpcCall} and
* {@link #postProcessJaxRpcCall} for setting up the call object.
* <p>The 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
* @param service the JAX-RPC Service to use for the call
* @return the return value of the invocation, if any
* @throws Throwable the exception thrown by the invocation, if any
* @see #prepareJaxRpcCall
* @see #postProcessJaxRpcCall
*/
protected Object performJaxRpcCall(MethodInvocation invocation, Service service) throws Throwable {
Method method = invocation.getMethod();
QName portQName = this.portQName;
// 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, method.getName());
}
// Apply properties to JAX-RPC stub.
prepareJaxRpcCall(call);
// Allow for custom post-processing in subclasses.
postProcessJaxRpcCall(call, invocation);
// Perform actual invocation.
return call.invoke(invocation.getArguments());
}
/**
* Prepare the given JAX-RPC call, applying properties to it. Called by {@link #invoke}.
* <p>Just applied when actually using JAX-RPC dynamic calls, i.e. if no compliant
* port interface was specified. Else, a JAX-RPC port stub will be used.
* @param call the current JAX-RPC call object
* @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. Called by {@link #invoke}.
* <p>The default implementation is empty.
* <p>Just applied when actually using JAX-RPC dynamic calls, i.e. if no 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 #setPortInterface
* @see #postProcessPortStub
*/
protected void postProcessJaxRpcCall(Call call, MethodInvocation invocation) {
}
/**
* Handle the given RemoteException that was thrown from a JAX-RPC port stub
* or JAX-RPC call invocation.
* @param method the service interface method that we invoked
* @param ex the original RemoteException
* @return the exception to rethrow (may be the original RemoteException
* or an extracted/wrapped exception, but never <code>null)
*/
protected Throwable handleRemoteException(Method method, RemoteException ex) {
boolean isConnectFailure = isConnectFailure(ex);
if (isConnectFailure && this.refreshServiceAfterConnectFailure) {
reset();
}
Throwable cause = ex.getCause();
if (cause != null && ReflectionUtils.declaresException(method, cause.getClass())) {
if (logger.isDebugEnabled()) {
logger.debug("Rethrowing wrapped exception of type [" + cause.getClass().getName() + "] as-is");
}
// Declared on the service interface: probably a wrapped business exception.
return ex.getCause();
}
else {
// Throw either a RemoteAccessException or the original RemoteException,
// depending on what the service interface declares.
return RmiClientInterceptorUtils.convertRmiAccessException(
method, ex, isConnectFailure, portQName.toString());
}
}
/**
* Determine whether the given RMI exception indicates a connect failure.
* <p>The default implementation returns
Other Spring Framework examples (source code examples)Here is a short list of links related to this Spring Framework JaxRpcPortClientInterceptor.java source code file: |
... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2024 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.