001/*
002 * Copyright 2002-2013 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 *      https://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
017package org.springframework.remoting.rmi;
018
019import java.rmi.RemoteException;
020import java.rmi.registry.LocateRegistry;
021import java.rmi.registry.Registry;
022import java.rmi.server.RMIClientSocketFactory;
023import java.rmi.server.RMIServerSocketFactory;
024import java.rmi.server.UnicastRemoteObject;
025
026import org.apache.commons.logging.Log;
027import org.apache.commons.logging.LogFactory;
028
029import org.springframework.beans.factory.DisposableBean;
030import org.springframework.beans.factory.FactoryBean;
031import org.springframework.beans.factory.InitializingBean;
032
033/**
034 * {@link FactoryBean} that locates a {@link java.rmi.registry.Registry} and
035 * exposes it for bean references. Can also create a local RMI registry
036 * on the fly if none exists already.
037 *
038 * <p>Can be used to set up and pass around the actual Registry object to
039 * applications objects that need to work with RMI. One example for such an
040 * object that needs to work with RMI is Spring's {@link RmiServiceExporter},
041 * which either works with a passed-in Registry reference or falls back to
042 * the registry as specified by its local properties and defaults.
043 *
044 * <p>Also useful to enforce creation of a local RMI registry at a given port,
045 * for example for a JMX connector. If used in conjunction with
046 * {@link org.springframework.jmx.support.ConnectorServerFactoryBean},
047 * it is recommended to mark the connector definition (ConnectorServerFactoryBean)
048 * as "depends-on" the registry definition (RmiRegistryFactoryBean),
049 * to guarantee starting up the registry first.
050 *
051 * <p>Note: The implementation of this class mirrors the corresponding logic
052 * in {@link RmiServiceExporter}, and also offers the same customization hooks.
053 * RmiServiceExporter implements its own registry lookup as a convenience:
054 * It is very common to simply rely on the registry defaults.
055 *
056 * @author Juergen Hoeller
057 * @since 1.2.3
058 * @see RmiServiceExporter#setRegistry
059 * @see org.springframework.jmx.support.ConnectorServerFactoryBean
060 * @see java.rmi.registry.Registry
061 * @see java.rmi.registry.LocateRegistry
062 */
063public class RmiRegistryFactoryBean implements FactoryBean<Registry>, InitializingBean, DisposableBean {
064
065        protected final Log logger = LogFactory.getLog(getClass());
066
067        private String host;
068
069        private int port = Registry.REGISTRY_PORT;
070
071        private RMIClientSocketFactory clientSocketFactory;
072
073        private RMIServerSocketFactory serverSocketFactory;
074
075        private Registry registry;
076
077        private boolean alwaysCreate = false;
078
079        private boolean created = false;
080
081
082        /**
083         * Set the host of the registry for the exported RMI service,
084         * i.e. {@code rmi://HOST:port/name}
085         * <p>Default is localhost.
086         */
087        public void setHost(String host) {
088                this.host = host;
089        }
090
091        /**
092         * Return the host of the registry for the exported RMI service.
093         */
094        public String getHost() {
095                return this.host;
096        }
097
098        /**
099         * Set the port of the registry for the exported RMI service,
100         * i.e. {@code rmi://host:PORT/name}
101         * <p>Default is {@code Registry.REGISTRY_PORT} (1099).
102         */
103        public void setPort(int port) {
104                this.port = port;
105        }
106
107        /**
108         * Return the port of the registry for the exported RMI service.
109         */
110        public int getPort() {
111                return this.port;
112        }
113
114        /**
115         * Set a custom RMI client socket factory to use for the RMI registry.
116         * <p>If the given object also implements {@code java.rmi.server.RMIServerSocketFactory},
117         * it will automatically be registered as server socket factory too.
118         * @see #setServerSocketFactory
119         * @see java.rmi.server.RMIClientSocketFactory
120         * @see java.rmi.server.RMIServerSocketFactory
121         * @see java.rmi.registry.LocateRegistry#getRegistry(String, int, java.rmi.server.RMIClientSocketFactory)
122         */
123        public void setClientSocketFactory(RMIClientSocketFactory clientSocketFactory) {
124                this.clientSocketFactory = clientSocketFactory;
125        }
126
127        /**
128         * Set a custom RMI server socket factory to use for the RMI registry.
129         * <p>Only needs to be specified when the client socket factory does not
130         * implement {@code java.rmi.server.RMIServerSocketFactory} already.
131         * @see #setClientSocketFactory
132         * @see java.rmi.server.RMIClientSocketFactory
133         * @see java.rmi.server.RMIServerSocketFactory
134         * @see java.rmi.registry.LocateRegistry#createRegistry(int, RMIClientSocketFactory, java.rmi.server.RMIServerSocketFactory)
135         */
136        public void setServerSocketFactory(RMIServerSocketFactory serverSocketFactory) {
137                this.serverSocketFactory = serverSocketFactory;
138        }
139
140        /**
141         * Set whether to always create the registry in-process,
142         * not attempting to locate an existing registry at the specified port.
143         * <p>Default is "false". Switch this flag to "true" in order to avoid
144         * the overhead of locating an existing registry when you always
145         * intend to create a new registry in any case.
146         */
147        public void setAlwaysCreate(boolean alwaysCreate) {
148                this.alwaysCreate = alwaysCreate;
149        }
150
151
152        @Override
153        public void afterPropertiesSet() throws Exception {
154                // Check socket factories for registry.
155                if (this.clientSocketFactory instanceof RMIServerSocketFactory) {
156                        this.serverSocketFactory = (RMIServerSocketFactory) this.clientSocketFactory;
157                }
158                if ((this.clientSocketFactory != null && this.serverSocketFactory == null) ||
159                                (this.clientSocketFactory == null && this.serverSocketFactory != null)) {
160                        throw new IllegalArgumentException(
161                                        "Both RMIClientSocketFactory and RMIServerSocketFactory or none required");
162                }
163
164                // Fetch RMI registry to expose.
165                this.registry = getRegistry(this.host, this.port, this.clientSocketFactory, this.serverSocketFactory);
166        }
167
168
169        /**
170         * Locate or create the RMI registry.
171         * @param registryHost the registry host to use (if this is specified,
172         * no implicit creation of a RMI registry will happen)
173         * @param registryPort the registry port to use
174         * @param clientSocketFactory the RMI client socket factory for the registry (if any)
175         * @param serverSocketFactory the RMI server socket factory for the registry (if any)
176         * @return the RMI registry
177         * @throws java.rmi.RemoteException if the registry couldn't be located or created
178         */
179        protected Registry getRegistry(String registryHost, int registryPort,
180                        RMIClientSocketFactory clientSocketFactory, RMIServerSocketFactory serverSocketFactory)
181                        throws RemoteException {
182
183                if (registryHost != null) {
184                        // Host explicitly specified: only lookup possible.
185                        if (logger.isInfoEnabled()) {
186                                logger.info("Looking for RMI registry at port '" + registryPort + "' of host [" + registryHost + "]");
187                        }
188                        Registry reg = LocateRegistry.getRegistry(registryHost, registryPort, clientSocketFactory);
189                        testRegistry(reg);
190                        return reg;
191                }
192
193                else {
194                        return getRegistry(registryPort, clientSocketFactory, serverSocketFactory);
195                }
196        }
197
198        /**
199         * Locate or create the RMI registry.
200         * @param registryPort the registry port to use
201         * @param clientSocketFactory the RMI client socket factory for the registry (if any)
202         * @param serverSocketFactory the RMI server socket factory for the registry (if any)
203         * @return the RMI registry
204         * @throws RemoteException if the registry couldn't be located or created
205         */
206        protected Registry getRegistry(
207                        int registryPort, RMIClientSocketFactory clientSocketFactory, RMIServerSocketFactory serverSocketFactory)
208                        throws RemoteException {
209
210                if (clientSocketFactory != null) {
211                        if (this.alwaysCreate) {
212                                logger.info("Creating new RMI registry");
213                                this.created = true;
214                                return LocateRegistry.createRegistry(registryPort, clientSocketFactory, serverSocketFactory);
215                        }
216                        if (logger.isInfoEnabled()) {
217                                logger.info("Looking for RMI registry at port '" + registryPort + "', using custom socket factory");
218                        }
219                        synchronized (LocateRegistry.class) {
220                                try {
221                                        // Retrieve existing registry.
222                                        Registry reg = LocateRegistry.getRegistry(null, registryPort, clientSocketFactory);
223                                        testRegistry(reg);
224                                        return reg;
225                                }
226                                catch (RemoteException ex) {
227                                        logger.debug("RMI registry access threw exception", ex);
228                                        logger.info("Could not detect RMI registry - creating new one");
229                                        // Assume no registry found -> create new one.
230                                        this.created = true;
231                                        return LocateRegistry.createRegistry(registryPort, clientSocketFactory, serverSocketFactory);
232                                }
233                        }
234                }
235
236                else {
237                        return getRegistry(registryPort);
238                }
239        }
240
241        /**
242         * Locate or create the RMI registry.
243         * @param registryPort the registry port to use
244         * @return the RMI registry
245         * @throws RemoteException if the registry couldn't be located or created
246         */
247        protected Registry getRegistry(int registryPort) throws RemoteException {
248                if (this.alwaysCreate) {
249                        logger.info("Creating new RMI registry");
250                        this.created = true;
251                        return LocateRegistry.createRegistry(registryPort);
252                }
253                if (logger.isInfoEnabled()) {
254                        logger.info("Looking for RMI registry at port '" + registryPort + "'");
255                }
256                synchronized (LocateRegistry.class) {
257                        try {
258                                // Retrieve existing registry.
259                                Registry reg = LocateRegistry.getRegistry(registryPort);
260                                testRegistry(reg);
261                                return reg;
262                        }
263                        catch (RemoteException ex) {
264                                logger.debug("RMI registry access threw exception", ex);
265                                logger.info("Could not detect RMI registry - creating new one");
266                                // Assume no registry found -> create new one.
267                                this.created = true;
268                                return LocateRegistry.createRegistry(registryPort);
269                        }
270                }
271        }
272
273        /**
274         * Test the given RMI registry, calling some operation on it to
275         * check whether it is still active.
276         * <p>Default implementation calls {@code Registry.list()}.
277         * @param registry the RMI registry to test
278         * @throws RemoteException if thrown by registry methods
279         * @see java.rmi.registry.Registry#list()
280         */
281        protected void testRegistry(Registry registry) throws RemoteException {
282                registry.list();
283        }
284
285
286        @Override
287        public Registry getObject() throws Exception {
288                return this.registry;
289        }
290
291        @Override
292        public Class<? extends Registry> getObjectType() {
293                return (this.registry != null ? this.registry.getClass() : Registry.class);
294        }
295
296        @Override
297        public boolean isSingleton() {
298                return true;
299        }
300
301
302        /**
303         * Unexport the RMI registry on bean factory shutdown,
304         * provided that this bean actually created a registry.
305         */
306        @Override
307        public void destroy() throws RemoteException {
308                if (this.created) {
309                        logger.info("Unexporting RMI registry");
310                        UnicastRemoteObject.unexportObject(this.registry, true);
311                }
312        }
313
314}