001package org.apache.commons.jcs3.auxiliary.remote.server; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import java.io.IOException; 023import java.net.InetAddress; 024import java.net.MalformedURLException; 025import java.rmi.Naming; 026import java.rmi.NotBoundException; 027import java.rmi.Remote; 028import java.rmi.RemoteException; 029import java.rmi.registry.Registry; 030import java.rmi.server.RMISocketFactory; 031import java.rmi.server.UnicastRemoteObject; 032import java.util.Objects; 033import java.util.Properties; 034import java.util.concurrent.Executors; 035import java.util.concurrent.ScheduledExecutorService; 036import java.util.concurrent.TimeUnit; 037 038import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheConfigurator; 039import org.apache.commons.jcs3.auxiliary.remote.RemoteUtils; 040import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheConstants; 041import org.apache.commons.jcs3.engine.behavior.ICacheServiceAdmin; 042import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger; 043import org.apache.commons.jcs3.log.Log; 044import org.apache.commons.jcs3.log.LogManager; 045import org.apache.commons.jcs3.utils.config.OptionConverter; 046import org.apache.commons.jcs3.utils.config.PropertySetter; 047import org.apache.commons.jcs3.utils.threadpool.DaemonThreadFactory; 048 049/** 050 * Provides remote cache services. This creates remote cache servers and can proxy command line 051 * requests to a running server. 052 */ 053public class RemoteCacheServerFactory 054 implements IRemoteCacheConstants 055{ 056 /** The logger */ 057 private static final Log log = LogManager.getLog( RemoteCacheServerFactory.class ); 058 059 /** The single instance of the RemoteCacheServer object. */ 060 private static RemoteCacheServer<?, ?> remoteCacheServer; 061 062 /** The name of the service. */ 063 private static String serviceName = IRemoteCacheConstants.REMOTE_CACHE_SERVICE_VAL; 064 065 /** Executes the registry keep alive. */ 066 private static ScheduledExecutorService keepAliveDaemon; 067 068 /** A reference to the registry. */ 069 private static Registry registry; 070 071 /** Constructor for the RemoteCacheServerFactory object. */ 072 private RemoteCacheServerFactory() 073 { 074 } 075 076 /** 077 * This will allow you to get stats from the server, etc. Perhaps we should provide methods on 078 * the factory to do this instead. 079 * <p> 080 * A remote cache is either a local cache or a cluster cache. 081 * </p> 082 * @return Returns the remoteCacheServer. 083 */ 084 @SuppressWarnings("unchecked") // Need cast to specific RemoteCacheServer 085 public static <K, V> RemoteCacheServer<K, V> getRemoteCacheServer() 086 { 087 return (RemoteCacheServer<K, V>)remoteCacheServer; 088 } 089 090 // ///////////////////// Startup/shutdown methods. ////////////////// 091 /** 092 * Starts up the remote cache server on this JVM, and binds it to the registry on the given host 093 * and port. 094 * <p> 095 * A remote cache is either a local cache or a cluster cache. 096 * <p> 097 * @param host 098 * @param port 099 * @param props 100 * @throws IOException 101 */ 102 public static void startup( String host, final int port, final Properties props) 103 throws IOException 104 { 105 if ( remoteCacheServer != null ) 106 { 107 throw new IllegalArgumentException( "Server already started." ); 108 } 109 110 synchronized ( RemoteCacheServer.class ) 111 { 112 if ( remoteCacheServer != null ) 113 { 114 return; 115 } 116 117 final RemoteCacheServerAttributes rcsa = configureRemoteCacheServerAttributes(props); 118 119 // These should come from the file! 120 rcsa.setRemoteLocation( Objects.toString(host, ""), port ); 121 log.info( "Creating server with these attributes: {0}", rcsa ); 122 123 setServiceName( rcsa.getRemoteServiceName() ); 124 125 final RMISocketFactory customRMISocketFactory = configureObjectSpecificCustomFactory( props ); 126 127 RemoteUtils.configureGlobalCustomSocketFactory( rcsa.getRmiSocketFactoryTimeoutMillis() ); 128 129 // CONFIGURE THE EVENT LOGGER 130 final ICacheEventLogger cacheEventLogger = configureCacheEventLogger( props ); 131 132 // CREATE SERVER 133 if ( customRMISocketFactory != null ) 134 { 135 remoteCacheServer = new RemoteCacheServer<>( rcsa, props, customRMISocketFactory ); 136 } 137 else 138 { 139 remoteCacheServer = new RemoteCacheServer<>( rcsa, props ); 140 } 141 142 remoteCacheServer.setCacheEventLogger( cacheEventLogger ); 143 144 // START THE REGISTRY 145 registry = RemoteUtils.createRegistry(port); 146 147 // REGISTER THE SERVER 148 registerServer( serviceName, remoteCacheServer ); 149 150 // KEEP THE REGISTRY ALIVE 151 if ( rcsa.isUseRegistryKeepAlive() ) 152 { 153 if ( keepAliveDaemon == null ) 154 { 155 keepAliveDaemon = Executors.newScheduledThreadPool(1, 156 new DaemonThreadFactory("JCS-RemoteCacheServerFactory-")); 157 } 158 keepAliveDaemon.scheduleAtFixedRate(() -> keepAlive(host, port, cacheEventLogger), 159 0, rcsa.getRegistryKeepAliveDelayMillis(), TimeUnit.MILLISECONDS); 160 } 161 } 162 } 163 164 /** 165 * Tries to lookup the server. If unsuccessful it will rebind the server using the factory 166 * rebind method. 167 * 168 * @param registryHost - Hostname of the registry 169 * @param registryPort - the port on which to start the registry 170 * @param cacheEventLogger the event logger for error messages 171 * @since 3.1 172 */ 173 protected static void keepAlive(String registryHost, int registryPort, ICacheEventLogger cacheEventLogger) 174 { 175 String namingURL = RemoteUtils.getNamingURL(registryHost, registryPort, serviceName); 176 log.debug( "looking up server {0}", namingURL ); 177 178 try 179 { 180 final Object obj = Naming.lookup( namingURL ); 181 182 // Successful connection to the remote server. 183 final String message = "RMI registry looks fine. Found [" + obj + "] in registry [" + namingURL + "]"; 184 if ( cacheEventLogger != null ) 185 { 186 cacheEventLogger.logApplicationEvent( "RegistryKeepAliveRunner", "Naming.lookup", message ); 187 } 188 log.debug( message ); 189 } 190 catch ( final Exception ex ) 191 { 192 // Failed to connect to the remote server. 193 final String message = "Problem finding server at [" + namingURL 194 + "]. Will attempt to start registry and rebind."; 195 log.error( message, ex ); 196 if ( cacheEventLogger != null ) 197 { 198 cacheEventLogger.logError( "RegistryKeepAliveRunner", "Naming.lookup", message + ":" + ex.getMessage() ); 199 } 200 201 registry = RemoteUtils.createRegistry(registryPort); 202 203 if ( cacheEventLogger != null ) 204 { 205 if (registry != null) 206 { 207 cacheEventLogger.logApplicationEvent( "RegistryKeepAliveRunner", "createRegistry", 208 "Successfully created registry [" + serviceName + "]." ); 209 } 210 else 211 { 212 cacheEventLogger.logError( "RegistryKeepAliveRunner", "createRegistry", 213 "Could not start registry [" + serviceName + "]." ); 214 } 215 } 216 } 217 218 try 219 { 220 registerServer(serviceName, remoteCacheServer); 221 222 final String message = "Successfully rebound server to registry [" + serviceName + "]."; 223 if ( cacheEventLogger != null ) 224 { 225 cacheEventLogger.logApplicationEvent( "RegistryKeepAliveRunner", "registerServer", message ); 226 } 227 log.info( message ); 228 } 229 catch ( final RemoteException e ) 230 { 231 final String message = "Could not rebind server to registry [" + serviceName + "]."; 232 log.error( message, e ); 233 if ( cacheEventLogger != null ) 234 { 235 cacheEventLogger.logError( "RegistryKeepAliveRunner", "registerServer", message + ":" 236 + e.getMessage() ); 237 } 238 } 239 } 240 241 /** 242 * Tries to get the event logger. 243 * <p> 244 * @param props configuration properties 245 * @return ICacheEventLogger, may be null 246 */ 247 protected static ICacheEventLogger configureCacheEventLogger( final Properties props ) 248 { 249 return AuxiliaryCacheConfigurator 250 .parseCacheEventLogger( props, IRemoteCacheConstants.CACHE_SERVER_PREFIX ); 251 } 252 253 /** 254 * This configures an object specific custom factory. This will be configured for just this 255 * object in the registry. This can be null. 256 * <p> 257 * @param props 258 * @return RMISocketFactory 259 */ 260 protected static RMISocketFactory configureObjectSpecificCustomFactory( final Properties props ) 261 { 262 final RMISocketFactory customRMISocketFactory = 263 OptionConverter.instantiateByKey( props, CUSTOM_RMI_SOCKET_FACTORY_PROPERTY_PREFIX, null ); 264 265 if ( customRMISocketFactory != null ) 266 { 267 PropertySetter.setProperties( customRMISocketFactory, props, CUSTOM_RMI_SOCKET_FACTORY_PROPERTY_PREFIX 268 + "." ); 269 log.info( "Will use server specific custom socket factory. {0}", 270 customRMISocketFactory ); 271 } 272 else 273 { 274 log.info( "No server specific custom socket factory defined." ); 275 } 276 return customRMISocketFactory; 277 } 278 279 /** 280 * Registers the server with the registry. I broke this off because we might want to have code 281 * that will restart a dead registry. It will need to rebind the server. 282 * <p> 283 * @param serviceName the name of the service 284 * @param server the server object to bind 285 * @throws RemoteException 286 */ 287 protected static void registerServer(final String serviceName, final Remote server ) 288 throws RemoteException 289 { 290 if ( server == null ) 291 { 292 throw new RemoteException( "Cannot register the server until it is created." ); 293 } 294 295 if ( registry == null ) 296 { 297 throw new RemoteException( "Cannot register the server: Registry is null." ); 298 } 299 300 log.info( "Binding server to {0}", serviceName ); 301 302 registry.rebind( serviceName, server ); 303 } 304 305 /** 306 * Configure. 307 * <p> 308 * jcs.remotecache.serverattributes.ATTRIBUTENAME=ATTRIBUTEVALUE 309 * <p> 310 * @param prop 311 * @return RemoteCacheServerAttributesconfigureRemoteCacheServerAttributes 312 */ 313 protected static RemoteCacheServerAttributes configureRemoteCacheServerAttributes( final Properties prop ) 314 { 315 final RemoteCacheServerAttributes rcsa = new RemoteCacheServerAttributes(); 316 317 // configure automatically 318 PropertySetter.setProperties( rcsa, prop, CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX + "." ); 319 320 return rcsa; 321 } 322 323 /** 324 * Unbinds the remote server. 325 * <p> 326 * @param host 327 * @param port 328 * @throws IOException 329 */ 330 static void shutdownImpl( final String host, final int port ) 331 throws IOException 332 { 333 synchronized ( RemoteCacheServer.class ) 334 { 335 if ( remoteCacheServer == null ) 336 { 337 return; 338 } 339 log.info( "Unbinding host={0}, port={1}, serviceName={2}", 340 host, port, getServiceName() ); 341 try 342 { 343 Naming.unbind( RemoteUtils.getNamingURL(host, port, getServiceName()) ); 344 } 345 catch ( final MalformedURLException ex ) 346 { 347 // impossible case. 348 throw new IllegalArgumentException( ex.getMessage() + "; host=" + host + ", port=" + port 349 + ", serviceName=" + getServiceName() ); 350 } 351 catch ( final NotBoundException ex ) 352 { 353 // ignore. 354 } 355 remoteCacheServer.release(); 356 remoteCacheServer = null; 357 358 // Shut down keepalive scheduler 359 if ( keepAliveDaemon != null ) 360 { 361 keepAliveDaemon.shutdownNow(); 362 keepAliveDaemon = null; 363 } 364 365 // Try to release registry 366 if (registry != null) 367 { 368 UnicastRemoteObject.unexportObject(registry, true); 369 registry = null; 370 } 371 } 372 } 373 374 /** 375 * Creates an local RMI registry on the default port, starts up the remote cache server, and 376 * binds it to the registry. 377 * <p> 378 * A remote cache is either a local cache or a cluster cache. 379 * <p> 380 * @param args The command line arguments 381 * @throws Exception 382 */ 383 public static void main( final String[] args ) 384 throws Exception 385 { 386 final Properties prop = args.length > 0 ? RemoteUtils.loadProps( args[args.length - 1] ) : new Properties(); 387 388 int port; 389 try 390 { 391 port = Integer.parseInt( prop.getProperty( "registry.port" ) ); 392 } 393 catch ( final NumberFormatException ex ) 394 { 395 port = Registry.REGISTRY_PORT; 396 } 397 398 // shutdown 399 if ( args.length > 0 && args[0].toLowerCase().indexOf( "-shutdown" ) != -1 ) 400 { 401 try 402 { 403 final ICacheServiceAdmin admin = lookupCacheServiceAdmin(prop, port); 404 admin.shutdown(); 405 } 406 catch ( final Exception ex ) 407 { 408 log.error( "Problem calling shutdown.", ex ); 409 } 410 log.debug( "done." ); 411 System.exit( 0 ); 412 } 413 414 // STATS 415 if ( args.length > 0 && args[0].toLowerCase().indexOf( "-stats" ) != -1 ) 416 { 417 log.debug( "getting cache stats" ); 418 419 try 420 { 421 final ICacheServiceAdmin admin = lookupCacheServiceAdmin(prop, port); 422 423 try 424 { 425// System.out.println( admin.getStats().toString() ); 426 log.debug( admin.getStats() ); 427 } 428 catch ( final IOException es ) 429 { 430 log.error( es ); 431 } 432 } 433 catch ( final Exception ex ) 434 { 435 log.error( "Problem getting stats.", ex ); 436 } 437 log.debug( "done." ); 438 System.exit( 0 ); 439 } 440 441 // startup. 442 final String hostName = prop.getProperty( "registry.host" ); 443 final InetAddress host = InetAddress.getByName(hostName); 444 445 if (host.isLoopbackAddress()) 446 { 447 log.debug( "main> creating registry on the localhost" ); 448 RemoteUtils.createRegistry( port ); 449 } 450 log.debug( "main> starting up RemoteCacheServer" ); 451 startup( host.getHostName(), port, prop); 452 log.debug( "main> done" ); 453 } 454 455 /** 456 * Look up the remote cache service admin instance 457 * 458 * @param config the configuration properties 459 * @param port the local port 460 * @return the admin object instance 461 * 462 * @throws Exception if lookup fails 463 */ 464 private static ICacheServiceAdmin lookupCacheServiceAdmin(final Properties config, final int port) throws Exception 465 { 466 final String remoteServiceName = config.getProperty( REMOTE_CACHE_SERVICE_NAME, REMOTE_CACHE_SERVICE_VAL ).trim(); 467 final String registry = RemoteUtils.getNamingURL("", port, remoteServiceName); 468 469 log.debug( "looking up server {0}", registry ); 470 final Object obj = Naming.lookup( registry ); 471 log.debug( "server found" ); 472 473 return (ICacheServiceAdmin) obj; 474 } 475 476 /** 477 * @param serviceName the serviceName to set 478 */ 479 protected static void setServiceName( final String serviceName ) 480 { 481 RemoteCacheServerFactory.serviceName = serviceName; 482 } 483 484 /** 485 * @return the serviceName 486 */ 487 protected static String getServiceName() 488 { 489 return serviceName; 490 } 491}