001package org.apache.commons.jcs.engine.control;
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.io.InputStream;
024import java.lang.management.ManagementFactory;
025import java.security.AccessControlException;
026import java.util.ArrayList;
027import java.util.Properties;
028import java.util.concurrent.ConcurrentHashMap;
029import java.util.concurrent.ConcurrentMap;
030import java.util.concurrent.Executors;
031import java.util.concurrent.LinkedBlockingDeque;
032import java.util.concurrent.ScheduledExecutorService;
033import java.util.concurrent.atomic.AtomicInteger;
034import java.util.concurrent.locks.ReentrantLock;
035
036import javax.management.MBeanServer;
037import javax.management.ObjectName;
038
039import org.apache.commons.jcs.access.exception.CacheException;
040import org.apache.commons.jcs.admin.JCSAdminBean;
041import org.apache.commons.jcs.auxiliary.AuxiliaryCache;
042import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
043import org.apache.commons.jcs.auxiliary.AuxiliaryCacheFactory;
044import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheConstants;
045import org.apache.commons.jcs.engine.CacheConstants;
046import org.apache.commons.jcs.engine.CompositeCacheAttributes;
047import org.apache.commons.jcs.engine.ElementAttributes;
048import org.apache.commons.jcs.engine.behavior.ICache;
049import org.apache.commons.jcs.engine.behavior.ICacheType.CacheType;
050import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes;
051import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
052import org.apache.commons.jcs.engine.behavior.IElementAttributes;
053import org.apache.commons.jcs.engine.behavior.IProvideScheduler;
054import org.apache.commons.jcs.engine.behavior.IShutdownObserver;
055import org.apache.commons.jcs.engine.control.event.ElementEventQueue;
056import org.apache.commons.jcs.engine.control.event.behavior.IElementEventQueue;
057import org.apache.commons.jcs.engine.stats.CacheStats;
058import org.apache.commons.jcs.engine.stats.behavior.ICacheStats;
059import org.apache.commons.jcs.utils.config.OptionConverter;
060import org.apache.commons.jcs.utils.threadpool.DaemonThreadFactory;
061import org.apache.commons.jcs.utils.threadpool.ThreadPoolManager;
062import org.apache.commons.logging.Log;
063import org.apache.commons.logging.LogFactory;
064
065/**
066 * Manages a composite cache. This provides access to caches and is the primary way to shutdown the
067 * caching system as a whole.
068 * <p>
069 * The composite cache manager is responsible for creating / configuring cache regions. It serves as
070 * a factory for the ComositeCache class. The CompositeCache is the core of JCS, the hub for various
071 * auxiliaries.
072 */
073public class CompositeCacheManager
074    implements IRemoteCacheConstants, ICompositeCacheManager, IProvideScheduler
075{
076    /** The logger */
077    private static final Log log = LogFactory.getLog( CompositeCacheManager.class );
078
079    /** JMX object name */
080    public static final String JMX_OBJECT_NAME = "org.apache.commons.jcs:type=JCSAdminBean";
081
082    /** default region prefix */
083    private static final String DEFAULT_REGION = "jcs.default";
084
085    /** Caches managed by this cache manager */
086    private final ConcurrentMap<String, ICache<?, ?>> caches =
087        new ConcurrentHashMap<String, ICache<?, ?>>();
088
089    /** Lock for initialization of caches */
090    private final ReentrantLock cacheLock = new ReentrantLock();
091
092    /** Number of clients accessing this cache manager */
093    private final AtomicInteger clients = new AtomicInteger(0);
094
095    /** Default cache attributes for this cache manager */
096    private ICompositeCacheAttributes defaultCacheAttr = new CompositeCacheAttributes();
097
098    /** Default element attributes for this cache manager */
099    private IElementAttributes defaultElementAttr = new ElementAttributes();
100
101    /** Used to keep track of configured auxiliaries */
102    private final ConcurrentMap<String, AuxiliaryCacheFactory> auxiliaryFactoryRegistry =
103        new ConcurrentHashMap<String, AuxiliaryCacheFactory>( );
104
105    /** Used to keep track of attributes for auxiliaries. */
106    private final ConcurrentMap<String, AuxiliaryCacheAttributes> auxiliaryAttributeRegistry =
107        new ConcurrentHashMap<String, AuxiliaryCacheAttributes>( );
108
109    /** Used to keep track of configured auxiliaries */
110    private final ConcurrentMap<String, AuxiliaryCache<?, ?>> auxiliaryCaches =
111        new ConcurrentHashMap<String, AuxiliaryCache<?, ?>>( );
112
113    /** Properties with which this manager was configured. This is exposed for other managers. */
114    private Properties configurationProperties;
115
116    /** The default auxiliary caches to be used if not preconfigured */
117    private String defaultAuxValues;
118
119    /** The Singleton Instance */
120    private static CompositeCacheManager instance;
121
122    /** Should we use system property substitutions. */
123    private static final boolean DEFAULT_USE_SYSTEM_PROPERTIES = true;
124
125    /** Once configured, you can force a reconfiguration of sorts. */
126    private static final boolean DEFAULT_FORCE_RECONFIGURATION = false;
127
128    /** Stack for those waiting for notification of a shutdown. */
129    private final LinkedBlockingDeque<IShutdownObserver> shutdownObservers = new LinkedBlockingDeque<IShutdownObserver>();
130
131    /** The central background scheduler. */
132    private ScheduledExecutorService scheduledExecutor;
133
134    /** The central event queue. */
135    private IElementEventQueue elementEventQueue;
136
137    /** Shutdown hook thread instance */
138    private ShutdownHook shutdownHook;
139
140    /** Indicates whether the instance has been initialized. */
141    private boolean isInitialized = false;
142
143    /** Indicates whether configure has been called. */
144    private boolean isConfigured = false;
145
146    /** Indicates whether JMX bean has been registered. */
147    private boolean isJMXRegistered = false;
148
149    private String jmxName = JMX_OBJECT_NAME;
150
151    /**
152     * Gets the CacheHub instance. For backward compatibility, if this creates the instance it will
153     * attempt to configure it with the default configuration. If you want to configure from your
154     * own source, use {@link #getUnconfiguredInstance}and then call {@link #configure}
155     * <p>
156     * @return CompositeCacheManager
157     * @throws CacheException if the configuration cannot be loaded
158     */
159    public static synchronized CompositeCacheManager getInstance() throws CacheException
160    {
161        return getInstance( CacheConstants.DEFAULT_CONFIG );
162    }
163
164    /**
165     * Initializes the cache manager using the props file for the given name.
166     * <p>
167     * @param propsFilename
168     * @return CompositeCacheManager configured from the give propsFileName
169     * @throws CacheException if the configuration cannot be loaded
170     */
171    public static synchronized CompositeCacheManager getInstance( String propsFilename ) throws CacheException
172    {
173        if ( instance == null )
174        {
175            if ( log.isInfoEnabled() )
176            {
177                log.info( "Instance is null, creating with config [" + propsFilename + "]" );
178            }
179
180            instance = createInstance();
181        }
182
183        if (!instance.isInitialized())
184        {
185            instance.initialize();
186        }
187
188        if (!instance.isConfigured())
189        {
190            instance.configure( propsFilename );
191        }
192
193        instance.clients.incrementAndGet();
194
195        return instance;
196    }
197
198    /**
199     * Get a CacheHub instance which is not configured. If an instance already exists, it will be
200     * returned.
201     *<p>
202     * @return CompositeCacheManager
203     */
204    public static synchronized CompositeCacheManager getUnconfiguredInstance()
205    {
206        if ( instance == null )
207        {
208            if ( log.isInfoEnabled() )
209            {
210                log.info( "Instance is null, returning unconfigured instance" );
211            }
212
213            instance = createInstance();
214        }
215
216        if (!instance.isInitialized())
217        {
218            instance.initialize();
219        }
220
221        instance.clients.incrementAndGet();
222
223        return instance;
224    }
225
226    /**
227     * Simple factory method, must override in subclasses so getInstance creates / returns the
228     * correct object.
229     * <p>
230     * @return CompositeCacheManager
231     */
232    protected static CompositeCacheManager createInstance()
233    {
234        return new CompositeCacheManager();
235    }
236
237    /**
238     * Default constructor
239     */
240    protected CompositeCacheManager()
241    {
242        // empty
243    }
244
245    /** Creates a shutdown hook and starts the scheduler service */
246    protected void initialize()
247    {
248        if (!isInitialized)
249        {
250            this.shutdownHook = new ShutdownHook();
251            try
252            {
253                Runtime.getRuntime().addShutdownHook( shutdownHook );
254            }
255            catch ( AccessControlException e )
256            {
257                log.error( "Could not register shutdown hook.", e );
258            }
259
260            this.scheduledExecutor = Executors.newScheduledThreadPool(4,
261                    new DaemonThreadFactory("JCS-Scheduler-", Thread.MIN_PRIORITY));
262
263            // Register JMX bean
264            if (!isJMXRegistered && jmxName != null)
265            {
266                MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
267                JCSAdminBean adminBean = new JCSAdminBean(this);
268                try
269                {
270                    ObjectName jmxObjectName = new ObjectName(jmxName);
271                    mbs.registerMBean(adminBean, jmxObjectName);
272                    isJMXRegistered = true;
273                }
274                catch (Exception e)
275                {
276                    log.warn( "Could not register JMX bean.", e );
277                }
278            }
279
280            this.elementEventQueue = new ElementEventQueue();
281
282            isInitialized = true;
283        }
284    }
285
286    /**
287     * Get the element event queue
288     *
289     * @return the elementEventQueue
290     */
291    public IElementEventQueue getElementEventQueue()
292    {
293        return elementEventQueue;
294    }
295
296    /**
297     * Get the scheduler service
298     *
299     * @return the scheduledExecutor
300     */
301    @Override
302    public ScheduledExecutorService getScheduledExecutorService()
303    {
304        return scheduledExecutor;
305    }
306
307    /**
308     * Configure with default properties file
309     * @throws CacheException if the configuration cannot be loaded
310     */
311    public void configure() throws CacheException
312    {
313        configure( CacheConstants.DEFAULT_CONFIG );
314    }
315
316    /**
317     * Configure from specific properties file.
318     * <p>
319     * @param propFile Path <u>within classpath </u> to load configuration from
320     * @throws CacheException if the configuration cannot be loaded
321     */
322    public void configure( String propFile ) throws CacheException
323    {
324        log.info( "Creating cache manager from config file: " + propFile );
325
326        Properties props = new Properties();
327
328        InputStream is = getClass().getResourceAsStream( propFile );
329
330        if ( is != null )
331        {
332            try
333            {
334                props.load( is );
335
336                if ( log.isDebugEnabled() )
337                {
338                    log.debug( "File [" + propFile + "] contained " + props.size() + " properties" );
339                }
340            }
341            catch ( IOException ex )
342            {
343                throw new CacheException("Failed to load properties for name [" + propFile + "]", ex);
344            }
345            finally
346            {
347                try
348                {
349                    is.close();
350                }
351                catch ( IOException ignore )
352                {
353                    // Ignored
354                }
355            }
356        }
357        else
358        {
359            throw new CacheException( "Failed to read configuration file [" + propFile + "]" );
360        }
361
362        configure( props );
363    }
364
365    /**
366     * Configure from properties object.
367     * <p>
368     * This method will call configure, instructing it to use system properties as a default.
369     * @param props
370     */
371    public void configure( Properties props )
372    {
373        configure( props, DEFAULT_USE_SYSTEM_PROPERTIES );
374    }
375
376    /**
377     * Configure from properties object, overriding with values from the system properties if
378     * instructed.
379     * <p>
380     * You can override a specific value by passing in a system property:
381     * <p>
382     * For example, you could override this value in the cache.ccf file by starting up your program
383     * with the argument: -Djcs.auxiliary.LTCP.attributes.TcpListenerPort=1111
384     * <p>
385     * @param props
386     * @param useSystemProperties -- if true, values starting with jcs will be put into the props
387     *            file prior to configuring the cache.
388     */
389    public void configure( Properties props, boolean useSystemProperties )
390    {
391        configure( props, useSystemProperties, DEFAULT_FORCE_RECONFIGURATION );
392    }
393
394    /**
395     * Configure from properties object, overriding with values from the system properties if
396     * instructed.
397     * <p>
398     * You can override a specific value by passing in a system property:
399     * <p>
400     * For example, you could override this value in the cache.ccf file by starting up your program
401     * with the argument: -Djcs.auxiliary.LTCP.attributes.TcpListenerPort=1111
402     * <p>
403     * @param props
404     * @param useSystemProperties -- if true, values starting with jcs will be put into the props
405     *            file prior to configuring the cache.
406     * @param forceReconfiguration - if the manager is already configured, we will try again. This
407     *            may not work properly.
408     */
409    public synchronized void configure( Properties props, boolean useSystemProperties, boolean forceReconfiguration )
410    {
411        if ( props == null )
412        {
413            log.error( "No properties found.  Please configure the cache correctly." );
414            return;
415        }
416
417        if ( isConfigured )
418        {
419            if ( !forceReconfiguration )
420            {
421                if ( log.isDebugEnabled() )
422                {
423                    log.debug( "Configure called after the manager has been configured.  "
424                        + "Force reconfiguration is false.  Doing nothing" );
425                }
426                return;
427            }
428            else
429            {
430                if ( log.isInfoEnabled() )
431                {
432                    log.info( "Configure called after the manager has been configured.  "
433                        + "Force reconfiguration is true.  Reconfiguring as best we can." );
434                }
435            }
436        }
437        if ( useSystemProperties )
438        {
439            CompositeCacheConfigurator.overrideWithSystemProperties( props );
440        }
441        doConfigure( props );
442    }
443
444    /**
445     * Configure the cache using the supplied properties.
446     * <p>
447     * @param properties assumed not null
448     */
449    private void doConfigure( Properties properties )
450    {
451        // We will expose this for managers that need raw properties.
452        this.configurationProperties = properties;
453
454        // set the props value and then configure the ThreadPoolManager
455        ThreadPoolManager.setProps( properties );
456        ThreadPoolManager poolMgr = ThreadPoolManager.getInstance();
457        if ( log.isDebugEnabled() )
458        {
459            log.debug( "ThreadPoolManager = " + poolMgr );
460        }
461
462        // configure the cache
463        CompositeCacheConfigurator configurator = newConfigurator();
464
465        long start = System.currentTimeMillis();
466
467        // set default value list
468        this.defaultAuxValues = OptionConverter.findAndSubst( CompositeCacheManager.DEFAULT_REGION,
469                properties );
470
471        log.info( "Setting default auxiliaries to " + this.defaultAuxValues );
472
473        // set default cache attr
474        this.defaultCacheAttr = configurator.parseCompositeCacheAttributes( properties, "",
475                new CompositeCacheAttributes(), DEFAULT_REGION );
476
477        log.info( "setting defaultCompositeCacheAttributes to " + this.defaultCacheAttr );
478
479        // set default element attr
480        this.defaultElementAttr = configurator.parseElementAttributes( properties, "",
481                new ElementAttributes(), DEFAULT_REGION );
482
483        log.info( "setting defaultElementAttributes to " + this.defaultElementAttr );
484
485        // set up system caches to be used by non system caches
486        // need to make sure there is no circularity of reference
487        configurator.parseSystemRegions( properties, this );
488
489        // setup preconfigured caches
490        configurator.parseRegions( properties, this );
491
492        long end = System.currentTimeMillis();
493        if ( log.isInfoEnabled() )
494        {
495            log.info( "Finished configuration in " + ( end - start ) + " ms." );
496        }
497
498        isConfigured = true;
499    }
500
501    /**
502     * Gets the defaultCacheAttributes attribute of the CacheHub object
503     * <p>
504     * @return The defaultCacheAttributes value
505     */
506    public ICompositeCacheAttributes getDefaultCacheAttributes()
507    {
508        return this.defaultCacheAttr.clone();
509    }
510
511    /**
512     * Gets the defaultElementAttributes attribute of the CacheHub object
513     * <p>
514     * @return The defaultElementAttributes value
515     */
516    public IElementAttributes getDefaultElementAttributes()
517    {
518        return this.defaultElementAttr.clone();
519    }
520
521    /**
522     * Gets the cache attribute of the CacheHub object
523     * <p>
524     * @param cacheName
525     * @return CompositeCache -- the cache region controller
526     */
527    @Override
528    public <K, V> CompositeCache<K, V> getCache( String cacheName )
529    {
530        return getCache( cacheName, getDefaultCacheAttributes() );
531    }
532
533    /**
534     * Gets the cache attribute of the CacheHub object
535     * <p>
536     * @param cacheName
537     * @param cattr
538     * @return CompositeCache
539     */
540    public <K, V> CompositeCache<K, V> getCache( String cacheName, ICompositeCacheAttributes cattr )
541    {
542        cattr.setCacheName( cacheName );
543        return getCache( cattr, getDefaultElementAttributes() );
544    }
545
546    /**
547     * Gets the cache attribute of the CacheHub object
548     * <p>
549     * @param cacheName
550     * @param cattr
551     * @param attr
552     * @return CompositeCache
553     */
554    public <K, V> CompositeCache<K, V>  getCache( String cacheName, ICompositeCacheAttributes cattr, IElementAttributes attr )
555    {
556        cattr.setCacheName( cacheName );
557        return getCache( cattr, attr );
558    }
559
560    /**
561     * Gets the cache attribute of the CacheHub object
562     * <p>
563     * @param cattr
564     * @return CompositeCache
565     */
566    public <K, V> CompositeCache<K, V>  getCache( ICompositeCacheAttributes cattr )
567    {
568        return getCache( cattr, getDefaultElementAttributes() );
569    }
570
571    /**
572     * If the cache has already been created, then the CacheAttributes and the element Attributes
573     * will be ignored. Currently there is no overriding the CacheAttributes once it is set up. You
574     * can change the default ElementAttributes for a region later.
575     * <p>
576     * Overriding the default elemental attributes will require changing the way the attributes are
577     * assigned to elements. Get cache creates a cache with defaults if none are specified. We might
578     * want to create separate method for creating/getting. . .
579     * <p>
580     * @param cattr
581     * @param attr
582     * @return CompositeCache
583     */
584    @SuppressWarnings("unchecked") // Need to cast because of common map for all caches
585    public <K, V> CompositeCache<K, V>  getCache( ICompositeCacheAttributes cattr, IElementAttributes attr )
586    {
587        CompositeCache<K, V> cache;
588
589        if ( log.isDebugEnabled() )
590        {
591            log.debug( "attr = " + attr );
592        }
593
594        cache = (CompositeCache<K, V>) caches.get( cattr.getCacheName() );
595
596        if (cache == null)
597        {
598            cacheLock.lock();
599
600            try
601            {
602                // double check
603                cache = (CompositeCache<K, V>) caches.get( cattr.getCacheName() );
604
605                if ( cache == null )
606                {
607                    CompositeCacheConfigurator configurator = newConfigurator();
608
609                    cache = configurator.parseRegion( this.getConfigurationProperties(), this, cattr.getCacheName(),
610                                                      this.defaultAuxValues, cattr );
611
612                    caches.put( cattr.getCacheName(), cache );
613                }
614            }
615            finally
616            {
617                cacheLock.unlock();
618            }
619        }
620
621        return cache;
622    }
623
624    protected CompositeCacheConfigurator newConfigurator() {
625        return new CompositeCacheConfigurator();
626    }
627
628    /**
629     * @param name
630     */
631    public void freeCache( String name )
632    {
633        freeCache( name, false );
634    }
635
636    /**
637     * @param name
638     * @param fromRemote
639     */
640    public void freeCache( String name, boolean fromRemote )
641    {
642        CompositeCache<?, ?> cache = (CompositeCache<?, ?>) caches.remove( name );
643
644        if ( cache != null )
645        {
646            cache.dispose( fromRemote );
647        }
648    }
649
650    /**
651     * Calls freeCache on all regions
652     */
653    public void shutDown()
654    {
655        synchronized (CompositeCacheManager.class)
656        {
657            // shutdown element event queue
658            this.elementEventQueue.dispose();
659
660            // shutdown all scheduled jobs
661            this.scheduledExecutor.shutdownNow();
662
663            // shutdown all thread pools
664            ThreadPoolManager.dispose();
665
666            // notify any observers
667            IShutdownObserver observer = null;
668            while ((observer = shutdownObservers.poll()) != null)
669            {
670                observer.shutdown();
671            }
672
673            // Unregister JMX bean
674            if (isJMXRegistered)
675            {
676                MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
677                try
678                {
679                    ObjectName jmxObjectName = new ObjectName(jmxName);
680                    mbs.unregisterMBean(jmxObjectName);
681                }
682                catch (Exception e)
683                {
684                    log.warn( "Could not unregister JMX bean.", e );
685                }
686
687                isJMXRegistered = false;
688            }
689
690            // do the traditional shutdown of the regions.
691            for (String name : getCacheNames())
692            {
693                freeCache( name );
694            }
695
696            // shut down factories
697            for (AuxiliaryCacheFactory factory : auxiliaryFactoryRegistry.values())
698            {
699                factory.dispose();
700            }
701
702            auxiliaryAttributeRegistry.clear();
703            auxiliaryFactoryRegistry.clear();
704
705            if (shutdownHook != null)
706            {
707                try
708                {
709                    Runtime.getRuntime().removeShutdownHook(shutdownHook);
710                }
711                catch (IllegalStateException e)
712                {
713                    // May fail if the JVM is already shutting down
714                }
715
716                this.shutdownHook = null;
717            }
718
719            isConfigured = false;
720            isInitialized = false;
721        }
722    }
723
724    /** */
725    public void release()
726    {
727        release( false );
728    }
729
730    /**
731     * @param fromRemote
732     */
733    private void release( boolean fromRemote )
734    {
735        synchronized ( CompositeCacheManager.class )
736        {
737            // Wait until called by the last client
738            if ( clients.decrementAndGet() > 0 )
739            {
740                if ( log.isDebugEnabled() )
741                {
742                    log.debug( "Release called, but " + clients + " remain" );
743                }
744                return;
745            }
746
747            if ( log.isDebugEnabled() )
748            {
749                log.debug( "Last client called release. There are " + caches.size() + " caches which will be disposed" );
750            }
751
752            for (ICache<?, ?> c : caches.values() )
753            {
754                CompositeCache<?, ?> cache = (CompositeCache<?, ?>) c;
755
756                if ( cache != null )
757                {
758                    cache.dispose( fromRemote );
759                }
760            }
761        }
762    }
763
764    /**
765     * Returns a list of the current cache names.
766     * @return String[]
767     */
768    public String[] getCacheNames()
769    {
770        return caches.keySet().toArray(new String[caches.size()]);
771    }
772
773    /**
774     * @return ICacheType.CACHE_HUB
775     */
776    public CacheType getCacheType()
777    {
778        return CacheType.CACHE_HUB;
779    }
780
781    /**
782     * @param auxFac
783     */
784    public void registryFacPut( AuxiliaryCacheFactory auxFac )
785    {
786        auxiliaryFactoryRegistry.put( auxFac.getName(), auxFac );
787    }
788
789    /**
790     * @param name
791     * @return AuxiliaryCacheFactory
792     */
793    public AuxiliaryCacheFactory registryFacGet( String name )
794    {
795        return auxiliaryFactoryRegistry.get( name );
796    }
797
798    /**
799     * @param auxAttr
800     */
801    public void registryAttrPut( AuxiliaryCacheAttributes auxAttr )
802    {
803        auxiliaryAttributeRegistry.put( auxAttr.getName(), auxAttr );
804    }
805
806    /**
807     * @param name
808     * @return AuxiliaryCacheAttributes
809     */
810    public AuxiliaryCacheAttributes registryAttrGet( String name )
811    {
812        return auxiliaryAttributeRegistry.get( name );
813    }
814
815    /**
816     * Add a cache to the map of registered caches
817     *
818     * @param cacheName the region name
819     * @param cache the cache instance
820     */
821    public void addCache(String cacheName, ICache<?, ?> cache)
822    {
823        caches.put(cacheName, cache);
824    }
825
826    /**
827     * Add a cache to the map of registered auxiliary caches
828     *
829     * @param auxName the auxiliary name
830     * @param cacheName the region name
831     * @param cache the cache instance
832     */
833    public void addAuxiliaryCache(String auxName, String cacheName, AuxiliaryCache<?, ?> cache)
834    {
835        String key = String.format("aux.%s.region.%s", auxName, cacheName);
836        auxiliaryCaches.put(key, cache);
837    }
838
839    /**
840     * Get a cache from the map of registered auxiliary caches
841     *
842     * @param auxName the auxiliary name
843     * @param cacheName the region name
844     *
845     * @return the cache instance
846     */
847    @Override
848    @SuppressWarnings("unchecked") // because of common map for all auxiliary caches
849    public <K, V> AuxiliaryCache<K, V> getAuxiliaryCache(String auxName, String cacheName)
850    {
851        String key = String.format("aux.%s.region.%s", auxName, cacheName);
852        return (AuxiliaryCache<K, V>) auxiliaryCaches.get(key);
853    }
854
855    /**
856     * Gets stats for debugging. This calls gets statistics and then puts all the results in a
857     * string. This returns data for all regions.
858     * <p>
859     * @return String
860     */
861    @Override
862    public String getStats()
863    {
864        ICacheStats[] stats = getStatistics();
865        if ( stats == null )
866        {
867            return "NONE";
868        }
869
870        // force the array elements into a string.
871        StringBuilder buf = new StringBuilder();
872        int statsLen = stats.length;
873        for ( int i = 0; i < statsLen; i++ )
874        {
875            buf.append( "\n---------------------------\n" );
876            buf.append( stats[i] );
877        }
878        return buf.toString();
879    }
880
881    /**
882     * This returns data gathered for all regions and all the auxiliaries they currently uses.
883     * <p>
884     * @return ICacheStats[]
885     */
886    public ICacheStats[] getStatistics()
887    {
888        ArrayList<ICacheStats> cacheStats = new ArrayList<ICacheStats>();
889        for (ICache<?, ?> c :  caches.values())
890        {
891            CompositeCache<?, ?> cache = (CompositeCache<?, ?>) c;
892            if ( cache != null )
893            {
894                cacheStats.add( cache.getStatistics() );
895            }
896        }
897        ICacheStats[] stats = cacheStats.toArray( new CacheStats[0] );
898        return stats;
899    }
900
901    /**
902     * Perhaps the composite cache itself should be the observable object. It doesn't make much of a
903     * difference. There are some problems with region by region shutdown. Some auxiliaries are
904     * global. They will need to track when every region has shutdown before doing things like
905     * closing the socket with a lateral.
906     * <p>
907     * @param observer
908     */
909    @Override
910    public void registerShutdownObserver( IShutdownObserver observer )
911    {
912        if (!shutdownObservers.contains(observer))
913        {
914                shutdownObservers.push( observer );
915        }
916        else
917        {
918                log.warn("Shutdown observer added twice " + observer);
919        }
920    }
921
922    /**
923     * @param observer
924     */
925    @Override
926    public void deregisterShutdownObserver( IShutdownObserver observer )
927    {
928        shutdownObservers.remove( observer );
929    }
930
931    /**
932     * This is exposed so other manager can get access to the props.
933     * <p>
934     * @return the configurationProperties
935     */
936    @Override
937    public Properties getConfigurationProperties()
938    {
939        return configurationProperties;
940    }
941
942    /**
943     * @return the isInitialized
944     */
945    public boolean isInitialized()
946    {
947        return isInitialized;
948    }
949
950    /**
951     * @return the isConfigured
952     */
953    public boolean isConfigured()
954    {
955        return isConfigured;
956    }
957
958    public void setJmxName(final String name)
959    {
960        if (isJMXRegistered)
961        {
962            throw new IllegalStateException("Too late, MBean registration is done");
963        }
964        jmxName = name;
965    }
966
967    /**
968     * Called on shutdown. This gives use a chance to store the keys and to optimize even if the
969     * cache manager's shutdown method was not called manually.
970     */
971    class ShutdownHook
972        extends Thread
973    {
974        /**
975         * This will persist the keys on shutdown.
976         * <p>
977         * @see java.lang.Thread#run()
978         */
979        @SuppressWarnings("synthetic-access")
980        @Override
981        public void run()
982        {
983            if ( isInitialized() )
984            {
985                log.info( "Shutdown hook activated.  Shutdown was not called.  Shutting down JCS." );
986                shutDown();
987            }
988        }
989    }
990}