blob: bb4a0a9314d2f75ad7a10791737355ee14ee4810 [file] [log] [blame]
package org.apache.commons.jcs3.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.net.InetAddress;
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.jcs3.auxiliary.AuxiliaryCacheConfigurator;
import org.apache.commons.jcs3.auxiliary.remote.RemoteUtils;
import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheConstants;
import org.apache.commons.jcs3.engine.behavior.ICacheServiceAdmin;
import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
import org.apache.commons.jcs3.log.Log;
import org.apache.commons.jcs3.log.LogManager;
import org.apache.commons.jcs3.utils.config.OptionConverter;
import org.apache.commons.jcs3.utils.config.PropertySetter;
import org.apache.commons.jcs3.utils.threadpool.DaemonThreadFactory;
/**
* 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 = LogManager.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;
/** Constructor for the RemoteCacheServerFactory object. */
private RemoteCacheServerFactory()
{
}
/**
* 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 props
* @throws IOException
*/
public static void startup( String host, final int port, final Properties props)
throws IOException
{
if ( remoteCacheServer != null )
{
throw new IllegalArgumentException( "Server already started." );
}
synchronized ( RemoteCacheServer.class )
{
if ( remoteCacheServer != null )
{
return;
}
if ( host == null )
{
host = "";
}
final RemoteCacheServerAttributes rcsa = configureRemoteCacheServerAttributes(props);
// These should come from the file!
rcsa.setRemoteLocation( host, port );
log.info( "Creating server with these attributes: {0}", rcsa );
setServiceName( rcsa.getRemoteServiceName() );
final RMISocketFactory customRMISocketFactory = configureObjectSpecificCustomFactory( props );
RemoteUtils.configureGlobalCustomSocketFactory( rcsa.getRmiSocketFactoryTimeoutMillis() );
// CONFIGURE THE EVENT LOGGER
final ICacheEventLogger cacheEventLogger = configureCacheEventLogger( props );
// CREATE SERVER
if ( customRMISocketFactory != null )
{
remoteCacheServer = new RemoteCacheServer<>( rcsa, props, customRMISocketFactory );
}
else
{
remoteCacheServer = new RemoteCacheServer<>( rcsa, props );
}
remoteCacheServer.setCacheEventLogger( cacheEventLogger );
// START THE REGISTRY
registry = RemoteUtils.createRegistry(port);
// REGISTER THE SERVER
registerServer( serviceName, remoteCacheServer );
// KEEP THE REGISTRY ALIVE
if ( rcsa.isUseRegistryKeepAlive() )
{
if ( keepAliveDaemon == null )
{
keepAliveDaemon = Executors.newScheduledThreadPool(1,
new DaemonThreadFactory("JCS-RemoteCacheServerFactory-"));
}
final 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( final 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( final Properties props )
{
final 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
+ "." );
log.info( "Will use server specific custom socket factory. {0}",
customRMISocketFactory );
}
else
{
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 serviceName the name of the service
* @param server the server object to bind
* @throws RemoteException
*/
protected static void registerServer(final String serviceName, final Remote server )
throws RemoteException
{
if ( server == null )
{
throw new RemoteException( "Cannot register the server until it is created." );
}
if ( registry == null )
{
throw new RemoteException( "Cannot register the server: Registry is null." );
}
log.info( "Binding server to {0}", serviceName );
registry.rebind( serviceName, server );
}
/**
* Configure.
* <p>
* jcs.remotecache.serverattributes.ATTRIBUTENAME=ATTRIBUTEVALUE
* <p>
* @param prop
* @return RemoteCacheServerAttributesconfigureRemoteCacheServerAttributes
*/
protected static RemoteCacheServerAttributes configureRemoteCacheServerAttributes( final Properties prop )
{
final 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( final Properties prop, final RemoteCacheServerAttributes rcsa )
{
// DEPRECATED CONFIG
final String servicePortStr = prop.getProperty( REMOTE_CACHE_SERVICE_PORT );
if ( servicePortStr != null )
{
try
{
final int servicePort = Integer.parseInt( servicePortStr );
rcsa.setServicePort( servicePort );
log.debug( "Remote cache service uses port number {0}", servicePort );
}
catch ( final NumberFormatException ignore )
{
log.debug( "Remote cache service port property {0}" +
" not specified. An anonymous port will be used.", REMOTE_CACHE_SERVICE_PORT );
}
}
final String socketTimeoutMillisStr = prop.getProperty( SOCKET_TIMEOUT_MILLIS );
if ( socketTimeoutMillisStr != null )
{
try
{
final int rmiSocketFactoryTimeoutMillis = Integer.parseInt( socketTimeoutMillisStr );
rcsa.setRmiSocketFactoryTimeoutMillis( rmiSocketFactoryTimeoutMillis );
log.debug( "Remote cache socket timeout {0} ms.", rmiSocketFactoryTimeoutMillis );
}
catch ( final NumberFormatException ignore )
{
log.debug( "Remote cache socket timeout property {0}" +
" not specified. The default will be used.", SOCKET_TIMEOUT_MILLIS );
}
}
final String lccStr = prop.getProperty( REMOTE_LOCAL_CLUSTER_CONSISTENCY );
if ( lccStr != null )
{
final boolean lcc = Boolean.parseBoolean( lccStr );
rcsa.setLocalClusterConsistency( lcc );
}
final String acgStr = prop.getProperty( REMOTE_ALLOW_CLUSTER_GET );
if ( acgStr != null )
{
final 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( final String host, final int port )
throws IOException
{
synchronized ( RemoteCacheServer.class )
{
if ( remoteCacheServer == null )
{
return;
}
log.info( "Unbinding host={0}, port={1}, serviceName={2}",
host, port, getServiceName() );
try
{
Naming.unbind( RemoteUtils.getNamingURL(host, port, getServiceName()) );
}
catch ( final MalformedURLException ex )
{
// impossible case.
throw new IllegalArgumentException( ex.getMessage() + "; host=" + host + ", port=" + port
+ ", serviceName=" + getServiceName() );
}
catch ( final 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( final String[] args )
throws Exception
{
final Properties prop = args.length > 0 ? RemoteUtils.loadProps( args[args.length - 1] ) : new Properties();
int port;
try
{
port = Integer.parseInt( prop.getProperty( "registry.port" ) );
}
catch ( final NumberFormatException ex )
{
port = Registry.REGISTRY_PORT;
}
// shutdown
if ( args.length > 0 && args[0].toLowerCase().indexOf( "-shutdown" ) != -1 )
{
try
{
final ICacheServiceAdmin admin = lookupCacheServiceAdmin(prop, port);
admin.shutdown();
}
catch ( final 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
{
final ICacheServiceAdmin admin = lookupCacheServiceAdmin(prop, port);
try
{
// System.out.println( admin.getStats().toString() );
log.debug( admin.getStats() );
}
catch ( final IOException es )
{
log.error( es );
}
}
catch ( final Exception ex )
{
log.error( "Problem getting stats.", ex );
}
log.debug( "done." );
System.exit( 0 );
}
// startup.
final String hostName = prop.getProperty( "registry.host" );
final InetAddress host = InetAddress.getByName(hostName);
if (host.isLoopbackAddress())
{
log.debug( "main> creating registry on the localhost" );
RemoteUtils.createRegistry( port );
}
log.debug( "main> starting up RemoteCacheServer" );
startup( host.getHostName(), port, prop);
log.debug( "main> done" );
}
/**
* Look up the remote cache service admin instance
*
* @param config the configuration properties
* @param port the local port
* @return the admin object instance
*
* @throws Exception if lookup fails
*/
private static ICacheServiceAdmin lookupCacheServiceAdmin(final Properties config, final int port) throws Exception
{
final String remoteServiceName = config.getProperty( REMOTE_CACHE_SERVICE_NAME, REMOTE_CACHE_SERVICE_VAL ).trim();
final String registry = RemoteUtils.getNamingURL("", port, remoteServiceName);
log.debug( "looking up server {0}", registry );
final Object obj = Naming.lookup( registry );
log.debug( "server found" );
return (ICacheServiceAdmin) obj;
}
/**
* @param serviceName the serviceName to set
*/
protected static void setServiceName( final String serviceName )
{
RemoteCacheServerFactory.serviceName = serviceName;
}
/**
* @return the serviceName
*/
protected static String getServiceName()
{
return serviceName;
}
}