View Javadoc
1   package org.apache.commons.jcs3.engine.control;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.lang.management.ManagementFactory;
25  import java.security.AccessControlException;
26  import java.util.List;
27  import java.util.Objects;
28  import java.util.Properties;
29  import java.util.Set;
30  import java.util.concurrent.ConcurrentHashMap;
31  import java.util.concurrent.ConcurrentMap;
32  import java.util.concurrent.Executors;
33  import java.util.concurrent.LinkedBlockingDeque;
34  import java.util.concurrent.ScheduledExecutorService;
35  import java.util.concurrent.atomic.AtomicInteger;
36  import java.util.stream.Collectors;
37  import java.util.stream.Stream;
38  
39  import javax.management.MBeanServer;
40  import javax.management.ObjectName;
41  
42  import org.apache.commons.jcs3.access.exception.CacheException;
43  import org.apache.commons.jcs3.admin.JCSAdminBean;
44  import org.apache.commons.jcs3.auxiliary.AuxiliaryCache;
45  import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
46  import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheFactory;
47  import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheConstants;
48  import org.apache.commons.jcs3.engine.CompositeCacheAttributes;
49  import org.apache.commons.jcs3.engine.ElementAttributes;
50  import org.apache.commons.jcs3.engine.behavior.ICache;
51  import org.apache.commons.jcs3.engine.behavior.ICacheType.CacheType;
52  import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
53  import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
54  import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
55  import org.apache.commons.jcs3.engine.behavior.IProvideScheduler;
56  import org.apache.commons.jcs3.engine.behavior.IShutdownObserver;
57  import org.apache.commons.jcs3.engine.control.event.ElementEventQueue;
58  import org.apache.commons.jcs3.engine.control.event.behavior.IElementEventQueue;
59  import org.apache.commons.jcs3.engine.stats.CacheStats;
60  import org.apache.commons.jcs3.engine.stats.behavior.ICacheStats;
61  import org.apache.commons.jcs3.log.Log;
62  import org.apache.commons.jcs3.log.LogManager;
63  import org.apache.commons.jcs3.utils.config.OptionConverter;
64  import org.apache.commons.jcs3.utils.threadpool.DaemonThreadFactory;
65  import org.apache.commons.jcs3.utils.threadpool.ThreadPoolManager;
66  import org.apache.commons.jcs3.utils.timing.ElapsedTimer;
67  
68  /**
69   * Manages a composite cache. This provides access to caches and is the primary way to shutdown the
70   * caching system as a whole.
71   * <p>
72   * The composite cache manager is responsible for creating / configuring cache regions. It serves as
73   * a factory for the ComositeCache class. The CompositeCache is the core of JCS, the hub for various
74   * auxiliaries.
75   */
76  public class CompositeCacheManager
77      implements IRemoteCacheConstants, ICompositeCacheManager, IProvideScheduler
78  {
79      /** The logger */
80      private static final Log log = LogManager.getLog( CompositeCacheManager.class );
81  
82      /** JMX object name */
83      public static final String JMX_OBJECT_NAME = "org.apache.commons.jcs3:type=JCSAdminBean";
84  
85      /** This is the name of the config file that we will look for by default. */
86      private static final String DEFAULT_CONFIG = "/cache.ccf";
87  
88      /** default region prefix */
89      private static final String DEFAULT_REGION = "jcs.default";
90  
91      /** Should we use system property substitutions. */
92      private static final boolean DEFAULT_USE_SYSTEM_PROPERTIES = true;
93  
94      /** Once configured, you can force a reconfiguration of sorts. */
95      private static final boolean DEFAULT_FORCE_RECONFIGURATION = false;
96  
97      /** Caches managed by this cache manager */
98      private final ConcurrentMap<String, ICache<?, ?>> caches = new ConcurrentHashMap<>();
99  
100     /** Number of clients accessing this cache manager */
101     private final AtomicInteger clients = new AtomicInteger(0);
102 
103     /** Default cache attributes for this cache manager */
104     private ICompositeCacheAttributes defaultCacheAttr = new CompositeCacheAttributes();
105 
106     /** Default element attributes for this cache manager */
107     private IElementAttributes defaultElementAttr = new ElementAttributes();
108 
109     /** Used to keep track of configured auxiliaries */
110     private final ConcurrentMap<String, AuxiliaryCacheFactory> auxiliaryFactoryRegistry =
111         new ConcurrentHashMap<>( );
112 
113     /** Used to keep track of attributes for auxiliaries. */
114     private final ConcurrentMap<String, AuxiliaryCacheAttributes> auxiliaryAttributeRegistry =
115         new ConcurrentHashMap<>( );
116 
117     /** Used to keep track of configured auxiliaries */
118     private final ConcurrentMap<String, AuxiliaryCache<?, ?>> auxiliaryCaches =
119         new ConcurrentHashMap<>( );
120 
121     /** Properties with which this manager was configured. This is exposed for other managers. */
122     private Properties configurationProperties;
123 
124     /** The default auxiliary caches to be used if not preconfigured */
125     private String defaultAuxValues;
126 
127     /** The Singleton Instance */
128     private static CompositeCacheManager instance;
129 
130     /** Stack for those waiting for notification of a shutdown. */
131     private final LinkedBlockingDeque<IShutdownObserver> shutdownObservers = new LinkedBlockingDeque<>();
132 
133     /** The central background scheduler. */
134     private ScheduledExecutorService scheduledExecutor;
135 
136     /** The central event queue. */
137     private IElementEventQueue elementEventQueue;
138 
139     /** Shutdown hook thread instance */
140     private Thread shutdownHook;
141 
142     /** Indicates whether the instance has been initialized. */
143     private boolean isInitialized;
144 
145     /** Indicates whether configure has been called. */
146     private boolean isConfigured;
147 
148     /** Indicates whether JMX bean has been registered. */
149     private boolean isJMXRegistered;
150 
151     private String jmxName = JMX_OBJECT_NAME;
152 
153     /**
154      * Gets the CacheHub instance. For backward compatibility, if this creates the instance it will
155      * attempt to configure it with the default configuration. If you want to configure from your
156      * own source, use {@link #getUnconfiguredInstance}and then call {@link #configure}
157      * <p>
158      * @return CompositeCacheManager
159      * @throws CacheException if the configuration cannot be loaded
160      */
161     public static synchronized CompositeCacheManager getInstance() throws CacheException
162     {
163         return getInstance( DEFAULT_CONFIG );
164     }
165 
166     /**
167      * Initializes the cache manager using the props file for the given name.
168      * <p>
169      * @param propsFilename
170      * @return CompositeCacheManager configured from the give propsFileName
171      * @throws CacheException if the configuration cannot be loaded
172      */
173     public static synchronized CompositeCacheManager getInstance( final String propsFilename ) throws CacheException
174     {
175         if ( instance == null )
176         {
177             log.info( "Instance is null, creating with config [{0}]", propsFilename );
178             instance = createInstance();
179         }
180 
181         if (!instance.isInitialized())
182         {
183             instance.initialize();
184         }
185 
186         if (!instance.isConfigured())
187         {
188             instance.configure( propsFilename );
189         }
190 
191         instance.clients.incrementAndGet();
192 
193         return instance;
194     }
195 
196     /**
197      * Get a CacheHub instance which is not configured. If an instance already exists, it will be
198      * returned.
199      *<p>
200      * @return CompositeCacheManager
201      */
202     public static synchronized CompositeCacheManager getUnconfiguredInstance()
203     {
204         if ( instance == null )
205         {
206             log.info( "Instance is null, returning unconfigured instance" );
207             instance = createInstance();
208         }
209 
210         if (!instance.isInitialized())
211         {
212             instance.initialize();
213         }
214 
215         instance.clients.incrementAndGet();
216 
217         return instance;
218     }
219 
220     /**
221      * Simple factory method, must override in subclasses so getInstance creates / returns the
222      * correct object.
223      * <p>
224      * @return CompositeCacheManager
225      */
226     protected static CompositeCacheManager createInstance()
227     {
228         return new CompositeCacheManager();
229     }
230 
231     /**
232      * Default constructor
233      */
234     protected CompositeCacheManager()
235     {
236         // empty
237     }
238 
239     /** Creates a shutdown hook and starts the scheduler service */
240     protected synchronized void initialize()
241     {
242         if (!isInitialized)
243         {
244             this.shutdownHook = new Thread(() -> {
245                 if ( isInitialized() )
246                 {
247                     log.info("Shutdown hook activated. Shutdown was not called. Shutting down JCS.");
248                     shutDown();
249                 }
250             });
251             try
252             {
253                 Runtime.getRuntime().addShutdownHook( shutdownHook );
254             }
255             catch ( final 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                 final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
267                 final JCSAdminBean adminBean = new JCSAdminBean(this);
268                 try
269                 {
270                     final ObjectName jmxObjectName = new ObjectName(jmxName);
271                     mbs.registerMBean(adminBean, jmxObjectName);
272                     isJMXRegistered = true;
273                 }
274                 catch (final Exception e)
275                 {
276                     log.warn( "Could not register JMX bean.", e );
277                 }
278             }
279 
280             isInitialized = true;
281         }
282     }
283 
284     /**
285      * Get the element event queue
286      *
287      * @return the elementEventQueue
288      */
289     public IElementEventQueue getElementEventQueue()
290     {
291         return elementEventQueue;
292     }
293 
294     /**
295      * Get the scheduler service
296      *
297      * @return the scheduledExecutor
298      */
299     @Override
300     public ScheduledExecutorService getScheduledExecutorService()
301     {
302         return scheduledExecutor;
303     }
304 
305     /**
306      * Configure with default properties file
307      * @throws CacheException if the configuration cannot be loaded
308      */
309     public void configure() throws CacheException
310     {
311         configure( DEFAULT_CONFIG );
312     }
313 
314     /**
315      * Configure from specific properties file.
316      * <p>
317      * @param propFile Path <u>within classpath </u> to load configuration from
318      * @throws CacheException if the configuration cannot be loaded
319      */
320     public void configure( final String propFile ) throws CacheException
321     {
322         log.info( "Creating cache manager from config file: {0}", propFile );
323 
324         final Properties props = new Properties();
325 
326         try (InputStream is = getClass().getResourceAsStream( propFile ))
327         {
328             props.load( is );
329             log.debug( "File [{0}] contained {1} properties", () -> propFile, props::size);
330         }
331         catch ( final IOException ex )
332         {
333             throw new CacheException("Failed to load properties for name [" + propFile + "]", ex);
334         }
335 
336         configure( props );
337     }
338 
339     /**
340      * Configure from properties object.
341      * <p>
342      * This method will call configure, instructing it to use system properties as a default.
343      * @param props
344      */
345     public void configure( final Properties props )
346     {
347         configure( props, DEFAULT_USE_SYSTEM_PROPERTIES );
348     }
349 
350     /**
351      * Configure from properties object, overriding with values from the system properties if
352      * instructed.
353      * <p>
354      * You can override a specific value by passing in a system property:
355      * <p>
356      * For example, you could override this value in the cache.ccf file by starting up your program
357      * with the argument: -Djcs.auxiliary.LTCP.attributes.TcpListenerPort=1111
358      * <p>
359      * @param props
360      * @param useSystemProperties -- if true, values starting with jcs will be put into the props
361      *            file prior to configuring the cache.
362      */
363     public void configure( final Properties props, final boolean useSystemProperties )
364     {
365         configure( props, useSystemProperties, DEFAULT_FORCE_RECONFIGURATION );
366     }
367 
368     /**
369      * Configure from properties object, overriding with values from the system properties if
370      * instructed.
371      * <p>
372      * You can override a specific value by passing in a system property:
373      * <p>
374      * For example, you could override this value in the cache.ccf file by starting up your program
375      * with the argument: -Djcs.auxiliary.LTCP.attributes.TcpListenerPort=1111
376      * <p>
377      * @param props
378      * @param useSystemProperties -- if true, values starting with jcs will be put into the props
379      *            file prior to configuring the cache.
380      * @param forceReconfiguration - if the manager is already configured, we will try again. This
381      *            may not work properly.
382      */
383     public synchronized void configure( final Properties props, final boolean useSystemProperties, final boolean forceReconfiguration )
384     {
385         if ( props == null )
386         {
387             log.error( "No properties found. Please configure the cache correctly." );
388             return;
389         }
390 
391         if ( isConfigured )
392         {
393             if ( !forceReconfiguration )
394             {
395                 log.debug( "Configure called after the manager has been configured.  "
396                          + "Force reconfiguration is false. Doing nothing" );
397                 return;
398             }
399             log.info( "Configure called after the manager has been configured.  "
400                     + "Force reconfiguration is true. Reconfiguring as best we can." );
401         }
402         if ( useSystemProperties )
403         {
404             CompositeCacheConfigurator.overrideWithSystemProperties( props );
405         }
406         doConfigure( props );
407     }
408 
409     /**
410      * Configure the cache using the supplied properties.
411      * <p>
412      * @param properties assumed not null
413      */
414     private synchronized void doConfigure( final Properties properties )
415     {
416         // We will expose this for managers that need raw properties.
417         this.configurationProperties = properties;
418 
419         // set the props value and then configure the ThreadPoolManager
420         ThreadPoolManager.setProps( properties );
421         final ThreadPoolManager poolMgr = ThreadPoolManager.getInstance();
422         log.debug( "ThreadPoolManager = {0}", poolMgr);
423 
424         // Create event queue
425         this.elementEventQueue = new ElementEventQueue();
426 
427         // configure the cache
428         final CompositeCacheConfigurator configurator = newConfigurator();
429 
430         final ElapsedTimer timer = new ElapsedTimer();
431 
432         // set default value list
433         this.defaultAuxValues = OptionConverter.findAndSubst( CompositeCacheManager.DEFAULT_REGION,
434                 properties );
435 
436         log.info( "Setting default auxiliaries to \"{0}\"", this.defaultAuxValues );
437 
438         // set default cache attr
439         this.defaultCacheAttr = configurator.parseCompositeCacheAttributes( properties, "",
440                 new CompositeCacheAttributes(), DEFAULT_REGION );
441 
442         log.info( "setting defaultCompositeCacheAttributes to {0}", this.defaultCacheAttr );
443 
444         // set default element attr
445         this.defaultElementAttr = configurator.parseElementAttributes( properties, "",
446                 new ElementAttributes(), DEFAULT_REGION );
447 
448         log.info( "setting defaultElementAttributes to {0}", this.defaultElementAttr );
449 
450         // set up system caches to be used by non system caches
451         // need to make sure there is no circularity of reference
452         configurator.parseSystemRegions( properties, this );
453 
454         // setup preconfigured caches
455         configurator.parseRegions( properties, this );
456 
457         log.info( "Finished configuration in {0} ms.", timer::getElapsedTime);
458 
459         isConfigured = true;
460     }
461 
462     /**
463      * Gets the defaultCacheAttributes attribute of the CacheHub object
464      * <p>
465      * @return The defaultCacheAttributes value
466      */
467     public ICompositeCacheAttributes getDefaultCacheAttributes()
468     {
469         return this.defaultCacheAttr.clone();
470     }
471 
472     /**
473      * Gets the defaultElementAttributes attribute of the CacheHub object
474      * <p>
475      * @return The defaultElementAttributes value
476      */
477     public IElementAttributes getDefaultElementAttributes()
478     {
479         return this.defaultElementAttr.clone();
480     }
481 
482     /**
483      * Gets the cache attribute of the CacheHub object
484      * <p>
485      * @param cacheName
486      * @return CompositeCache -- the cache region controller
487      */
488     @Override
489     public <K, V> CompositeCache<K, V> getCache( final String cacheName )
490     {
491         return getCache( cacheName, getDefaultCacheAttributes() );
492     }
493 
494     /**
495      * Gets the cache attribute of the CacheHub object
496      * <p>
497      * @param cacheName
498      * @param cattr
499      * @return CompositeCache
500      */
501     public <K, V> CompositeCache<K, V> getCache( final String cacheName, final ICompositeCacheAttributes cattr )
502     {
503         cattr.setCacheName( cacheName );
504         return getCache( cattr, getDefaultElementAttributes() );
505     }
506 
507     /**
508      * Gets the cache attribute of the CacheHub object
509      * <p>
510      * @param cacheName
511      * @param cattr
512      * @param attr
513      * @return CompositeCache
514      */
515     public <K, V> CompositeCache<K, V>  getCache( final String cacheName, final ICompositeCacheAttributes cattr, final IElementAttributes attr )
516     {
517         cattr.setCacheName( cacheName );
518         return getCache( cattr, attr );
519     }
520 
521     /**
522      * Gets the cache attribute of the CacheHub object
523      * <p>
524      * @param cattr
525      * @return CompositeCache
526      */
527     public <K, V> CompositeCache<K, V>  getCache( final ICompositeCacheAttributes cattr )
528     {
529         return getCache( cattr, getDefaultElementAttributes() );
530     }
531 
532     /**
533      * If the cache has already been created, then the CacheAttributes and the element Attributes
534      * will be ignored. Currently there is no overriding the CacheAttributes once it is set up. You
535      * can change the default ElementAttributes for a region later.
536      * <p>
537      * Overriding the default elemental attributes will require changing the way the attributes are
538      * assigned to elements. Get cache creates a cache with defaults if none are specified. We might
539      * want to create separate method for creating/getting. . .
540      * <p>
541      * @param cattr
542      * @param attr
543      * @return CompositeCache
544      */
545     @SuppressWarnings("unchecked") // Need to cast because of common map for all caches
546     public <K, V> CompositeCache<K, V>  getCache( final ICompositeCacheAttributes cattr, final IElementAttributes attr )
547     {
548         log.debug( "attr = {0}", attr );
549 
550         return (CompositeCache<K, V>) caches.computeIfAbsent(cattr.getCacheName(),
551                 cacheName -> {
552             final CompositeCacheConfigurator configurator = newConfigurator();
553             return configurator.parseRegion( this.getConfigurationProperties(), this, cacheName,
554                                               this.defaultAuxValues, cattr );
555         });
556     }
557 
558     protected CompositeCacheConfigurator newConfigurator() {
559         return new CompositeCacheConfigurator();
560     }
561 
562     /**
563      * @param name
564      */
565     public void freeCache( final String name )
566     {
567         freeCache( name, false );
568     }
569 
570     /**
571      * @param name
572      * @param fromRemote
573      */
574     public void freeCache( final String name, final boolean fromRemote )
575     {
576         final CompositeCache<?, ?> cache = (CompositeCache<?, ?>) caches.remove( name );
577 
578         if ( cache != null )
579         {
580             cache.dispose( fromRemote );
581         }
582     }
583 
584     /**
585      * Calls freeCache on all regions
586      */
587     public synchronized void shutDown()
588     {
589         // shutdown element event queue
590         if (this.elementEventQueue != null)
591         {
592             this.elementEventQueue.dispose();
593         }
594 
595         // notify any observers
596         IShutdownObserver observer = null;
597         while ((observer = shutdownObservers.poll()) != null)
598         {
599             observer.shutdown();
600         }
601 
602         // Unregister JMX bean
603         if (isJMXRegistered)
604         {
605             final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
606             try
607             {
608                 final ObjectName jmxObjectName = new ObjectName(jmxName);
609                 mbs.unregisterMBean(jmxObjectName);
610             }
611             catch (final Exception e)
612             {
613                 log.warn( "Could not unregister JMX bean.", e );
614             }
615 
616             isJMXRegistered = false;
617         }
618 
619         // do the traditional shutdown of the regions.
620         getCacheNames().forEach(this::freeCache);
621 
622         // shut down auxiliaries
623         for (final String key : auxiliaryCaches.keySet())
624         {
625             try
626             {
627                 freeAuxiliaryCache(key);
628             }
629             catch (final IOException e)
630             {
631                 log.warn("Auxiliary cache {0} failed to shut down", key, e);
632             }
633         }
634 
635         // shut down factories
636         auxiliaryFactoryRegistry.values().forEach(AuxiliaryCacheFactory::dispose);
637 
638         auxiliaryAttributeRegistry.clear();
639         auxiliaryFactoryRegistry.clear();
640 
641         // shutdown all scheduled jobs
642         this.scheduledExecutor.shutdownNow();
643 
644         // shutdown all thread pools
645         ThreadPoolManager.dispose();
646 
647         if (shutdownHook != null)
648         {
649             try
650             {
651                 Runtime.getRuntime().removeShutdownHook(shutdownHook);
652             }
653             catch (final IllegalStateException e)
654             {
655                 // May fail if the JVM is already shutting down
656             }
657 
658             this.shutdownHook = null;
659         }
660 
661         isConfigured = false;
662         isInitialized = false;
663     }
664 
665     /** */
666     public void release()
667     {
668         release( false );
669     }
670 
671     /**
672      * @param fromRemote
673      */
674     private void release( final boolean fromRemote )
675     {
676         synchronized ( CompositeCacheManager.class )
677         {
678             // Wait until called by the last client
679             if ( clients.decrementAndGet() > 0 )
680             {
681                 log.debug( "Release called, but {0} remain", clients);
682                 return;
683             }
684 
685             log.debug( "Last client called release. There are {0} caches which will be disposed",
686                     caches::size);
687 
688             caches.values().stream()
689                 .filter(Objects::nonNull)
690                 .forEach(cache -> ((CompositeCache<?, ?>)cache).dispose( fromRemote ));
691         }
692     }
693 
694     /**
695      * Returns a list of the current cache names.
696      * @return Set<String>
697      */
698     public Set<String> getCacheNames()
699     {
700         return caches.keySet();
701     }
702 
703     /**
704      * @return ICacheType.CACHE_HUB
705      */
706     public CacheType getCacheType()
707     {
708         return CacheType.CACHE_HUB;
709     }
710 
711     /**
712      * @param auxFac
713      */
714     public void registryFacPut( final AuxiliaryCacheFactory auxFac )
715     {
716         auxiliaryFactoryRegistry.put( auxFac.getName(), auxFac );
717     }
718 
719     /**
720      * @param name
721      * @return AuxiliaryCacheFactory
722      */
723     public AuxiliaryCacheFactory registryFacGet( final String name )
724     {
725         return auxiliaryFactoryRegistry.get( name );
726     }
727 
728     /**
729      * @param auxAttr
730      */
731     public void registryAttrPut( final AuxiliaryCacheAttributes auxAttr )
732     {
733         auxiliaryAttributeRegistry.put( auxAttr.getName(), auxAttr );
734     }
735 
736     /**
737      * @param name
738      * @return AuxiliaryCacheAttributes
739      */
740     public AuxiliaryCacheAttributes registryAttrGet( final String name )
741     {
742         return auxiliaryAttributeRegistry.get( name );
743     }
744 
745     /**
746      * Add a cache to the map of registered caches
747      *
748      * @param cacheName the region name
749      * @param cache the cache instance
750      */
751     public void addCache(final String cacheName, final ICache<?, ?> cache)
752     {
753         caches.put(cacheName, cache);
754     }
755 
756     /**
757      * Add a cache to the map of registered auxiliary caches
758      *
759      * @param auxName the auxiliary name
760      * @param cacheName the region name
761      * @param cache the cache instance
762      */
763     public void addAuxiliaryCache(final String auxName, final String cacheName, final AuxiliaryCache<?, ?> cache)
764     {
765         final String key = String.format("aux.%s.region.%s", auxName, cacheName);
766         auxiliaryCaches.put(key, cache);
767     }
768 
769     /**
770      * Get a cache from the map of registered auxiliary caches
771      *
772      * @param auxName the auxiliary name
773      * @param cacheName the region name
774      *
775      * @return the cache instance
776      */
777     @Override
778     @SuppressWarnings("unchecked") // because of common map for all auxiliary caches
779     public <K, V> AuxiliaryCache<K, V> getAuxiliaryCache(final String auxName, final String cacheName)
780     {
781         final String key = String.format("aux.%s.region.%s", auxName, cacheName);
782         return (AuxiliaryCache<K, V>) auxiliaryCaches.get(key);
783     }
784 
785     /**
786      * Dispose a cache and remove it from the map of registered auxiliary caches
787      *
788      * @param auxName the auxiliary name
789      * @param cacheName the region name
790      * @throws IOException if disposing of the cache fails
791      */
792     public void freeAuxiliaryCache(final String auxName, final String cacheName) throws IOException
793     {
794         final String key = String.format("aux.%s.region.%s", auxName, cacheName);
795         freeAuxiliaryCache(key);
796     }
797 
798     /**
799      * Dispose a cache and remove it from the map of registered auxiliary caches
800      *
801      * @param key the key into the map of auxiliaries
802      * @throws IOException if disposing of the cache fails
803      */
804     public void freeAuxiliaryCache(final String key) throws IOException
805     {
806         final AuxiliaryCache<?, ?> aux = auxiliaryCaches.remove( key );
807 
808         if ( aux != null )
809         {
810             aux.dispose();
811         }
812     }
813 
814     /**
815      * Gets stats for debugging. This calls gets statistics and then puts all the results in a
816      * string. This returns data for all regions.
817      * <p>
818      * @return String
819      */
820     @Override
821     public String getStats()
822     {
823         final ICacheStats[] stats = getStatistics();
824         if ( stats == null )
825         {
826             return "NONE";
827         }
828 
829         // force the array elements into a string.
830         final StringBuilder buf = new StringBuilder();
831         Stream.of(stats).forEach(stat -> {
832             buf.append( "\n---------------------------\n" );
833             buf.append( stat );
834         });
835         return buf.toString();
836     }
837 
838     /**
839      * This returns data gathered for all regions and all the auxiliaries they currently uses.
840      * <p>
841      * @return ICacheStats[]
842      */
843     public ICacheStats[] getStatistics()
844     {
845         final List<ICacheStats> cacheStats = caches.values().stream()
846             .filter(Objects::nonNull)
847             .map(cache -> ((CompositeCache<?, ?>)cache).getStatistics() )
848             .collect(Collectors.toList());
849 
850         return cacheStats.toArray( new CacheStats[0] );
851     }
852 
853     /**
854      * Perhaps the composite cache itself should be the observable object. It doesn't make much of a
855      * difference. There are some problems with region by region shutdown. Some auxiliaries are
856      * global. They will need to track when every region has shutdown before doing things like
857      * closing the socket with a lateral.
858      * <p>
859      * @param observer
860      */
861     @Override
862     public void registerShutdownObserver( final IShutdownObserver observer )
863     {
864     	if (!shutdownObservers.contains(observer))
865     	{
866     		shutdownObservers.push( observer );
867     	}
868     	else
869     	{
870     		log.warn("Shutdown observer added twice {0}", observer);
871     	}
872     }
873 
874     /**
875      * @param observer
876      */
877     @Override
878     public void deregisterShutdownObserver( final IShutdownObserver observer )
879     {
880         shutdownObservers.remove( observer );
881     }
882 
883     /**
884      * This is exposed so other manager can get access to the props.
885      * <p>
886      * @return the configurationProperties
887      */
888     @Override
889     public Properties getConfigurationProperties()
890     {
891         return configurationProperties;
892     }
893 
894     /**
895      * @return the isInitialized
896      */
897     public boolean isInitialized()
898     {
899         return isInitialized;
900     }
901 
902     /**
903      * @return the isConfigured
904      */
905     public boolean isConfigured()
906     {
907         return isConfigured;
908     }
909 
910     public void setJmxName(final String name)
911     {
912         if (isJMXRegistered)
913         {
914             throw new IllegalStateException("Too late, MBean registration is done");
915         }
916         jmxName = name;
917     }
918 }