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}