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