package org.apache.commons.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.lang.management.ManagementFactory; import java.security.AccessControlException; import java.util.ArrayList; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; import javax.management.MBeanServer; import javax.management.ObjectName; import org.apache.commons.jcs.access.exception.CacheException; import org.apache.commons.jcs.admin.JCSAdminBean; import org.apache.commons.jcs.auxiliary.AuxiliaryCache; import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes; import org.apache.commons.jcs.auxiliary.AuxiliaryCacheFactory; import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheConstants; import org.apache.commons.jcs.engine.CacheConstants; import org.apache.commons.jcs.engine.CompositeCacheAttributes; import org.apache.commons.jcs.engine.ElementAttributes; import org.apache.commons.jcs.engine.behavior.ICache; import org.apache.commons.jcs.engine.behavior.ICacheType.CacheType; import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes; import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager; import org.apache.commons.jcs.engine.behavior.IElementAttributes; import org.apache.commons.jcs.engine.behavior.IProvideScheduler; import org.apache.commons.jcs.engine.behavior.IShutdownObserver; import org.apache.commons.jcs.engine.control.event.ElementEventQueue; import org.apache.commons.jcs.engine.control.event.behavior.IElementEventQueue; import org.apache.commons.jcs.engine.stats.CacheStats; import org.apache.commons.jcs.engine.stats.behavior.ICacheStats; import org.apache.commons.jcs.utils.config.OptionConverter; import org.apache.commons.jcs.utils.threadpool.DaemonThreadFactory; import org.apache.commons.jcs.utils.threadpool.ThreadPoolManager; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * 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. */ public class CompositeCacheManager implements IRemoteCacheConstants, ICompositeCacheManager, IProvideScheduler { /** The logger */ private static final Log log = LogFactory.getLog( CompositeCacheManager.class ); /** JMX object name */ public static final String JMX_OBJECT_NAME = "org.apache.commons.jcs:type=JCSAdminBean"; /** default region prefix */ private static final String DEFAULT_REGION = "jcs.default"; /** Caches managed by this cache manager */ private final ConcurrentMap<String, ICache<?, ?>> caches = new ConcurrentHashMap<String, ICache<?, ?>>(); /** Lock for initialization of caches */ private final ReentrantLock cacheLock = new ReentrantLock(); /** Number of clients accessing this cache manager */ private final AtomicInteger clients = new AtomicInteger(0); /** Default cache attributes for this cache manager */ private ICompositeCacheAttributes defaultCacheAttr = new CompositeCacheAttributes(); /** Default element attributes for this cache manager */ private IElementAttributes defaultElementAttr = new ElementAttributes(); /** Used to keep track of configured auxiliaries */ private final ConcurrentMap<String, AuxiliaryCacheFactory> auxiliaryFactoryRegistry = new ConcurrentHashMap<String, AuxiliaryCacheFactory>( ); /** Used to keep track of attributes for auxiliaries. */ private final ConcurrentMap<String, AuxiliaryCacheAttributes> auxiliaryAttributeRegistry = new ConcurrentHashMap<String, AuxiliaryCacheAttributes>( ); /** Used to keep track of configured auxiliaries */ private final ConcurrentMap<String, AuxiliaryCache<?, ?>> auxiliaryCaches = new ConcurrentHashMap<String, AuxiliaryCache<?, ?>>( ); /** 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 */ private String defaultAuxValues; /** The Singleton Instance */ private static CompositeCacheManager instance; /** Should we use system property substitutions. */ private static final boolean DEFAULT_USE_SYSTEM_PROPERTIES = true; /** Once configured, you can force a reconfiguration of sorts. */ private static final boolean DEFAULT_FORCE_RECONFIGURATION = false; /** Stack for those waiting for notification of a shutdown. */ private final LinkedBlockingDeque<IShutdownObserver> shutdownObservers = new LinkedBlockingDeque<IShutdownObserver>(); /** The central background scheduler. */ private ScheduledExecutorService scheduledExecutor; /** The central event queue. */ private IElementEventQueue elementEventQueue; /** Shutdown hook thread instance */ private ShutdownHook shutdownHook; /** Indicates whether the instance has been initialized. */ private boolean isInitialized = false; /** Indicates whether configure has been called. */ private boolean isConfigured = false; /** Indicates whether JMX bean has been registered. */ private boolean isJMXRegistered = false; private String jmxName = JMX_OBJECT_NAME; /** * 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 * @throws CacheException if the configuration cannot be loaded */ public static synchronized CompositeCacheManager getInstance() throws CacheException { return getInstance( CacheConstants.DEFAULT_CONFIG ); } /** * Initializes the cache manager using the props file for the given name. * <p> * @param propsFilename * @return CompositeCacheManager configured from the give propsFileName * @throws CacheException if the configuration cannot be loaded */ public static synchronized CompositeCacheManager getInstance( String propsFilename ) throws CacheException { if ( instance == null ) { if ( log.isInfoEnabled() ) { log.info( "Instance is null, creating with config [" + propsFilename + "]" ); } instance = createInstance(); } if (!instance.isInitialized()) { instance.initialize(); } if (!instance.isConfigured()) { instance.configure( propsFilename ); } instance.clients.incrementAndGet(); 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(); } if (!instance.isInitialized()) { instance.initialize(); } instance.clients.incrementAndGet(); 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(); } /** * Default constructor */ protected CompositeCacheManager() { // empty } /** Creates a shutdown hook and starts the scheduler service */ protected void initialize() { if (!isInitialized) { this.shutdownHook = new ShutdownHook(); try { Runtime.getRuntime().addShutdownHook( shutdownHook ); } catch ( AccessControlException e ) { log.error( "Could not register shutdown hook.", e ); } this.scheduledExecutor = Executors.newScheduledThreadPool(4, new DaemonThreadFactory("JCS-Scheduler-", Thread.MIN_PRIORITY)); // Register JMX bean if (!isJMXRegistered && jmxName != null) { MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); JCSAdminBean adminBean = new JCSAdminBean(this); try { ObjectName jmxObjectName = new ObjectName(jmxName); mbs.registerMBean(adminBean, jmxObjectName); isJMXRegistered = true; } catch (Exception e) { log.warn( "Could not register JMX bean.", e ); } } this.elementEventQueue = new ElementEventQueue(); isInitialized = true; } } /** * Get the element event queue * * @return the elementEventQueue */ public IElementEventQueue getElementEventQueue() { return elementEventQueue; } /** * Get the scheduler service * * @return the scheduledExecutor */ @Override public ScheduledExecutorService getScheduledExecutorService() { return scheduledExecutor; } /** * Configure with default properties file * @throws CacheException if the configuration cannot be loaded */ public void configure() throws CacheException { configure( CacheConstants.DEFAULT_CONFIG ); } /** * Configure from specific properties file. * <p> * @param propFile Path <u>within classpath </u> to load configuration from * @throws CacheException if the configuration cannot be loaded */ public void configure( String propFile ) throws CacheException { 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 ) { throw new CacheException("Failed to load properties for name [" + propFile + "]", ex); } finally { try { is.close(); } catch ( IOException ignore ) { // Ignored } } } else { throw new CacheException( "Failed to read configuration file [" + propFile + "]" ); } configure( props ); } /** * Configure from properties object. * <p> * This method will call configure, instructing it to use system 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 properties if * instructed. * <p> * You can override a specific value by passing in a system 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 ) { configure( props, useSystemProperties, DEFAULT_FORCE_RECONFIGURATION ); } /** * Configure from properties object, overriding with values from the system properties if * instructed. * <p> * You can override a specific value by passing in a system 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. * @param forceReconfiguration - if the manager is already configured, we will try again. This * may not work properly. */ public synchronized void configure( Properties props, boolean useSystemProperties, boolean forceReconfiguration ) { if ( props == null ) { log.error( "No properties found. Please configure the cache correctly." ); return; } if ( isConfigured ) { if ( !forceReconfiguration ) { if ( log.isDebugEnabled() ) { log.debug( "Configure called after the manager has been configured. " + "Force reconfiguration is false. Doing nothing" ); } return; } else { if ( log.isInfoEnabled() ) { log.info( "Configure called after the manager has been configured. " + "Force reconfiguration is true. Reconfiguring as best we can." ); } } } if ( useSystemProperties ) { CompositeCacheConfigurator.overrideWithSystemProperties( props ); } doConfigure( props ); } /** * Configure the cache using the supplied properties. * <p> * @param properties assumed not null */ private void doConfigure( Properties properties ) { // We will expose this for managers that need raw properties. this.configurationProperties = properties; // set the props value and then configure the ThreadPoolManager ThreadPoolManager.setProps( properties ); ThreadPoolManager poolMgr = ThreadPoolManager.getInstance(); if ( log.isDebugEnabled() ) { log.debug( "ThreadPoolManager = " + poolMgr ); } // configure the cache CompositeCacheConfigurator configurator = new CompositeCacheConfigurator(); long start = System.currentTimeMillis(); // set default value list this.defaultAuxValues = OptionConverter.findAndSubst( CompositeCacheManager.DEFAULT_REGION, properties ); log.info( "Setting default auxiliaries to " + this.defaultAuxValues ); // set default cache attr this.defaultCacheAttr = configurator.parseCompositeCacheAttributes( properties, "", new CompositeCacheAttributes(), DEFAULT_REGION ); log.info( "setting defaultCompositeCacheAttributes to " + this.defaultCacheAttr ); // set default element attr this.defaultElementAttr = configurator.parseElementAttributes( properties, "", new ElementAttributes(), DEFAULT_REGION ); log.info( "setting defaultElementAttributes to " + this.defaultElementAttr ); // set up system caches to be used by non system caches // need to make sure there is no circularity of reference configurator.parseSystemRegions( properties, this ); // setup preconfigured caches configurator.parseRegions( properties, this ); long end = System.currentTimeMillis(); if ( log.isInfoEnabled() ) { log.info( "Finished configuration in " + ( end - start ) + " ms." ); } isConfigured = true; } /** * Gets the defaultCacheAttributes attribute of the CacheHub object * <p> * @return The defaultCacheAttributes value */ public ICompositeCacheAttributes getDefaultCacheAttributes() { return this.defaultCacheAttr.clone(); } /** * Gets the defaultElementAttributes attribute of the CacheHub object * <p> * @return The defaultElementAttributes value */ public IElementAttributes getDefaultElementAttributes() { return this.defaultElementAttr.clone(); } /** * Gets the cache attribute of the CacheHub object * <p> * @param cacheName * @return CompositeCache -- the cache region controller */ @Override public <K, V> CompositeCache<K, V> getCache( String cacheName ) { return getCache( cacheName, this.defaultCacheAttr.clone() ); } /** * Gets the cache attribute of the CacheHub object * <p> * @param cacheName * @param cattr * @return CompositeCache */ public <K, V> CompositeCache<K, V> 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 <K, V> CompositeCache<K, V> 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 <K, V> CompositeCache<K, V> 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 overriding 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 */ @SuppressWarnings("unchecked") // Need to cast because of common map for all caches public <K, V> CompositeCache<K, V> getCache( ICompositeCacheAttributes cattr, IElementAttributes attr ) { CompositeCache<K, V> cache; if ( log.isDebugEnabled() ) { log.debug( "attr = " + attr ); } cache = (CompositeCache<K, V>) caches.get( cattr.getCacheName() ); if (cache == null) { cacheLock.lock(); try { // double check cache = (CompositeCache<K, V>) caches.get( cattr.getCacheName() ); if ( cache == null ) { CompositeCacheConfigurator configurator = new CompositeCacheConfigurator(); cache = configurator.parseRegion( this.getConfigurationProperties(), this, cattr.getCacheName(), this.defaultAuxValues, cattr ); caches.put( cattr.getCacheName(), cache ); } } finally { cacheLock.unlock(); } } 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() { synchronized (CompositeCacheManager.class) { // shutdown element event queue this.elementEventQueue.dispose(); // shutdown all scheduled jobs this.scheduledExecutor.shutdownNow(); // shutdown all thread pools ThreadPoolManager.dispose(); // notify any observers IShutdownObserver observer = null; while ((observer = shutdownObservers.poll()) != null) { observer.shutdown(); } // Unregister JMX bean if (isJMXRegistered) { MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); try { ObjectName jmxObjectName = new ObjectName(jmxName); mbs.unregisterMBean(jmxObjectName); } catch (Exception e) { log.warn( "Could not unregister JMX bean.", e ); } isJMXRegistered = false; } // do the traditional shutdown of the regions. for (String name : getCacheNames()) { freeCache( name ); } // shut down factories for (AuxiliaryCacheFactory factory : auxiliaryFactoryRegistry.values()) { factory.dispose(); } auxiliaryAttributeRegistry.clear(); auxiliaryFactoryRegistry.clear(); if (shutdownHook != null) { try { Runtime.getRuntime().removeShutdownHook(shutdownHook); } catch (IllegalStateException e) { // May fail if the JVM is already shutting down } this.shutdownHook = null; } isConfigured = false; isInitialized = false; } } /** */ public void release() { release( false ); } /** * @param fromRemote */ private void release( boolean fromRemote ) { synchronized ( CompositeCacheManager.class ) { // Wait until called by the last client if ( clients.decrementAndGet() > 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" ); } for (ICache<?, ?> c : caches.values() ) { CompositeCache<?, ?> cache = (CompositeCache<?, ?>) c; if ( cache != null ) { cache.dispose( fromRemote ); } } } } /** * Returns a list of the current cache names. * @return String[] */ public String[] getCacheNames() { return caches.keySet().toArray(new String[caches.size()]); } /** * @return ICacheType.CACHE_HUB */ public CacheType getCacheType() { return CacheType.CACHE_HUB; } /** * @param auxFac */ public void registryFacPut( AuxiliaryCacheFactory auxFac ) { auxiliaryFactoryRegistry.put( auxFac.getName(), auxFac ); } /** * @param name * @return AuxiliaryCacheFactory */ public AuxiliaryCacheFactory registryFacGet( String name ) { return auxiliaryFactoryRegistry.get( name ); } /** * @param auxAttr */ public void registryAttrPut( AuxiliaryCacheAttributes auxAttr ) { auxiliaryAttributeRegistry.put( auxAttr.getName(), auxAttr ); } /** * @param name * @return AuxiliaryCacheAttributes */ public AuxiliaryCacheAttributes registryAttrGet( String name ) { return auxiliaryAttributeRegistry.get( name ); } /** * Add a cache to the map of registered caches * * @param cacheName the region name * @param cache the cache instance */ public void addCache(String cacheName, ICache<?, ?> cache) { caches.put(cacheName, cache); } /** * Add a cache to the map of registered auxiliary caches * * @param auxName the auxiliary name * @param cacheName the region name * @param cache the cache instance */ public void addAuxiliaryCache(String auxName, String cacheName, AuxiliaryCache<?, ?> cache) { String key = String.format("aux.%s.region.%s", auxName, cacheName); auxiliaryCaches.put(key, cache); } /** * Get a cache from the map of registered auxiliary caches * * @param auxName the auxiliary name * @param cacheName the region name * * @return the cache instance */ @Override @SuppressWarnings("unchecked") // because of common map for all auxiliary caches public <K, V> AuxiliaryCache<K, V> getAuxiliaryCache(String auxName, String cacheName) { String key = String.format("aux.%s.region.%s", auxName, cacheName); return (AuxiliaryCache<K, V>) auxiliaryCaches.get(key); } /** * 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 */ @Override public String getStats() { ICacheStats[] stats = getStatistics(); if ( stats == null ) { return "NONE"; } // force the array elements into a string. StringBuilder buf = new StringBuilder(); 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<ICacheStats> cacheStats = new ArrayList<ICacheStats>(); for (ICache<?, ?> c : caches.values()) { CompositeCache<?, ?> cache = (CompositeCache<?, ?>) c; if ( cache != null ) { cacheStats.add( cache.getStatistics() ); } } ICacheStats[] stats = 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 */ @Override public void registerShutdownObserver( IShutdownObserver observer ) { if (!shutdownObservers.contains(observer)) { shutdownObservers.push( observer ); } else { log.warn("Shutdown observer added twice " + observer); } } /** * @param observer */ @Override public void deregisterShutdownObserver( IShutdownObserver observer ) { shutdownObservers.remove( observer ); } /** * This is exposed so other manager can get access to the props. * <p> * @return the configurationProperties */ @Override public Properties getConfigurationProperties() { return configurationProperties; } /** * @return the isInitialized */ public boolean isInitialized() { return isInitialized; } /** * @return the isConfigured */ public boolean isConfigured() { return isConfigured; } public void setJmxName(final String name) { if (isJMXRegistered) { throw new IllegalStateException("Too late, MBean registration is done"); } jmxName = name; } /** * 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() */ @SuppressWarnings("synthetic-access") @Override public void run() { if ( isInitialized() ) { log.info( "Shutdown hook activated. Shutdown was not called. Shutting down JCS." ); shutDown(); } } } }