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
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
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
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
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