View Javadoc

1   /**
2    *  Copyright 2003-2006 Greg Luck
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  
17  
18  package net.sf.ehcache;
19  
20  import net.sf.ehcache.config.ConfigurationHelper;
21  import net.sf.ehcache.config.ConfigurationFactory;
22  import net.sf.ehcache.config.Configuration;
23  import net.sf.ehcache.distribution.CacheManagerPeerListener;
24  import net.sf.ehcache.distribution.CacheManagerPeerProvider;
25  import net.sf.ehcache.event.CacheManagerEventListener;
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  
29  import java.io.File;
30  import java.io.InputStream;
31  import java.net.URL;
32  import java.util.Collections;
33  import java.util.HashSet;
34  import java.util.Iterator;
35  import java.util.Set;
36  import java.util.HashMap;
37  import java.util.Map;
38  import java.util.Collection;
39  
40  /**
41   * A container for {@link Cache}s that maintain all aspects of their lifecycle.
42   * <p/>
43   * CacheManager is meant to have one singleton per virtual machine. Its creational methods are implemented so as to
44   * make it a singleton. The design reasons for one CacheManager per VM are:
45   * <ol>
46   * <li>The CacheManager will by default look for a resource named ehcache.xml, or failing that ehcache-failsafe.xml
47   * <li>Persistent stores write files to a directory
48   * <li>Event listeners are given cache names as arguments. They are assured the cache is referenceable through a single
49   * CacheManager.
50   * </ol>
51   *
52   * @author Greg Luck
53   * @version $Id: CacheManager.java 52 2006-04-24 14:50:03Z gregluck $
54   */
55  public final class CacheManager {
56  
57      private static final Log LOG = LogFactory.getLog(CacheManager.class.getName());
58  
59      /**
60       * Keeps track of the disk store paths of all CacheManagers.
61       * Can be checked before letting a new CacheManager start up.
62       */
63      private static final Set ALL_CACHE_MANAGER_DISK_STORE_PATHS = Collections.synchronizedSet(new HashSet());
64  
65      /**
66       * The Singleton Instance.
67       */
68      private static CacheManager singleton;
69  
70      /**
71       * Caches managed by this manager.
72       */
73      private final Map caches = new HashMap();
74  
75      /**
76       * Default cache cache.
77       */
78      private Cache defaultCache;
79  
80      /**
81       * The path for the directory in which disk caches are created.
82       */
83      private String diskStorePath;
84  
85      /**
86       * The CacheManagerEventListener which will be notified of significant events.
87       */
88      private CacheManagerEventListener cacheManagerEventListener;
89  
90      private Status status;
91  
92      private CacheManagerPeerProvider cacheManagerPeerProvider;
93      private CacheManagerPeerListener cacheManagerPeerListener;
94  
95      /**
96       * An constructor for CacheManager, which takes a configuration object, rather than one created by parsing
97       * an ehcache.xml file. This constructor gives complete control over the creation of the CacheManager.
98       * <p/>
99       * Care should be taken to ensure that, if multiple CacheManages are created, they do now overwrite each others
100      * disk store files, as would happend if two were created which used the same diskStore path.
101      * <p/>
102      * This method does not act as a singleton. Callers must maintain their own reference to it.
103      * <p/>
104      * Note that if one of the {@link #create()}  methods are called, a new singleton instance will be created,
105      * separate from any instances created in this method.
106      *
107      * @param configuration
108      * @throws CacheException
109      */
110     public CacheManager(Configuration configuration) throws CacheException {
111         status = Status.STATUS_UNINITIALISED;
112         init(configuration, null, null, null);
113     }
114 
115     /**
116      * An ordinary constructor for CacheManager.
117      * This method does not act as a singleton. Callers must maintain a reference to it.
118      * Note that if one of the {@link #create()}  methods are called, a new singleton will be created,
119      * separate from any instances created in this method.
120      *
121      * @param configurationFileName an xml configuration file available through a file name. The configuration
122      *                              {@link File} is created
123      *                              using new <code>File(configurationFileName)</code>
124      * @throws CacheException
125      * @see #create(String)
126      */
127     public CacheManager(String configurationFileName) throws CacheException {
128         status = Status.STATUS_UNINITIALISED;
129         init(null, configurationFileName, null, null);
130     }
131 
132     /**
133      * An ordinary constructor for CacheManager.
134      * This method does not act as a singleton. Callers must maintain a reference to it.
135      * Note that if one of the {@link #create()}  methods are called, a new singleton will be created,
136      * separate from any instances created in this method.
137      * <p/>
138      * This method can be used to specify a configuration resource in the classpath other
139      * than the default of \"/ehcache.xml\":
140      * <pre>
141      * URL url = this.getClass().getResource("/ehcache-2.xml");
142      * </pre>
143      * Note that {@link Class#getResource} will look for resources in the same package unless a leading "/"
144      * is used, in which case it will look in the root of the classpath.
145      * <p/>
146      * You can also load a resource using other class loaders. e.g. {@link Thread#getContextClassLoader()}
147      *
148      * @param configurationURL an xml configuration available through a URL.
149      * @throws CacheException
150      * @see #create(java.net.URL)
151      * @since 1.2
152      */
153     public CacheManager(URL configurationURL) throws CacheException {
154         status = Status.STATUS_UNINITIALISED;
155         init(null, null, configurationURL, null);
156     }
157 
158     /**
159      * An ordinary constructor for CacheManager.
160      * This method does not act as a singleton. Callers must maintain a reference to it.
161      * Note that if one of the {@link #create()}  methods are called, a new singleton will be created,
162      * separate from any instances created in this method.
163      *
164      * @param configurationInputStream an xml configuration file available through an inputstream
165      * @throws CacheException
166      * @see #create(java.io.InputStream)
167      */
168     public CacheManager(InputStream configurationInputStream) throws CacheException {
169         status = Status.STATUS_UNINITIALISED;
170         init(null, null, null, configurationInputStream);
171     }
172 
173     /**
174      * Constructor.
175      * @throws CacheException
176      */
177     public CacheManager() throws CacheException {
178         //default config will be done
179         status = Status.STATUS_UNINITIALISED;
180         init(null, null, null, null);
181     }
182 
183     private void init(Configuration configuration, String configurationFileName, URL configurationURL,
184                       InputStream configurationInputStream) {
185         Configuration localConfiguration = configuration;
186         if (configuration == null) {
187             localConfiguration = parseConfiguration(configurationFileName, configurationURL, configurationInputStream);
188         } else {
189             localConfiguration.setSource("Programmatically configured.");
190         }
191 
192         ConfigurationHelper configurationHelper = new ConfigurationHelper(this, localConfiguration);
193         configure(configurationHelper);
194 
195         status = Status.STATUS_ALIVE;
196         if (cacheManagerPeerListener != null) {
197             cacheManagerPeerListener.init();
198         }
199         if (cacheManagerPeerProvider != null) {
200             cacheManagerPeerProvider.init();
201         }
202 
203     }
204 
205     /**
206      * Loads configuration, either from the supplied {@link ConfigurationHelper} or by creating a new Configuration instance
207      * from the configuration file referred to by file, inputstream or URL.
208      * <p/>
209      * Should only be called once.
210      *
211      * @param configurationFileName the file name to parse, or null
212      * @param configurationURL the URL to pass, or null
213      * @param configurationInputStream, the InputStream to parse, or null
214      * @return the loaded configuration
215      * @throws CacheException if the configuration cannot be parsed
216      */
217     private synchronized Configuration parseConfiguration(String configurationFileName, URL configurationURL,
218                                                           InputStream configurationInputStream) throws CacheException {
219         reinitialisationCheck();
220         Configuration configuration;
221         String configurationSource;
222         if (configurationFileName != null) {
223             LOG.debug("Configuring CacheManager from " + configurationFileName);
224             configuration = ConfigurationFactory.parseConfiguration(new File(configurationFileName));
225             configurationSource = "file located at " + configurationFileName;
226         } else if (configurationURL != null) {
227             configuration = ConfigurationFactory.parseConfiguration(configurationURL);
228             configurationSource = "URL of " + configurationURL;
229         } else if (configurationInputStream != null) {
230             configuration = ConfigurationFactory.parseConfiguration(configurationInputStream);
231             configurationSource = "InputStream " + configurationInputStream;
232         } else {
233             if (LOG.isDebugEnabled()) {
234                 LOG.debug("Configuring ehcache from classpath.");
235             }
236             configuration = ConfigurationFactory.parseConfiguration();
237             configurationSource = "classpath";
238         }
239         configuration.setSource(configurationSource);
240         return configuration;
241 
242     }
243 
244     private void configure(ConfigurationHelper configurationHelper) {
245 
246         diskStorePath = configurationHelper.getDiskStorePath();
247         if (!ALL_CACHE_MANAGER_DISK_STORE_PATHS.add(diskStorePath)) {
248             throw new CacheException("Cannot parseConfiguration CacheManager. Attempt to create a new instance" +
249                     " of CacheManager using the diskStorePath \"" + diskStorePath + "\" which is already used" +
250                     " by an existing CacheManager. The source of the configuration was "
251                     + configurationHelper.getConfigurationBean().getConfigurationSource() + ".");
252         }
253 
254         cacheManagerEventListener = configurationHelper.createCacheManagerEventListener();
255         cacheManagerPeerListener = configurationHelper.createCachePeerListener();
256         cacheManagerPeerProvider = configurationHelper.createCachePeerProvider();
257         defaultCache = configurationHelper.createDefaultCache();
258 
259         Set unitialisedCaches = configurationHelper.createCaches();
260         for (Iterator iterator = unitialisedCaches.iterator(); iterator.hasNext();) {
261             Cache unitialisedCache = (Cache) iterator.next();
262             addCacheNoCheck(unitialisedCache);
263         }
264     }
265 
266     private void reinitialisationCheck() throws IllegalStateException {
267         if (defaultCache != null || diskStorePath != null || caches.size() != 0
268                 || status.equals(Status.STATUS_SHUTDOWN)) {
269             throw new IllegalStateException("Attempt to reinitialise the Cache Manager");
270         }
271     }
272 
273     /**
274      * A factory method to create a singleton CacheManager with default config, or return it if it exists.
275      * <p/>
276      * The configuration will be read, {@link Cache}s created and required stores initialized.
277      * When the {@link CacheManager} is no longer required, call shutdown to free resources.
278      * @return the singleton CacheManager
279      * @throws CacheException if the CacheManager cannot be created
280      */
281     public static CacheManager create() throws CacheException {
282         synchronized (CacheManager.class) {
283             if (singleton == null) {
284                 if (LOG.isDebugEnabled()) {
285                     LOG.debug("Creating new CacheManager with default config");
286                 }
287                 singleton = new CacheManager();
288             } else {
289                 if (LOG.isDebugEnabled()) {
290                     LOG.debug("Attempting to create an existing singleton. Existing singleton returned.");
291                 }
292             }
293             return singleton;
294         }
295     }
296 
297     /**
298      * A factory method to create a singleton CacheManager with default config, or return it if it exists.
299      * <p/>
300      * This has the same effect as {@link CacheManager#create}
301      * <p/>
302      * Same as {@link #create()}
303      * @return the singleton CacheManager
304      * @throws CacheException if the CacheManager cannot be created
305      */
306     public static CacheManager getInstance() throws CacheException {
307         return CacheManager.create();
308     }
309 
310     /**
311      * A factory method to create a singleton CacheManager with a specified configuration.
312      *
313      * @param configurationFileName an xml file compliant with the ehcache.xsd schema
314      *                              <p/>
315      *                              The configuration will be read, {@link Cache}s created and required stores initialized.
316      *                              When the {@link CacheManager} is no longer required, call shutdown to free resources.
317      */
318     public static CacheManager create(String configurationFileName) throws CacheException {
319         synchronized (CacheManager.class) {
320             if (singleton == null) {
321                 if (LOG.isDebugEnabled()) {
322                     LOG.debug("Creating new CacheManager with config file: " + configurationFileName);
323                 }
324                 singleton = new CacheManager(configurationFileName);
325             }
326             return singleton;
327         }
328     }
329 
330     /**
331      * A factory method to create a singleton CacheManager from an URL.
332      * <p/>
333      * This method can be used to specify a configuration resource in the classpath other
334      * than the default of \"/ehcache.xml\":
335      * This method can be used to specify a configuration resource in the classpath other
336      * than the default of \"/ehcache.xml\":
337      * <pre>
338      * URL url = this.getClass().getResource("/ehcache-2.xml");
339      * </pre>
340      * Note that {@link Class#getResource} will look for resources in the same package unless a leading "/"
341      * is used, in which case it will look in the root of the classpath.
342      * <p/>
343      * You can also load a resource using other class loaders. e.g. {@link Thread#getContextClassLoader()}
344      *
345      * @param configurationFileURL an URL to an xml file compliant with the ehcache.xsd schema
346      *                             <p/>
347      *                             The configuration will be read, {@link Cache}s created and required stores initialized.
348      *                             When the {@link CacheManager} is no longer required, call shutdown to free resources.
349      */
350     public static CacheManager create(URL configurationFileURL) throws CacheException {
351         synchronized (CacheManager.class) {
352             if (singleton == null) {
353                 if (LOG.isDebugEnabled()) {
354                     LOG.debug("Creating new CacheManager with config URL: " + configurationFileURL);
355                 }
356                 singleton = new CacheManager(configurationFileURL);
357 
358             }
359             return singleton;
360         }
361     }
362 
363     /**
364      * A factory method to create a singleton CacheManager from a java.io.InputStream.
365      * <p/>
366      * This method makes it possible to use an inputstream for configuration.
367      * Note: it is the clients responsibility to close the inputstream.
368      * <p/>
369      *
370      * @param inputStream InputStream of xml compliant with the ehcache.xsd schema
371      *                    <p/>
372      *                    The configuration will be read, {@link Cache}s created and required stores initialized.
373      *                    When the {@link CacheManager} is no longer required, call shutdown to free resources.
374      */
375     public static CacheManager create(InputStream inputStream) throws CacheException {
376         synchronized (CacheManager.class) {
377             if (singleton == null) {
378                 if (LOG.isDebugEnabled()) {
379                     LOG.debug("Creating new CacheManager with InputStream");
380                 }
381                 singleton = new CacheManager(inputStream);
382             }
383             return singleton;
384         }
385     }
386 
387     /**
388      * Gets a Cache
389      *
390      * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
391      */
392     public synchronized Cache getCache(String name) throws IllegalStateException {
393         checkStatus();
394         return (Cache) caches.get(name);
395     }
396 
397     /**
398      * Adds a {@link Cache} based on the defaultCache with the given name.
399      * <p/>
400      * Memory and Disk stores will be configured for it and it will be added
401      * to the map of caches.
402      * <p/>
403      * Also notifies the CacheManagerEventListener after the cache was initialised and added.
404      * <p/>
405      * It will be created with the defaultCache attributes specified in ehcache.xml
406      *
407      * @param cacheName the name for the cache
408      * @throws ObjectExistsException if the cache already exists
409      * @throws CacheException        if there was an error creating the cache.
410      */
411     public synchronized void addCache(String cacheName) throws IllegalStateException,
412             ObjectExistsException, CacheException {
413         checkStatus();
414 
415         //NPE guard
416         if (cacheName == null || cacheName.length() == 0) {
417             return;
418         }
419 
420         if (caches.get(cacheName) != null) {
421             throw new ObjectExistsException("Cache " + cacheName + " already exists");
422         }
423         Cache cache = null;
424         try {
425             cache = (Cache) defaultCache.clone();
426         } catch (CloneNotSupportedException e) {
427             LOG.error("Failure adding cache. Initial cause was " + e.getMessage(), e);
428         }
429         if (cache != null) {
430             cache.setName(cacheName);
431         }
432         addCache(cache);
433     }
434 
435     /**
436      * Adds a {@link Cache} to the CacheManager.
437      * <p/>
438      * Memory and Disk stores will be configured for it and it will be added to the map of caches.
439      * Also notifies the CacheManagerEventListener after the cache was initialised and added.
440      *
441      * @param cache
442      * @throws IllegalStateException if the cache is not {@link Status#STATUS_UNINITIALISED} before this method is called.
443      * @throws ObjectExistsException if the cache already exists in the CacheManager
444      * @throws CacheException        if there was an error adding the cache to the CacheManager
445      */
446     public synchronized void addCache(Cache cache) throws IllegalStateException,
447             ObjectExistsException, CacheException {
448         checkStatus();
449         addCacheNoCheck(cache);
450     }
451 
452     private synchronized void addCacheNoCheck(Cache cache) throws IllegalStateException,
453             ObjectExistsException, CacheException {
454         if (caches.get(cache.getName()) != null) {
455             throw new ObjectExistsException("Cache " + cache.getName() + " already exists");
456         }
457         cache.initialise();
458         cache.setCacheManager(this);
459         caches.put(cache.getName(), cache);
460         if (cacheManagerEventListener != null) {
461             cacheManagerEventListener.notifyCacheAdded(cache.getName());
462         }
463     }
464 
465     /**
466      * Checks whether a cache exists.
467      * <p/>
468      *
469      * @param cacheName the cache name to check for
470      * @return true if it exists
471      * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
472      */
473     public synchronized boolean cacheExists(String cacheName) throws IllegalStateException {
474         checkStatus();
475         return (caches.get(cacheName) != null);
476     }
477 
478     /**
479      * Removes all caches using {@link #removeCache} for each cache.
480      */
481     public synchronized void removalAll() {
482         String[] cacheNames = getCacheNames();
483         for (int i = 0; i < cacheNames.length; i++) {
484             String cacheName = cacheNames[i];
485             removeCache(cacheName);
486         }
487     }
488 
489     /**
490      * Remove a cache from the CacheManager. The cache is disposed of.
491      *
492      * @param cacheName the cache name
493      * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
494      */
495     public synchronized void removeCache(String cacheName) throws IllegalStateException {
496         checkStatus();
497 
498         //NPE guard
499         if (cacheName == null || cacheName.length() == 0) {
500             return;
501         }
502 
503         Cache cache = (Cache) caches.remove(cacheName);
504         if (cache != null && cache.getStatus().equals(Status.STATUS_ALIVE)) {
505             cache.dispose();
506             if (cacheManagerEventListener != null) {
507                 cacheManagerEventListener.notifyCacheRemoved(cache.getName());
508             }
509         }
510     }
511 
512     /**
513      * Shuts down the CacheManager.
514      * <p/>
515      * If the shutdown occurs on the singleton, then the singleton is removed, so that if a singleton access method
516      * is called, a new singleton will be created.
517      */
518     public void shutdown() {
519         if (status.equals(Status.STATUS_SHUTDOWN)) {
520             if (LOG.isWarnEnabled()) {
521                 LOG.warn("CacheManager already shutdown");
522             }
523             return;
524         }
525         if (cacheManagerPeerProvider != null) {
526             cacheManagerPeerProvider.dispose();
527         }
528         if (cacheManagerPeerListener != null) {
529             cacheManagerPeerListener.dispose();
530         }
531         synchronized (CacheManager.class) {
532             ALL_CACHE_MANAGER_DISK_STORE_PATHS.remove(diskStorePath);
533 
534             Collection cacheSet = caches.values();
535             for (Iterator iterator = cacheSet.iterator(); iterator.hasNext();) {
536                 Cache cache = (Cache) iterator.next();
537                 if (cache != null) {
538                     cache.dispose();
539                 }
540             }
541             status = Status.STATUS_SHUTDOWN;
542 
543             //only delete singleton if the singleton is shutting down.
544             if (this == singleton) {
545                 singleton = null;
546             }
547         }
548     }
549 
550     /**
551      * Returns a list of the current cache names.
552      *
553      * @return an array of {@link String}s
554      * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
555      */
556     public synchronized String[] getCacheNames() throws IllegalStateException {
557         checkStatus();
558         String[] list = new String[caches.size()];
559         return (String[]) caches.keySet().toArray(list);
560     }
561 
562 
563     private void checkStatus() {
564         if (!(status.equals(Status.STATUS_ALIVE))) {
565             throw new IllegalStateException("The CacheManager is not alive.");
566         }
567     }
568 
569 
570     /**
571      * Gets the status attribute of the Cache
572      *
573      * @return The status value from the Status enum class
574      */
575     public Status getStatus() {
576         return status;
577     }
578 
579     /**
580      * Gets the <code>CacheManagerPeerProvider</code>
581      * For distributed caches, the peer provider finds other cache managers and their caches in the same cluster
582      *
583      * @return the provider, or null if one does not exist
584      */
585     public CacheManagerPeerProvider getCachePeerProvider() {
586         return cacheManagerPeerProvider;
587     }
588 
589     /**
590      * When CacheManage is configured as part of a cluster, a CacheManagerPeerListener will
591      * be registered in it. Use this to access the individual cache listeners
592      *
593      * @return the listener, or null if one does not exist
594      */
595     public CacheManagerPeerListener getCachePeerListener() {
596         return cacheManagerPeerListener;
597     }
598 
599     /**
600      * Gets the CacheManager event listener.
601      *
602      * @return null if none
603      */
604     public CacheManagerEventListener getCacheManagerEventListener() {
605         return cacheManagerEventListener;
606     }
607 
608     /**
609      * Sets the CacheManager event listener. Any existing listener is disposed and removed first.
610      *
611      * @param cacheManagerEventListener the listener to set.
612      */
613     public void setCacheManagerEventListener(CacheManagerEventListener cacheManagerEventListener) {
614         this.cacheManagerEventListener = cacheManagerEventListener;
615     }
616 
617 
618 
619 
620 }
621