package org.apache.commons.jcs.auxiliary.remote.server; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import java.io.IOException; import java.io.Serializable; import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.NotBoundException; import java.rmi.Remote; import java.rmi.RemoteException; import java.rmi.registry.Registry; import java.rmi.server.RMISocketFactory; import java.rmi.server.UnicastRemoteObject; import java.util.Properties; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.commons.jcs.auxiliary.AuxiliaryCacheConfigurator; import org.apache.commons.jcs.auxiliary.remote.RemoteUtils; import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheConstants; import org.apache.commons.jcs.engine.behavior.ICacheServiceAdmin; import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger; import org.apache.commons.jcs.utils.config.OptionConverter; import org.apache.commons.jcs.utils.config.PropertySetter; import org.apache.commons.jcs.utils.threadpool.DaemonThreadFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Provides remote cache services. This creates remote cache servers and can proxy command line * requests to a running server. */ public class RemoteCacheServerFactory implements IRemoteCacheConstants { /** The logger */ private static final Log log = LogFactory.getLog( RemoteCacheServerFactory.class ); /** The single instance of the RemoteCacheServer object. */ private static RemoteCacheServer<?, ?> remoteCacheServer; /** The name of the service. */ private static String serviceName = IRemoteCacheConstants.REMOTE_CACHE_SERVICE_VAL; /** Executes the registry keep alive. */ private static ScheduledExecutorService keepAliveDaemon; /** A reference to the registry. */ private static Registry registry = null; /** Constructor for the RemoteCacheServerFactory object. */ private RemoteCacheServerFactory() { super(); } /** * This will allow you to get stats from the server, etc. Perhaps we should provide methods on * the factory to do this instead. * <p> * A remote cache is either a local cache or a cluster cache. * </p> * @return Returns the remoteCacheServer. */ @SuppressWarnings("unchecked") // Need cast to specific RemoteCacheServer public static <K, V> RemoteCacheServer<K, V> getRemoteCacheServer() { return (RemoteCacheServer<K, V>)remoteCacheServer; } // ///////////////////// Startup/shutdown methods. ////////////////// /** * Starts up the remote cache server on this JVM, and binds it to the registry on the given host * and port. * <p> * A remote cache is either a local cache or a cluster cache. * <p> * @param host * @param port * @param propFile * @throws IOException */ public static void startup( String host, int port, String propFile ) throws IOException { if ( remoteCacheServer != null ) { throw new IllegalArgumentException( "Server already started." ); } synchronized ( RemoteCacheServer.class ) { if ( remoteCacheServer != null ) { return; } if ( log.isInfoEnabled() ) { log.info( "ConfigFileName = [" + propFile + "]" ); } Properties props = RemoteUtils.loadProps( propFile ); startup(host, port, props, propFile); } } /** * Starts up the remote cache server on this JVM, and binds it to the registry on the given host * and port. * <p> * A remote cache is either a local cache or a cluster cache. * <p> * @param host * @param port * @param props * @throws IOException */ public static void startup( String host, int port, Properties props, String propFile ) throws IOException { if ( remoteCacheServer != null ) { throw new IllegalArgumentException( "Server already started." ); } synchronized ( RemoteCacheServer.class ) { if ( remoteCacheServer != null ) { return; } if ( host == null ) { host = ""; } RemoteCacheServerAttributes rcsa = configureRemoteCacheServerAttributes(props); rcsa.setConfigFileName( propFile ); // These should come from the file! rcsa.setRemoteLocation( host, port ); if ( log.isInfoEnabled() ) { log.info( "Creating server with these attributes: " + rcsa ); } setServiceName( rcsa.getRemoteServiceName() ); RMISocketFactory customRMISocketFactory = configureObjectSpecificCustomFactory( props ); RemoteUtils.configureGlobalCustomSocketFactory( rcsa.getRmiSocketFactoryTimeoutMillis() ); // CONFIGURE THE EVENT LOGGER ICacheEventLogger cacheEventLogger = configureCacheEventLogger( props ); // CREATE SERVER if ( customRMISocketFactory != null ) { remoteCacheServer = new RemoteCacheServer<Serializable, Serializable>( rcsa, customRMISocketFactory ); } else { remoteCacheServer = new RemoteCacheServer<Serializable, Serializable>( rcsa ); } remoteCacheServer.setCacheEventLogger( cacheEventLogger ); // START THE REGISTRY if (rcsa.isStartRegistry()) { registry = RemoteUtils.createRegistry(port); } // REGISTER THE SERVER registerServer( RemoteUtils.getNamingURL(host, port, serviceName), remoteCacheServer ); // KEEP THE REGISTRY ALIVE if ( rcsa.isUseRegistryKeepAlive() ) { if ( keepAliveDaemon == null ) { keepAliveDaemon = Executors.newScheduledThreadPool(1, new DaemonThreadFactory("JCS-RemoteCacheServerFactory-")); } RegistryKeepAliveRunner runner = new RegistryKeepAliveRunner( host, port, serviceName ); runner.setCacheEventLogger( cacheEventLogger ); keepAliveDaemon.scheduleAtFixedRate(runner, 0, rcsa.getRegistryKeepAliveDelayMillis(), TimeUnit.MILLISECONDS); } } } /** * Tries to get the event logger by new and old config styles. * <p> * @param props * @return ICacheEventLogger */ protected static ICacheEventLogger configureCacheEventLogger( Properties props ) { ICacheEventLogger cacheEventLogger = AuxiliaryCacheConfigurator .parseCacheEventLogger( props, IRemoteCacheConstants.CACHE_SERVER_PREFIX ); // try the old way if ( cacheEventLogger == null ) { cacheEventLogger = AuxiliaryCacheConfigurator.parseCacheEventLogger( props, IRemoteCacheConstants.PROPERTY_PREFIX ); } return cacheEventLogger; } /** * This configures an object specific custom factory. This will be configured for just this * object in the registry. This can be null. * <p> * @param props * @return RMISocketFactory */ protected static RMISocketFactory configureObjectSpecificCustomFactory( Properties props ) { RMISocketFactory customRMISocketFactory = OptionConverter.instantiateByKey( props, CUSTOM_RMI_SOCKET_FACTORY_PROPERTY_PREFIX, null ); if ( customRMISocketFactory != null ) { PropertySetter.setProperties( customRMISocketFactory, props, CUSTOM_RMI_SOCKET_FACTORY_PROPERTY_PREFIX + "." ); if ( log.isInfoEnabled() ) { log.info( "Will use server specific custom socket factory. " + customRMISocketFactory ); } } else { if ( log.isInfoEnabled() ) { log.info( "No server specific custom socket factory defined." ); } } return customRMISocketFactory; } /** * Registers the server with the registry. I broke this off because we might want to have code * that will restart a dead registry. It will need to rebind the server. * <p> * @param namingURL * @param server * @throws RemoteException */ protected static void registerServer( String namingURL, Remote server ) throws RemoteException { if ( server == null ) { throw new RemoteException( "Cannot register the server until it is created." ); } if ( log.isInfoEnabled() ) { log.info( "Binding server to " + namingURL ); } try { Naming.rebind( namingURL, server ); } catch ( MalformedURLException ex ) { // impossible case. throw new IllegalArgumentException( ex.getMessage() + "; url=" + namingURL ); } } /** * Configure. * <p> * jcs.remotecache.serverattributes.ATTRIBUTENAME=ATTRIBUTEVALUE * <p> * @param prop * @return RemoteCacheServerAttributesconfigureRemoteCacheServerAttributes */ protected static RemoteCacheServerAttributes configureRemoteCacheServerAttributes( Properties prop ) { RemoteCacheServerAttributes rcsa = new RemoteCacheServerAttributes(); // configure automatically PropertySetter.setProperties( rcsa, prop, CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX + "." ); configureManuallyIfValuesArePresent( prop, rcsa ); return rcsa; } /** * This looks for the old config values. * <p> * @param prop * @param rcsa */ private static void configureManuallyIfValuesArePresent( Properties prop, RemoteCacheServerAttributes rcsa ) { // DEPRECATED CONFIG String servicePortStr = prop.getProperty( REMOTE_CACHE_SERVICE_PORT ); if ( servicePortStr != null ) { try { int servicePort = Integer.parseInt( servicePortStr ); rcsa.setServicePort( servicePort ); log.debug( "Remote cache service uses port number " + servicePort + "." ); } catch ( NumberFormatException ignore ) { log.debug( "Remote cache service port property " + REMOTE_CACHE_SERVICE_PORT + " not specified. An anonymous port will be used." ); } } String socketTimeoutMillisStr = prop.getProperty( SOCKET_TIMEOUT_MILLIS ); if ( socketTimeoutMillisStr != null ) { try { int rmiSocketFactoryTimeoutMillis = Integer.parseInt( socketTimeoutMillisStr ); rcsa.setRmiSocketFactoryTimeoutMillis( rmiSocketFactoryTimeoutMillis ); log.debug( "Remote cache socket timeout " + rmiSocketFactoryTimeoutMillis + "ms." ); } catch ( NumberFormatException ignore ) { log.debug( "Remote cache socket timeout property " + SOCKET_TIMEOUT_MILLIS + " not specified. The default will be used." ); } } String lccStr = prop.getProperty( REMOTE_LOCAL_CLUSTER_CONSISTENCY ); if ( lccStr != null ) { boolean lcc = Boolean.parseBoolean( lccStr ); rcsa.setLocalClusterConsistency( lcc ); } String acgStr = prop.getProperty( REMOTE_ALLOW_CLUSTER_GET ); if ( acgStr != null ) { boolean acg = Boolean.parseBoolean( lccStr ); rcsa.setAllowClusterGet( acg ); } // Register the RemoteCacheServer remote object in the registry. rcsa.setRemoteServiceName( prop.getProperty( REMOTE_CACHE_SERVICE_NAME, REMOTE_CACHE_SERVICE_VAL ).trim() ); } /** * Unbinds the remote server. * <p> * @param host * @param port * @throws IOException */ static void shutdownImpl( String host, int port ) throws IOException { synchronized ( RemoteCacheServer.class ) { if ( remoteCacheServer == null ) { return; } log.info( "Unbinding host=" + host + ", port=" + port + ", serviceName=" + getServiceName() ); try { Naming.unbind( RemoteUtils.getNamingURL(host, port, getServiceName()) ); } catch ( MalformedURLException ex ) { // impossible case. throw new IllegalArgumentException( ex.getMessage() + "; host=" + host + ", port=" + port + ", serviceName=" + getServiceName() ); } catch ( NotBoundException ex ) { // ignore. } remoteCacheServer.release(); remoteCacheServer = null; // Shut down keepalive scheduler if ( keepAliveDaemon != null ) { keepAliveDaemon.shutdownNow(); keepAliveDaemon = null; } // Try to release registry if (registry != null) { UnicastRemoteObject.unexportObject(registry, true); registry = null; } } } /** * Creates an local RMI registry on the default port, starts up the remote cache server, and * binds it to the registry. * <p> * A remote cache is either a local cache or a cluster cache. * <p> * @param args The command line arguments * @throws Exception */ public static void main( String[] args ) throws Exception { Properties prop = args.length > 0 ? RemoteUtils.loadProps( args[args.length - 1] ) : new Properties(); int port; try { port = Integer.parseInt( prop.getProperty( "registry.port" ) ); } catch ( NumberFormatException ex ) { port = Registry.REGISTRY_PORT; } // shutdown if ( args.length > 0 && args[0].toLowerCase().indexOf( "-shutdown" ) != -1 ) { String remoteServiceName = prop.getProperty( REMOTE_CACHE_SERVICE_NAME, REMOTE_CACHE_SERVICE_VAL ).trim(); String registry = RemoteUtils.getNamingURL("", port, remoteServiceName); if ( log.isDebugEnabled() ) { log.debug( "looking up server " + registry ); } Object obj = Naming.lookup( registry ); if ( log.isDebugEnabled() ) { log.debug( "server found" ); } ICacheServiceAdmin admin = (ICacheServiceAdmin) obj; try { admin.shutdown(); } catch ( Exception ex ) { log.error( "Problem calling shutdown.", ex ); } log.debug( "done." ); System.exit( 0 ); } // STATS if ( args.length > 0 && args[0].toLowerCase().indexOf( "-stats" ) != -1 ) { log.debug( "getting cache stats" ); try { String sz = prop.getProperty( REMOTE_CACHE_SERVICE_NAME, REMOTE_CACHE_SERVICE_VAL ).trim(); String registry = RemoteUtils.getNamingURL("", port, sz); log.debug( "looking up server " + registry ); Object obj = Naming.lookup( registry ); log.debug( "server found" ); log.debug( "obj = " + obj ); ICacheServiceAdmin admin = (ICacheServiceAdmin) obj; try { // System.out.println( admin.getStats().toString() ); log.debug( admin.getStats() ); } catch ( Exception es ) { log.error( es ); } } catch ( Exception ex ) { log.error( "Problem getting stats.", ex ); } log.debug( "done." ); System.exit( 0 ); } // startup. String host = prop.getProperty( "registry.host" ); if ( host == null || host.trim().equals( "" ) || host.trim().equals( "localhost" ) ) { log.debug( "main> creating registry on the localhost" ); RemoteUtils.createRegistry( port ); } log.debug( "main> starting up RemoteCacheServer" ); RemoteCacheServerFactory.startup( host, port, args.length > 0 ? args[0] : null ); log.debug( "main> done" ); } /** * @param serviceName the serviceName to set */ protected static void setServiceName( String serviceName ) { RemoteCacheServerFactory.serviceName = serviceName; } /** * @return the serviceName */ protected static String getServiceName() { return serviceName; } }