1. /*
  2. * Copyright 2002-2004 the original author or authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.springframework.remoting.rmi;
  17. import java.io.IOException;
  18. import java.lang.reflect.InvocationTargetException;
  19. import java.lang.reflect.Method;
  20. import java.rmi.Naming;
  21. import java.rmi.NotBoundException;
  22. import java.rmi.Remote;
  23. import java.rmi.RemoteException;
  24. import java.util.Arrays;
  25. import org.aopalliance.intercept.MethodInterceptor;
  26. import org.aopalliance.intercept.MethodInvocation;
  27. import org.springframework.beans.factory.InitializingBean;
  28. import org.springframework.remoting.RemoteAccessException;
  29. import org.springframework.remoting.support.RemoteInvocation;
  30. import org.springframework.remoting.support.UrlBasedRemoteAccessor;
  31. /**
  32. * Interceptor for accessing conventional RMI services or RMI invokers.
  33. * The service URL must be a valid RMI URL like "rmi://localhost:1099/myservice".
  34. *
  35. * <p>RMI invokers work at the RmiInvocationHandler level, needing only one stub
  36. * for any service. Service interfaces do not have to extend java.rmi.Remote or
  37. * throw RemoteException; Spring's unchecked RemoteAccessException will be thrown on
  38. * remote invocation failure. Of course, in and out parameters have to be serializable.
  39. *
  40. * <p>With conventional RMI services, this invoker is typically used with the RMI
  41. * service interface. Alternatively, this invoker can also proxy a remote RMI service
  42. * with a matching non-RMI business interface, i.e. an interface that mirrors the RMI
  43. * service methods but does not declare RemoteExceptions. In the latter case,
  44. * RemoteExceptions thrown by the RMI stub will automatically get converted to
  45. * Spring's unchecked RemoteAccessException.
  46. *
  47. * @author Juergen Hoeller
  48. * @since 29.09.2003
  49. * @see RmiServiceExporter
  50. * @see RmiInvocationHandler
  51. * @see org.springframework.remoting.RemoteAccessException
  52. * @see java.rmi.RemoteException
  53. * @see java.rmi.Remote
  54. */
  55. public class RmiClientInterceptor extends UrlBasedRemoteAccessor implements MethodInterceptor, InitializingBean {
  56. private Remote rmiProxy;
  57. public void afterPropertiesSet() throws IOException, NotBoundException {
  58. if (getServiceUrl() == null) {
  59. throw new IllegalArgumentException("serviceUrl is required");
  60. }
  61. Remote remoteObj = createRmiProxy();
  62. if (remoteObj instanceof RmiInvocationHandler) {
  63. logger.info("RMI object [" + getServiceUrl() + "] is an RMI invoker");
  64. }
  65. else if (getServiceInterface() != null) {
  66. boolean isImpl = getServiceInterface().isInstance(remoteObj);
  67. logger.info("Using service interface [" + getServiceInterface().getName() + "] for RMI object [" +
  68. getServiceUrl() + "] - " + (!isImpl ? "not " : "") + "directly implemented");
  69. }
  70. this.rmiProxy = remoteObj;
  71. }
  72. /**
  73. * Create the RMI proxy. Default implementation looks up the service URL
  74. * via java.rmi.Naming. Can be overridden in subclasses.
  75. * @see java.rmi.Naming#lookup
  76. */
  77. protected Remote createRmiProxy() throws IOException, NotBoundException {
  78. return Naming.lookup(getServiceUrl());
  79. }
  80. /**
  81. * Return the underlying RMI proxy that this interceptor delegates to.
  82. */
  83. protected Remote getRmiProxy() {
  84. return rmiProxy;
  85. }
  86. public Object invoke(MethodInvocation invocation) throws Throwable {
  87. try {
  88. if (this.rmiProxy instanceof RmiInvocationHandler) {
  89. RmiInvocationHandler invocationHandler = (RmiInvocationHandler) this.rmiProxy;
  90. return invoke(invocation, invocationHandler);
  91. }
  92. else {
  93. Method method = invocation.getMethod();
  94. if (method.getDeclaringClass().isInstance(this.rmiProxy)) {
  95. // directly implemented
  96. return method.invoke(this.rmiProxy, invocation.getArguments());
  97. }
  98. else {
  99. // not directly implemented
  100. Method proxyMethod = this.rmiProxy.getClass().getMethod(method.getName(), method.getParameterTypes());
  101. return proxyMethod.invoke(this.rmiProxy, invocation.getArguments());
  102. }
  103. }
  104. }
  105. catch (RemoteException ex) {
  106. logger.debug("RMI invoker for service [" + getServiceUrl() + "] threw exception", ex);
  107. if (!Arrays.asList(invocation.getMethod().getExceptionTypes()).contains(RemoteException.class)) {
  108. throw new RemoteAccessException("Cannot access RMI invoker for [" + getServiceUrl() + "]", ex);
  109. }
  110. else {
  111. throw ex;
  112. }
  113. }
  114. catch (InvocationTargetException ex) {
  115. Throwable targetException = ex.getTargetException();
  116. logger.debug("RMI method of service [" + getServiceUrl() + "] threw exception", targetException);
  117. if (targetException instanceof RemoteException &&
  118. !Arrays.asList(invocation.getMethod().getExceptionTypes()).contains(RemoteException.class)) {
  119. throw new RemoteAccessException("Cannot access RMI service [" + getServiceUrl() + "]", targetException);
  120. }
  121. else {
  122. throw targetException;
  123. }
  124. }
  125. catch (RuntimeException ex) {
  126. throw new RemoteAccessException("Failed to invoke RMI service [" + getServiceUrl() + "]", ex);
  127. }
  128. }
  129. /**
  130. * Apply the given AOP method invocation to the given RmiInvocationHandler.
  131. * The default implementation calls invoke with a plain RemoteInvocation.
  132. * <p>Can be overridden in subclasses to provide custom RemoteInvocation
  133. * subclasses, containing additional invocation parameters like user
  134. * credentials. Can also process the returned result object.
  135. * @param methodInvocation the current AOP method invocation
  136. * @param invocationHandler the RmiInvocationHandler to apply the invocation to
  137. * @return the invocation result
  138. * @throws NoSuchMethodException if the method name could not be resolved
  139. * @throws IllegalAccessException if the method could not be accessed
  140. * @throws InvocationTargetException if the method invocation resulted in an exception
  141. * @see org.springframework.remoting.support.RemoteInvocation
  142. */
  143. protected Object invoke(MethodInvocation methodInvocation, RmiInvocationHandler invocationHandler)
  144. throws RemoteException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
  145. return invocationHandler.invoke(new RemoteInvocation(methodInvocation));
  146. }
  147. }