001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.remoting.rmi;
018:
019: import java.rmi.AlreadyBoundException;
020: import java.rmi.NoSuchObjectException;
021: import java.rmi.NotBoundException;
022: import java.rmi.Remote;
023: import java.rmi.RemoteException;
024: import java.rmi.registry.LocateRegistry;
025: import java.rmi.registry.Registry;
026: import java.rmi.server.RMIClientSocketFactory;
027: import java.rmi.server.RMIServerSocketFactory;
028: import java.rmi.server.UnicastRemoteObject;
029:
030: import org.springframework.beans.factory.DisposableBean;
031: import org.springframework.beans.factory.InitializingBean;
032:
033: /**
034: * RMI exporter that exposes the specified service as RMI object with the specified name.
035: * Such services can be accessed via plain RMI or via {@link RmiProxyFactoryBean}.
036: * Also supports exposing any non-RMI service via RMI invokers, to be accessed via
037: * {@link RmiClientInterceptor} / {@link RmiProxyFactoryBean}'s automatic detection
038: * of such invokers.
039: *
040: * <p>With an RMI invoker, RMI communication works on the {@link RmiInvocationHandler}
041: * level, needing only one stub for any service. Service interfaces do not have to
042: * extend <code>java.rmi.Remote</code> or throw <code>java.rmi.RemoteException</code>
043: * on all methods, but in and out parameters have to be serializable.
044: *
045: * <p>The major advantage of RMI, compared to Hessian and Burlap, is serialization.
046: * Effectively, any serializable Java object can be transported without hassle.
047: * Hessian and Burlap have their own (de-)serialization mechanisms, but are
048: * HTTP-based and thus much easier to setup than RMI. Alternatively, consider
049: * Spring's HTTP invoker to combine Java serialization with HTTP-based transport.
050: *
051: * <p>Note: RMI makes a best-effort attempt to obtain the fully qualified host name.
052: * If one cannot be determined, it will fall back and use the IP address. Depending
053: * on your network configuration, in some cases it will resolve the IP to the loopback
054: * address. To ensure that RMI will use the host name bound to the correct network
055: * interface, you should pass the <code>java.rmi.server.hostname</code> property to the
056: * JVM that will export the registry and/or the service using the "-D" JVM argument.
057: * For example: <code>-Djava.rmi.server.hostname=myserver.com</code>
058: *
059: * @author Juergen Hoeller
060: * @since 13.05.2003
061: * @see RmiClientInterceptor
062: * @see RmiProxyFactoryBean
063: * @see java.rmi.Remote
064: * @see java.rmi.RemoteException
065: * @see org.springframework.remoting.caucho.HessianServiceExporter
066: * @see org.springframework.remoting.caucho.BurlapServiceExporter
067: * @see org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter
068: */
069: public class RmiServiceExporter extends RmiBasedExporter implements
070: InitializingBean, DisposableBean {
071:
072: private String serviceName;
073:
074: private int servicePort = 0; // anonymous port
075:
076: private RMIClientSocketFactory clientSocketFactory;
077:
078: private RMIServerSocketFactory serverSocketFactory;
079:
080: private Registry registry;
081:
082: private String registryHost;
083:
084: private int registryPort = Registry.REGISTRY_PORT;
085:
086: private RMIClientSocketFactory registryClientSocketFactory;
087:
088: private RMIServerSocketFactory registryServerSocketFactory;
089:
090: private boolean alwaysCreateRegistry = false;
091:
092: private boolean replaceExistingBinding = true;
093:
094: private Remote exportedObject;
095:
096: /**
097: * Set the name of the exported RMI service,
098: * i.e. <code>rmi://host:port/NAME</code>
099: */
100: public void setServiceName(String serviceName) {
101: this .serviceName = serviceName;
102: }
103:
104: /**
105: * Set the port that the exported RMI service will use.
106: * <p>Default is 0 (anonymous port).
107: */
108: public void setServicePort(int servicePort) {
109: this .servicePort = servicePort;
110: }
111:
112: /**
113: * Set a custom RMI client socket factory to use for exporting the service.
114: * <p>If the given object also implements <code>java.rmi.server.RMIServerSocketFactory</code>,
115: * it will automatically be registered as server socket factory too.
116: * @see #setServerSocketFactory
117: * @see java.rmi.server.RMIClientSocketFactory
118: * @see java.rmi.server.RMIServerSocketFactory
119: * @see UnicastRemoteObject#exportObject(Remote, int, RMIClientSocketFactory, RMIServerSocketFactory)
120: */
121: public void setClientSocketFactory(
122: RMIClientSocketFactory clientSocketFactory) {
123: this .clientSocketFactory = clientSocketFactory;
124: }
125:
126: /**
127: * Set a custom RMI server socket factory to use for exporting the service.
128: * <p>Only needs to be specified when the client socket factory does not
129: * implement <code>java.rmi.server.RMIServerSocketFactory</code> already.
130: * @see #setClientSocketFactory
131: * @see java.rmi.server.RMIClientSocketFactory
132: * @see java.rmi.server.RMIServerSocketFactory
133: * @see UnicastRemoteObject#exportObject(Remote, int, RMIClientSocketFactory, RMIServerSocketFactory)
134: */
135: public void setServerSocketFactory(
136: RMIServerSocketFactory serverSocketFactory) {
137: this .serverSocketFactory = serverSocketFactory;
138: }
139:
140: /**
141: * Specify the RMI registry to register the exported service with.
142: * Typically used in combination with RmiRegistryFactoryBean.
143: * <p>Alternatively, you can specify all registry properties locally.
144: * This exporter will then try to locate the specified registry,
145: * automatically creating a new local one if appropriate.
146: * <p>Default is a local registry at the default port (1099),
147: * created on the fly if necessary.
148: * @see RmiRegistryFactoryBean
149: * @see #setRegistryHost
150: * @see #setRegistryPort
151: * @see #setRegistryClientSocketFactory
152: * @see #setRegistryServerSocketFactory
153: */
154: public void setRegistry(Registry registry) {
155: this .registry = registry;
156: }
157:
158: /**
159: * Set the host of the registry for the exported RMI service,
160: * i.e. <code>rmi://HOST:port/name</code>
161: * <p>Default is localhost.
162: */
163: public void setRegistryHost(String registryHost) {
164: this .registryHost = registryHost;
165: }
166:
167: /**
168: * Set the port of the registry for the exported RMI service,
169: * i.e. <code>rmi://host:PORT/name</code>
170: * <p>Default is <code>Registry.REGISTRY_PORT</code> (1099).
171: * @see java.rmi.registry.Registry#REGISTRY_PORT
172: */
173: public void setRegistryPort(int registryPort) {
174: this .registryPort = registryPort;
175: }
176:
177: /**
178: * Set a custom RMI client socket factory to use for the RMI registry.
179: * <p>If the given object also implements <code>java.rmi.server.RMIServerSocketFactory</code>,
180: * it will automatically be registered as server socket factory too.
181: * @see #setRegistryServerSocketFactory
182: * @see java.rmi.server.RMIClientSocketFactory
183: * @see java.rmi.server.RMIServerSocketFactory
184: * @see LocateRegistry#getRegistry(String, int, RMIClientSocketFactory)
185: */
186: public void setRegistryClientSocketFactory(
187: RMIClientSocketFactory registryClientSocketFactory) {
188: this .registryClientSocketFactory = registryClientSocketFactory;
189: }
190:
191: /**
192: * Set a custom RMI server socket factory to use for the RMI registry.
193: * <p>Only needs to be specified when the client socket factory does not
194: * implement <code>java.rmi.server.RMIServerSocketFactory</code> already.
195: * @see #setRegistryClientSocketFactory
196: * @see java.rmi.server.RMIClientSocketFactory
197: * @see java.rmi.server.RMIServerSocketFactory
198: * @see LocateRegistry#createRegistry(int, RMIClientSocketFactory, RMIServerSocketFactory)
199: */
200: public void setRegistryServerSocketFactory(
201: RMIServerSocketFactory registryServerSocketFactory) {
202: this .registryServerSocketFactory = registryServerSocketFactory;
203: }
204:
205: /**
206: * Set whether to always create the registry in-process,
207: * not attempting to locate an existing registry at the specified port.
208: * <p>Default is "false". Switch this flag to "true" in order to avoid
209: * the overhead of locating an existing registry when you always
210: * intend to create a new registry in any case.
211: */
212: public void setAlwaysCreateRegistry(boolean alwaysCreateRegistry) {
213: this .alwaysCreateRegistry = alwaysCreateRegistry;
214: }
215:
216: /**
217: * Set whether to replace an existing binding in the RMI registry,
218: * that is, whether to simply override an existing binding with the
219: * specified service in case of a naming conflict in the registry.
220: * <p>Default is "true", assuming that an existing binding for this
221: * exporter's service name is an accidental leftover from a previous
222: * execution. Switch this to "false" to make the exporter fail in such
223: * a scenario, indicating that there was already an RMI object bound.
224: */
225: public void setReplaceExistingBinding(boolean replaceExistingBinding) {
226: this .replaceExistingBinding = replaceExistingBinding;
227: }
228:
229: public void afterPropertiesSet() throws RemoteException {
230: prepare();
231: }
232:
233: /**
234: * Initialize this service exporter, registering the service as RMI object.
235: * <p>Creates an RMI registry on the specified port if none exists.
236: * @throws RemoteException if service registration failed
237: */
238: public void prepare() throws RemoteException {
239: checkService();
240:
241: if (this .serviceName == null) {
242: throw new IllegalArgumentException(
243: "Property 'serviceName' is required");
244: }
245:
246: // Check socket factories for exported object.
247: if (this .clientSocketFactory instanceof RMIServerSocketFactory) {
248: this .serverSocketFactory = (RMIServerSocketFactory) this .clientSocketFactory;
249: }
250: if ((this .clientSocketFactory != null && this .serverSocketFactory == null)
251: || (this .clientSocketFactory == null && this .serverSocketFactory != null)) {
252: throw new IllegalArgumentException(
253: "Both RMIClientSocketFactory and RMIServerSocketFactory or none required");
254: }
255:
256: // Check socket factories for RMI registry.
257: if (this .registryClientSocketFactory instanceof RMIServerSocketFactory) {
258: this .registryServerSocketFactory = (RMIServerSocketFactory) this .registryClientSocketFactory;
259: }
260: if (this .registryClientSocketFactory == null
261: && this .registryServerSocketFactory != null) {
262: throw new IllegalArgumentException(
263: "RMIServerSocketFactory without RMIClientSocketFactory for registry not supported");
264: }
265:
266: // Determine RMI registry to use.
267: if (this .registry == null) {
268: this .registry = getRegistry(this .registryHost,
269: this .registryPort,
270: this .registryClientSocketFactory,
271: this .registryServerSocketFactory);
272: }
273:
274: // Initialize and cache exported object.
275: this .exportedObject = getObjectToExport();
276:
277: if (logger.isInfoEnabled()) {
278: logger.info("Binding service '" + this .serviceName
279: + "' to RMI registry: " + this .registry);
280: }
281:
282: // Export RMI object.
283: if (this .clientSocketFactory != null) {
284: UnicastRemoteObject.exportObject(this .exportedObject,
285: this .servicePort, this .clientSocketFactory,
286: this .serverSocketFactory);
287: } else {
288: UnicastRemoteObject.exportObject(this .exportedObject,
289: this .servicePort);
290: }
291:
292: // Bind RMI object to registry.
293: try {
294: if (this .replaceExistingBinding) {
295: this .registry.rebind(this .serviceName,
296: this .exportedObject);
297: } else {
298: this .registry.bind(this .serviceName,
299: this .exportedObject);
300: }
301: } catch (AlreadyBoundException ex) {
302: // Already an RMI object bound for the specified service name...
303: unexportObjectSilently();
304: throw new IllegalStateException(
305: "Already an RMI object bound for name '"
306: + this .serviceName + "': " + ex.toString());
307: } catch (RemoteException ex) {
308: // Registry binding failed: let's unexport the RMI object as well.
309: unexportObjectSilently();
310: throw ex;
311: }
312: }
313:
314: /**
315: * Locate or create the RMI registry for this exporter.
316: * @param registryHost the registry host to use (if this is specified,
317: * no implicit creation of a RMI registry will happen)
318: * @param registryPort the registry port to use
319: * @param clientSocketFactory the RMI client socket factory for the registry (if any)
320: * @param serverSocketFactory the RMI server socket factory for the registry (if any)
321: * @return the RMI registry
322: * @throws RemoteException if the registry couldn't be located or created
323: */
324: protected Registry getRegistry(String registryHost,
325: int registryPort,
326: RMIClientSocketFactory clientSocketFactory,
327: RMIServerSocketFactory serverSocketFactory)
328: throws RemoteException {
329:
330: if (registryHost != null) {
331: // Host explictly specified: only lookup possible.
332: if (logger.isInfoEnabled()) {
333: logger.info("Looking for RMI registry at port '"
334: + registryPort + "' of host [" + registryHost
335: + "]");
336: }
337: Registry reg = LocateRegistry.getRegistry(registryHost,
338: registryPort, clientSocketFactory);
339: testRegistry(reg);
340: return reg;
341: }
342:
343: else {
344: return getRegistry(registryPort, clientSocketFactory,
345: serverSocketFactory);
346: }
347: }
348:
349: /**
350: * Locate or create the RMI registry for this exporter.
351: * @param registryPort the registry port to use
352: * @param clientSocketFactory the RMI client socket factory for the registry (if any)
353: * @param serverSocketFactory the RMI server socket factory for the registry (if any)
354: * @return the RMI registry
355: * @throws RemoteException if the registry couldn't be located or created
356: */
357: protected Registry getRegistry(int registryPort,
358: RMIClientSocketFactory clientSocketFactory,
359: RMIServerSocketFactory serverSocketFactory)
360: throws RemoteException {
361:
362: if (clientSocketFactory != null) {
363: if (this .alwaysCreateRegistry) {
364: logger.info("Creating new RMI registry");
365: return LocateRegistry.createRegistry(registryPort,
366: clientSocketFactory, serverSocketFactory);
367: }
368: if (logger.isInfoEnabled()) {
369: logger.info("Looking for RMI registry at port '"
370: + registryPort
371: + "', using custom socket factory");
372: }
373: try {
374: // Retrieve existing registry.
375: Registry reg = LocateRegistry.getRegistry(null,
376: registryPort, clientSocketFactory);
377: testRegistry(reg);
378: return reg;
379: } catch (RemoteException ex) {
380: logger.debug("RMI registry access threw exception", ex);
381: logger
382: .info("Could not detect RMI registry - creating new one");
383: // Assume no registry found -> create new one.
384: return LocateRegistry.createRegistry(registryPort,
385: clientSocketFactory, serverSocketFactory);
386: }
387: }
388:
389: else {
390: return getRegistry(registryPort);
391: }
392: }
393:
394: /**
395: * Locate or create the RMI registry for this exporter.
396: * @param registryPort the registry port to use
397: * @return the RMI registry
398: * @throws RemoteException if the registry couldn't be located or created
399: */
400: protected Registry getRegistry(int registryPort)
401: throws RemoteException {
402: if (this .alwaysCreateRegistry) {
403: logger.info("Creating new RMI registry");
404: return LocateRegistry.createRegistry(registryPort);
405: }
406: if (logger.isInfoEnabled()) {
407: logger.info("Looking for RMI registry at port '"
408: + registryPort + "'");
409: }
410: try {
411: // Retrieve existing registry.
412: Registry reg = LocateRegistry.getRegistry(registryPort);
413: testRegistry(reg);
414: return reg;
415: } catch (RemoteException ex) {
416: logger.debug("RMI registry access threw exception", ex);
417: logger
418: .info("Could not detect RMI registry - creating new one");
419: // Assume no registry found -> create new one.
420: return LocateRegistry.createRegistry(registryPort);
421: }
422: }
423:
424: /**
425: * Test the given RMI registry, calling some operation on it to
426: * check whether it is still active.
427: * <p>Default implementation calls <code>Registry.list()</code>.
428: * @param registry the RMI registry to test
429: * @throws RemoteException if thrown by registry methods
430: * @see java.rmi.registry.Registry#list()
431: */
432: protected void testRegistry(Registry registry)
433: throws RemoteException {
434: registry.list();
435: }
436:
437: /**
438: * Unbind the RMI service from the registry on bean factory shutdown.
439: */
440: public void destroy() throws RemoteException {
441: if (logger.isInfoEnabled()) {
442: logger.info("Unbinding RMI service '" + this .serviceName
443: + "' from registry at port '" + this .registryPort
444: + "'");
445: }
446: try {
447: this .registry.unbind(this .serviceName);
448: } catch (NotBoundException ex) {
449: if (logger.isWarnEnabled()) {
450: logger.warn("RMI service '" + this .serviceName
451: + "' is not bound to registry at port '"
452: + this .registryPort + "' anymore", ex);
453: }
454: } finally {
455: unexportObjectSilently();
456: }
457: }
458:
459: /**
460: * Unexport the registered RMI object, logging any exception that arises.
461: */
462: private void unexportObjectSilently() {
463: try {
464: UnicastRemoteObject.unexportObject(this .exportedObject,
465: true);
466: } catch (NoSuchObjectException ex) {
467: if (logger.isWarnEnabled()) {
468: logger.warn(
469: "RMI object for service '" + this .serviceName
470: + "' isn't exported anymore", ex);
471: }
472: }
473: }
474:
475: }
|