blob: 382c17ccbe7e4081a2117fea777f9addc581b585 [file] [log] [blame]
package org.apache.jcs.engine.control;
/*
* 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.InputStream;
import java.io.Serializable;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.jcs.auxiliary.AuxiliaryCacheAttributes;
import org.apache.jcs.auxiliary.AuxiliaryCacheFactory;
import org.apache.jcs.auxiliary.remote.behavior.IRemoteCacheConstants;
import org.apache.jcs.engine.CacheConstants;
import org.apache.jcs.engine.CompositeCacheAttributes;
import org.apache.jcs.engine.ElementAttributes;
import org.apache.jcs.engine.behavior.ICacheType;
import org.apache.jcs.engine.behavior.ICompositeCacheAttributes;
import org.apache.jcs.engine.behavior.ICompositeCacheManager;
import org.apache.jcs.engine.behavior.IElementAttributes;
import org.apache.jcs.engine.behavior.IShutdownObservable;
import org.apache.jcs.engine.behavior.IShutdownObserver;
import org.apache.jcs.engine.stats.CacheStats;
import org.apache.jcs.engine.stats.behavior.ICacheStats;
import org.apache.jcs.utils.threadpool.ThreadPoolManager;
/**
* Manages a composite cache. This provides access to caches and is the primary way to shutdown the
* caching system as a whole.
* <p>
* The composite cache manager is responsible for creating / configuring cache regions. It serves as
* a factory for the ComositeCache class. The CompositeCache is the core of JCS, the hub for various
* auxiliaries.
* <p>
* It is recommended that you use the JCS convenience class for all cache access.
*/
public class CompositeCacheManager
implements IRemoteCacheConstants, Serializable, ICompositeCacheManager, IShutdownObservable
{
/** Don't change */
private static final long serialVersionUID = 7598584393134401756L;
/** The logger */
private final static Log log = LogFactory.getLog( CompositeCacheManager.class );
/** Caches managed by this cache manager */
protected Hashtable caches = new Hashtable();
/** Internal system caches for this cache manager */
protected Hashtable systemCaches = new Hashtable();
/** Number of clients accessing this cache manager */
private int clients;
/** Default cache attributes for this cache manager */
protected ICompositeCacheAttributes defaultCacheAttr = new CompositeCacheAttributes();
/** Default elemeent attributes for this cache manager */
protected IElementAttributes defaultElementAttr = new ElementAttributes();
/** Used to keep track of configured auxiliaries */
protected Hashtable auxiliaryFactoryRegistry = new Hashtable( 11 );
/** Used to keep track of attributes for auxiliaries. */
protected Hashtable auxiliaryAttributeRegistry = new Hashtable( 11 );
/** Properties with which this manager was configured. This is exposed for other managers. */
private Properties configurationProperties;
/** The default auxiliary caches to be used if not preconfigured */
protected String defaultAuxValues;
/** The Singleton Instance */
protected static CompositeCacheManager instance;
/** The prefix of relevant system properties */
private static final String SYSTEM_PROPERTY_KEY_PREFIX = "jcs";
/** Should we use system property substitutions. */
private static final boolean DEFAULT_USE_SYSTEM_PROPERTIES = true;
/** Those waiting for notification of a shutdown. */
private Set shutdownObservers = new HashSet();
/** Indicates whether shutdown has been called. */
private boolean isShutdown = false;
/**
* Gets the CacheHub instance. For backward compatibility, if this creates the instance it will
* attempt to configure it with the default configuration. If you want to configure from your
* own source, use {@link #getUnconfiguredInstance}and then call {@link #configure}
* <p>
* @return CompositeCacheManager
*/
public static synchronized CompositeCacheManager getInstance()
{
if ( instance == null )
{
log.debug( "Instance is null, creating with default config" );
instance = createInstance();
instance.configure();
}
instance.incrementClients();
return instance;
}
/**
* Initializes the cache manager using the props file for the given name.
* <p>
* @param propsFilename
* @return CompositeCacheManager configured from the give propsFileName
*/
public static synchronized CompositeCacheManager getInstance( String propsFilename )
{
if ( instance == null )
{
if ( log.isInfoEnabled() )
{
log.info( "Instance is null, creating with default config [" + propsFilename + "]" );
}
instance = createInstance();
instance.configure( propsFilename );
}
instance.incrementClients();
return instance;
}
/**
* Get a CacheHub instance which is not configured. If an instance already exists, it will be
* returned.
*<p>
* @return CompositeCacheManager
*/
public static synchronized CompositeCacheManager getUnconfiguredInstance()
{
if ( instance == null )
{
if ( log.isInfoEnabled() )
{
log.info( "Instance is null, returning unconfigured instance" );
}
instance = createInstance();
}
instance.incrementClients();
return instance;
}
/**
* Simple factory method, must override in subclasses so getInstance creates / returns the
* correct object.
* <p>
* @return CompositeCacheManager
*/
protected static CompositeCacheManager createInstance()
{
return new CompositeCacheManager();
}
/** CreAtes a shutdown hook */
protected CompositeCacheManager()
{
ShutdownHook shutdownHook = new ShutdownHook();
try
{
Runtime.getRuntime().addShutdownHook( shutdownHook );
}
catch ( AccessControlException e )
{
log.error( "Could not register shutdown hook.", e );
}
}
/**
* Configure with default properties file
*/
public void configure()
{
configure( CacheConstants.DEFAULT_CONFIG );
}
/**
* Configure from specific properties file.
* <p>
* @param propFile Path <u>within classpath </u> to load configuration from
*/
public void configure( String propFile )
{
log.info( "Creating cache manager from config file: " + propFile );
Properties props = new Properties();
InputStream is = getClass().getResourceAsStream( propFile );
if ( is != null )
{
try
{
props.load( is );
if ( log.isDebugEnabled() )
{
log.debug( "File [" + propFile + "] contained " + props.size() + " properties" );
}
}
catch ( IOException ex )
{
log.error( "Failed to load properties for name [" + propFile + "]", ex );
throw new IllegalStateException( ex.getMessage() );
}
finally
{
try
{
is.close();
}
catch ( Exception ignore )
{
// Ignored
}
}
}
else
{
log.error( "Failed to load properties for name [" + propFile + "]" );
throw new IllegalStateException( "Failed to load properties for name [" + propFile + "]" );
}
configure( props );
}
/**
* Configure from properties object.
* <p>
* This method will call confiure, instructing it to use ssytem properties as a default.
* @param props
*/
public void configure( Properties props )
{
configure( props, DEFAULT_USE_SYSTEM_PROPERTIES );
}
/**
* Configure from properties object, overriding with values from the system properteis if
* instructed.
* <p>
* You can override a specific value by passing in a ssytem property:
* <p>
* For example, you could override this value in the cache.ccf file by starting up your program
* with the argument: -Djcs.auxiliary.LTCP.attributes.TcpListenerPort=1111
* <p>
* @param props
* @param useSystemProperties -- if true, values starting with jcs will be put into the props
* file prior to configuring the cache.
*/
public void configure( Properties props, boolean useSystemProperties )
{
if ( props != null )
{
if ( useSystemProperties )
{
// override any setting with values from the system properties.
Properties sysProps = System.getProperties();
Set keys = sysProps.keySet();
Iterator keyIt = keys.iterator();
while ( keyIt.hasNext() )
{
String key = (String) keyIt.next();
if ( key.startsWith( SYSTEM_PROPERTY_KEY_PREFIX ) )
{
if ( log.isInfoEnabled() )
{
log.info( "Using system property [[" + key + "] [" + sysProps.getProperty( key ) + "]]" );
}
props.put( key, sysProps.getProperty( key ) );
}
}
}
// We will expose this for managers that need raw properties.
this.configurationProperties = props;
// set the props value and then configure the ThreadPoolManager
ThreadPoolManager.setProps( props );
ThreadPoolManager poolMgr = ThreadPoolManager.getInstance();
if ( log.isDebugEnabled() )
{
log.debug( "ThreadPoolManager = " + poolMgr );
}
// configure the cache
CompositeCacheConfigurator configurator = new CompositeCacheConfigurator( this );
configurator.doConfigure( props );
}
else
{
log.error( "No properties found. Please configure the cache correctly." );
}
}
/**
* Gets the defaultCacheAttributes attribute of the CacheHub object
* <p>
* @return The defaultCacheAttributes value
*/
public ICompositeCacheAttributes getDefaultCacheAttributes()
{
return this.defaultCacheAttr.copy();
}
/**
* Sets the defaultCacheAttributes attribute of the CacheHub object
* <p>
* @param icca The new defaultCacheAttributes value
*/
public void setDefaultCacheAttributes( ICompositeCacheAttributes icca )
{
this.defaultCacheAttr = icca;
}
/**
* Sets the defaultElementAttributes attribute of the CacheHub object
* <p>
* @param iea The new defaultElementAttributes value
*/
public void setDefaultElementAttributes( IElementAttributes iea )
{
this.defaultElementAttr = iea;
}
/**
* Gets the defaultElementAttributes attribute of the CacheHub object
* <p>
* @return The defaultElementAttributes value
*/
public IElementAttributes getDefaultElementAttributes()
{
return this.defaultElementAttr.copy();
}
/**
* Gets the cache attribute of the CacheHub object
* <p>
* @param cacheName
* @return CompositeCache -- the cache region controller
*/
public CompositeCache getCache( String cacheName )
{
return getCache( cacheName, this.defaultCacheAttr.copy() );
}
/**
* Gets the cache attribute of the CacheHub object
* <p>
* @param cacheName
* @param cattr
* @return CompositeCache
*/
public CompositeCache getCache( String cacheName, ICompositeCacheAttributes cattr )
{
cattr.setCacheName( cacheName );
return getCache( cattr, this.defaultElementAttr );
}
/**
* Gets the cache attribute of the CacheHub object
* <p>
* @param cacheName
* @param cattr
* @param attr
* @return CompositeCache
*/
public CompositeCache getCache( String cacheName, ICompositeCacheAttributes cattr, IElementAttributes attr )
{
cattr.setCacheName( cacheName );
return getCache( cattr, attr );
}
/**
* Gets the cache attribute of the CacheHub object
* <p>
* @param cattr
* @return CompositeCache
*/
public CompositeCache getCache( ICompositeCacheAttributes cattr )
{
return getCache( cattr, this.defaultElementAttr );
}
/**
* If the cache has already been created, then the CacheAttributes and the element Attributes
* will be ignored. Currently there is no overiding the CacheAttributes once it is set up. You
* can change the default ElementAttributes for a region later.
* <p>
* Overriding the default elemental attributes will require changing the way the attributes are
* assigned to elements. Get cache creates a cache with defaults if none are specified. We might
* want to create separate method for creating/getting. . .
* <p>
* @param cattr
* @param attr
* @return CompositeCache
*/
public CompositeCache getCache( ICompositeCacheAttributes cattr, IElementAttributes attr )
{
CompositeCache cache;
if ( log.isDebugEnabled() )
{
log.debug( "attr = " + attr );
}
synchronized ( caches )
{
cache = (CompositeCache) caches.get( cattr.getCacheName() );
if ( cache == null )
{
cattr.setCacheName( cattr.getCacheName() );
CompositeCacheConfigurator configurator = new CompositeCacheConfigurator( this );
cache = configurator.parseRegion( this.getConfigurationProperties(), cattr.getCacheName(),
this.defaultAuxValues, cattr );
caches.put( cattr.getCacheName(), cache );
}
}
return cache;
}
/**
* @param name
*/
public void freeCache( String name )
{
freeCache( name, false );
}
/**
* @param name
* @param fromRemote
*/
public void freeCache( String name, boolean fromRemote )
{
CompositeCache cache = (CompositeCache) caches.remove( name );
if ( cache != null )
{
cache.dispose( fromRemote );
}
}
/**
* Calls freeCache on all regions
*/
public void shutDown()
{
isShutdown = true;
// notify any observers
synchronized ( shutdownObservers )
{
// We don't need to worry about locking the set.
// since this is a shutdown command, nor do we need
// to queue these up.
Iterator it = shutdownObservers.iterator();
while ( it.hasNext() )
{
IShutdownObserver observer = (IShutdownObserver) it.next();
observer.shutdown();
}
}
// do the traditional shutdown of the regions.
String[] names = getCacheNames();
int len = names.length;
for ( int i = 0; i < len; i++ )
{
String name = names[i];
freeCache( name );
}
}
/** */
protected void incrementClients()
{
clients++;
}
/** */
public void release()
{
release( false );
}
/**
* @param fromRemote
*/
private void release( boolean fromRemote )
{
synchronized ( CompositeCacheManager.class )
{
// Wait until called by the last client
if ( --clients > 0 )
{
if ( log.isDebugEnabled() )
{
log.debug( "Release called, but " + clients + " remain" );
return;
}
}
if ( log.isDebugEnabled() )
{
log.debug( "Last client called release. There are " + caches.size() + " caches which will be disposed" );
}
Enumeration allCaches = caches.elements();
while ( allCaches.hasMoreElements() )
{
CompositeCache cache = (CompositeCache) allCaches.nextElement();
if ( cache != null )
{
cache.dispose( fromRemote );
}
}
}
}
/**
* Returns a list of the current cache names.
* @return String[]
*/
public String[] getCacheNames()
{
String[] list = new String[caches.size()];
int i = 0;
for ( Iterator itr = caches.keySet().iterator(); itr.hasNext(); )
{
list[i++] = (String) itr.next();
}
return list;
}
/**
* @return ICacheType.CACHE_HUB
*/
public int getCacheType()
{
return ICacheType.CACHE_HUB;
}
/**
* @return ICompositeCacheAttributes
*/
public ICompositeCacheAttributes getDefaultCattr()
{
return this.defaultCacheAttr;
}
/**
* @param auxFac
*/
void registryFacPut( AuxiliaryCacheFactory auxFac )
{
auxiliaryFactoryRegistry.put( auxFac.getName(), auxFac );
}
/**
* @param name
* @return AuxiliaryCacheFactory
*/
AuxiliaryCacheFactory registryFacGet( String name )
{
return (AuxiliaryCacheFactory) auxiliaryFactoryRegistry.get( name );
}
/**
* @param auxAttr
*/
void registryAttrPut( AuxiliaryCacheAttributes auxAttr )
{
auxiliaryAttributeRegistry.put( auxAttr.getName(), auxAttr );
}
/**
* @param name
* @return AuxiliaryCacheAttributes
*/
AuxiliaryCacheAttributes registryAttrGet( String name )
{
return (AuxiliaryCacheAttributes) auxiliaryAttributeRegistry.get( name );
}
/**
* Gets stats for debugging. This calls gets statistics and then puts all the results in a
* string. This returns data for all regions.
* <p>
* @return String
*/
public String getStats()
{
ICacheStats[] stats = getStatistics();
if ( stats == null )
{
return "NONE";
}
// force the array elements into a string.
StringBuffer buf = new StringBuffer();
int statsLen = stats.length;
for ( int i = 0; i < statsLen; i++ )
{
buf.append( "\n---------------------------\n" );
buf.append( stats[i] );
}
return buf.toString();
}
/**
* This returns data gathered for all regions and all the auxiliaries they currently uses.
* <p>
* @return ICacheStats[]
*/
public ICacheStats[] getStatistics()
{
ArrayList cacheStats = new ArrayList();
Enumeration allCaches = caches.elements();
while ( allCaches.hasMoreElements() )
{
CompositeCache cache = (CompositeCache) allCaches.nextElement();
if ( cache != null )
{
cacheStats.add( cache.getStatistics() );
}
}
ICacheStats[] stats = (ICacheStats[]) cacheStats.toArray( new CacheStats[0] );
return stats;
}
/**
* Perhaps the composite cache itself should be the observable object. It doesn't make much of a
* difference. There are some problems with region by region shutdown. Some auxiliaries are
* global. They will need to track when every region has shutdown before doing things like
* closing the socket with a lateral.
* <p>
* @param observer
*/
public void registerShutdownObserver( IShutdownObserver observer )
{
// synchronized to take care of iteration safety
// during shutdown.
synchronized ( shutdownObservers )
{
// the set will take care of duplication protection
shutdownObservers.add( observer );
}
}
/**
* @param observer
*/
public void deregisterShutdownObserver( IShutdownObserver observer )
{
synchronized ( shutdownObservers )
{
shutdownObservers.remove( observer );
}
}
/**
* This is exposed so other manager can get access to the props.
* <p>
* @param props
*/
public void setConfigurationProperties( Properties props )
{
this.configurationProperties = props;
}
/**
* This is exposed so other manager can get access to the props.
* <p>
* @return the configurationProperties
*/
public Properties getConfigurationProperties()
{
return configurationProperties;
}
/**
* Called on shutdown. This gives use a chance to store the keys and to optimize even if the
* cache manager's shutdown method was not called manually.
*/
class ShutdownHook
extends Thread
{
/**
* This will persist the keys on shutdown.
* <p>
* @see java.lang.Thread#run()
*/
public void run()
{
if ( !isShutdown )
{
log.info( "Shutdown hook activated. Shutdown was not called. Shutting down JCS." );
shutDown();
}
}
}
}