001package org.apache.commons.jcs3.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.List;
027import java.util.Objects;
028import java.util.Properties;
029import java.util.Set;
030import java.util.concurrent.ConcurrentHashMap;
031import java.util.concurrent.ConcurrentMap;
032import java.util.concurrent.Executors;
033import java.util.concurrent.LinkedBlockingDeque;
034import java.util.concurrent.ScheduledExecutorService;
035import java.util.concurrent.atomic.AtomicInteger;
036import java.util.stream.Collectors;
037import java.util.stream.Stream;
038
039import javax.management.MBeanServer;
040import javax.management.ObjectName;
041
042import org.apache.commons.jcs3.access.exception.CacheException;
043import org.apache.commons.jcs3.admin.JCSAdminBean;
044import org.apache.commons.jcs3.auxiliary.AuxiliaryCache;
045import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
046import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheFactory;
047import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheConstants;
048import org.apache.commons.jcs3.engine.CompositeCacheAttributes;
049import org.apache.commons.jcs3.engine.ElementAttributes;
050import org.apache.commons.jcs3.engine.behavior.ICache;
051import org.apache.commons.jcs3.engine.behavior.ICacheType.CacheType;
052import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
053import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
054import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
055import org.apache.commons.jcs3.engine.behavior.IProvideScheduler;
056import org.apache.commons.jcs3.engine.behavior.IShutdownObserver;
057import org.apache.commons.jcs3.engine.control.event.ElementEventQueue;
058import org.apache.commons.jcs3.engine.control.event.behavior.IElementEventQueue;
059import org.apache.commons.jcs3.engine.stats.CacheStats;
060import org.apache.commons.jcs3.engine.stats.behavior.ICacheStats;
061import org.apache.commons.jcs3.log.Log;
062import org.apache.commons.jcs3.log.LogManager;
063import org.apache.commons.jcs3.utils.config.OptionConverter;
064import org.apache.commons.jcs3.utils.threadpool.DaemonThreadFactory;
065import org.apache.commons.jcs3.utils.threadpool.ThreadPoolManager;
066import org.apache.commons.jcs3.utils.timing.ElapsedTimer;
067
068/**
069 * Manages a composite cache. This provides access to caches and is the primary way to shutdown the
070 * caching system as a whole.
071 * <p>
072 * The composite cache manager is responsible for creating / configuring cache regions. It serves as
073 * a factory for the ComositeCache class. The CompositeCache is the core of JCS, the hub for various
074 * auxiliaries.
075 */
076public class CompositeCacheManager
077    implements IRemoteCacheConstants, ICompositeCacheManager, IProvideScheduler
078{
079    /** The logger */
080    private static final Log log = LogManager.getLog( CompositeCacheManager.class );
081
082    /** JMX object name */
083    public static final String JMX_OBJECT_NAME = "org.apache.commons.jcs3:type=JCSAdminBean";
084
085    /** This is the name of the config file that we will look for by default. */
086    private static final String DEFAULT_CONFIG = "/cache.ccf";
087
088    /** default region prefix */
089    private static final String DEFAULT_REGION = "jcs.default";
090
091    /** Should we use system property substitutions. */
092    private static final boolean DEFAULT_USE_SYSTEM_PROPERTIES = true;
093
094    /** Once configured, you can force a reconfiguration of sorts. */
095    private static final boolean DEFAULT_FORCE_RECONFIGURATION = false;
096
097    /** Caches managed by this cache manager */
098    private final ConcurrentMap<String, ICache<?, ?>> caches = new ConcurrentHashMap<>();
099
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}