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.lang.reflect.InvocationTargetException;
  18. import java.lang.reflect.Method;
  19. import java.rmi.NotBoundException;
  20. import java.rmi.Remote;
  21. import java.rmi.RemoteException;
  22. import java.rmi.registry.LocateRegistry;
  23. import java.rmi.registry.Registry;
  24. import java.rmi.server.RMIClientSocketFactory;
  25. import java.rmi.server.RMIServerSocketFactory;
  26. import java.rmi.server.UnicastRemoteObject;
  27. import org.apache.commons.logging.Log;
  28. import org.apache.commons.logging.LogFactory;
  29. import org.springframework.beans.factory.DisposableBean;
  30. import org.springframework.beans.factory.InitializingBean;
  31. import org.springframework.remoting.support.RemoteExporter;
  32. import org.springframework.remoting.support.RemoteInvocation;
  33. /**
  34. * RMI exporter that exposes the specified service as RMI object with the specified
  35. * name. Such services can be accessed via plain RMI or via RmiProxyFactoryBean.
  36. * Also supports exposing any non-RMI service via RMI invokers, to be accessed via
  37. * RmiClientInterceptor/RmiProxyFactoryBean's automatic detection of such invokers.
  38. *
  39. * <p>With an RMI invoker, RMI communication works on the RmiInvocationHandler
  40. * level, needing only one stub for any service. Service interfaces do not have to
  41. * extend java.rmi.Remote or throw RemoteException on all methods, but in and out
  42. * parameters have to be serializable.
  43. *
  44. * <p>The major advantage of RMI, compared to Hessian and Burlap, is serialization.
  45. * Effectively, any serializable Java object can be transported without hassle.
  46. * Hessian and Burlap have their own (de-)serialization mechanisms, but are
  47. * HTTP-based and thus much easier to setup than RMI.
  48. *
  49. * <p>Note: be careful when exporting objects to servers running on multi-homed machines
  50. * or when changing from Windows to certain Linux-based environments. Changes might
  51. * occur as to where (on what network device or IP-address) objects will be bound.
  52. * Changing the IP-address can be done by specifying the system property
  53. * <code>java.rmi.server.host</code> (in your application server for example).
  54. * Exceptions resulting from proxies with wrong location information typically include
  55. * messages like 'Could not connect to 127.0.0.1'.
  56. *
  57. * @author Juergen Hoeller
  58. * @since 13.05.2003
  59. * @see RmiClientInterceptor
  60. * @see RmiProxyFactoryBean
  61. * @see org.springframework.remoting.caucho.HessianServiceExporter
  62. * @see org.springframework.remoting.caucho.BurlapServiceExporter
  63. */
  64. public class RmiServiceExporter extends RemoteExporter implements InitializingBean, DisposableBean {
  65. protected final Log logger = LogFactory.getLog(getClass());
  66. private String serviceName;
  67. private int servicePort = 0; // anonymous port
  68. private int registryPort = Registry.REGISTRY_PORT;
  69. private RMIClientSocketFactory clientSocketFactory;
  70. private RMIServerSocketFactory serverSocketFactory;
  71. private Remote exportedObject;
  72. /**
  73. * Set the name of the exported RMI service,
  74. * i.e. rmi://localhost:port/NAME
  75. */
  76. public void setServiceName(String serviceName) {
  77. this.serviceName = serviceName;
  78. }
  79. /**
  80. * Set the port that the exported RMI service will use.
  81. * Default is 0 (anonymous port).
  82. */
  83. public void setServicePort(int servicePort) {
  84. this.servicePort = servicePort;
  85. }
  86. /**
  87. * Set the port of the registry for the exported RMI service,
  88. * i.e. rmi://localhost:PORT/name
  89. * Default is Registry.REGISTRY_PORT (1099).
  90. */
  91. public void setRegistryPort(int registryPort) {
  92. this.registryPort = registryPort;
  93. }
  94. /**
  95. * Set a custom RMI client socket factory to use for exporting.
  96. * If the given object also implement RMIServerSocketFactory,
  97. * it will automatically be registered as server socket factory too.
  98. * @see #setServerSocketFactory
  99. * @see UnicastRemoteObject#exportObject(Remote, int, RMIClientSocketFactory, RMIServerSocketFactory)
  100. */
  101. public void setClientSocketFactory(RMIClientSocketFactory clientSocketFactory) {
  102. this.clientSocketFactory = clientSocketFactory;
  103. }
  104. /**
  105. * Set a custom RMI server socket factory to use for exporting.
  106. * @see #setClientSocketFactory
  107. */
  108. public void setServerSocketFactory(RMIServerSocketFactory serverSocketFactory) {
  109. this.serverSocketFactory = serverSocketFactory;
  110. }
  111. /**
  112. * Register the service as RMI object.
  113. * Creates an RMI registry on the specified port if none exists.
  114. */
  115. public void afterPropertiesSet() throws Exception {
  116. super.afterPropertiesSet();
  117. if (this.serviceName == null) {
  118. throw new IllegalArgumentException("serviceName is required");
  119. }
  120. if (this.clientSocketFactory instanceof RMIServerSocketFactory) {
  121. this.serverSocketFactory = (RMIServerSocketFactory) this.clientSocketFactory;
  122. }
  123. if ((this.clientSocketFactory != null && this.serverSocketFactory == null) ||
  124. (this.clientSocketFactory == null && this.serverSocketFactory != null)) {
  125. throw new IllegalArgumentException("Both RMIClientSocketFactory and RMIServerSocketFactory or none required");
  126. }
  127. Registry registry = null;
  128. logger.info("Looking for RMI registry at port '" + this.registryPort + "'");
  129. try {
  130. // retrieve registry
  131. registry = LocateRegistry.getRegistry(this.registryPort);
  132. registry.list();
  133. }
  134. catch (RemoteException ex) {
  135. logger.debug("RMI registry access threw exception", ex);
  136. logger.warn("Could not detect RMI registry - creating new one");
  137. // assume no registry found -> create new one
  138. registry = LocateRegistry.createRegistry(this.registryPort);
  139. }
  140. // determine remote object
  141. if (getService() instanceof Remote) {
  142. // conventional RMI service
  143. this.exportedObject = (Remote) getService();
  144. }
  145. else {
  146. // RMI invoker
  147. logger.info("RMI object '" + this.serviceName + "' is an RMI invoker");
  148. this.exportedObject = new RmiInvocationWrapper(getProxyForService(), this);
  149. }
  150. // export remote object and bind it to registry
  151. logger.info("Binding RMI service '" + this.serviceName + "' to registry at port '" + this.registryPort + "'");
  152. if (this.clientSocketFactory != null) {
  153. UnicastRemoteObject.exportObject(this.exportedObject, this.servicePort,
  154. this.clientSocketFactory, this.serverSocketFactory);
  155. }
  156. else {
  157. UnicastRemoteObject.exportObject(this.exportedObject, this.servicePort);
  158. }
  159. registry.rebind(this.serviceName, this.exportedObject);
  160. }
  161. /**
  162. * Apply the given remote invocation to the given target object.
  163. * The default implementation performs a plain method invocation.
  164. * <p>Can be overridden in subclasses for custom invocation behavior,
  165. * possibly for applying additional invocation parameters from a
  166. * custom RemoteInvocation subclass. Will typically match a corresponding
  167. * custom invoke implementation in RmiClientInterceptor/RmiProxyFactoryBean.
  168. * @param invocation the remote invocation
  169. * @param targetObject the target object to apply the invocation to
  170. * @return the invocation result
  171. * @throws NoSuchMethodException if the method name could not be resolved
  172. * @throws IllegalAccessException if the method could not be accessed
  173. * @throws InvocationTargetException if the method invocation resulted in an exception
  174. * @see RmiClientInterceptor#invoke
  175. */
  176. protected Object invoke(RemoteInvocation invocation, Object targetObject)
  177. throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
  178. Method method = targetObject.getClass().getMethod(invocation.getMethodName(),
  179. invocation.getParameterTypes());
  180. return method.invoke(targetObject, invocation.getArguments());
  181. }
  182. public void destroy() throws RemoteException, NotBoundException {
  183. logger.info("Unbinding RMI service '" + this.serviceName + "' from registry at port '" + this.registryPort + "'");
  184. Registry registry = LocateRegistry.getRegistry(this.registryPort);
  185. registry.unbind(this.serviceName);
  186. UnicastRemoteObject.unexportObject(this.exportedObject, true);
  187. }
  188. }