001: /*
002: * Copyright (c) 2002-2003 by OpenSymphony
003: * All rights reserved.
004: */
005: package com.opensymphony.oscache.base;
006:
007: import com.opensymphony.oscache.base.algorithm.AbstractConcurrentReadCache;
008: import com.opensymphony.oscache.base.events.*;
009: import com.opensymphony.oscache.base.persistence.PersistenceListener;
010: import com.opensymphony.oscache.util.StringUtil;
011:
012: import org.apache.commons.logging.Log;
013: import org.apache.commons.logging.LogFactory;
014:
015: import java.util.*;
016:
017: import javax.swing.event.EventListenerList;
018:
019: /**
020: * An AbstractCacheAdministrator defines an abstract cache administrator, implementing all
021: * the basic operations related to the configuration of a cache, including assigning
022: * any configured event handlers to cache objects.<p>
023: *
024: * Extend this class to implement a custom cache administrator.
025: *
026: * @version $Revision: 425 $
027: * @author a href="mailto:mike@atlassian.com">Mike Cannon-Brookes</a>
028: * @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
029: * @author <a href="mailto:abergevin@pyxis-tech.com">Alain Bergevin</a>
030: * @author <a href="mailto:fabian.crabus@gurulogic.de">Fabian Crabus</a>
031: * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
032: */
033: public abstract class AbstractCacheAdministrator implements
034: java.io.Serializable {
035: private static transient final Log log = LogFactory
036: .getLog(AbstractCacheAdministrator.class);
037:
038: /**
039: * A boolean cache configuration property that indicates whether the cache
040: * should cache objects in memory. Set this property to <code>false</code>
041: * to disable in-memory caching.
042: */
043: public final static String CACHE_MEMORY_KEY = "cache.memory";
044:
045: /**
046: * An integer cache configuration property that specifies the maximum number
047: * of objects to hold in the cache. Setting this to a negative value will
048: * disable the capacity functionality - there will be no limit to the number
049: * of objects that are held in cache.
050: */
051: public final static String CACHE_CAPACITY_KEY = "cache.capacity";
052:
053: /**
054: * A String cache configuration property that specifies the classname of
055: * an alternate caching algorithm. This class must extend
056: * {@link com.opensymphony.oscache.base.algorithm.AbstractConcurrentReadCache}
057: * By default caches will use {@link com.opensymphony.oscache.base.algorithm.LRUCache} as
058: * the default algorithm if the cache capacity is set to a postive value, or
059: * {@link com.opensymphony.oscache.base.algorithm.UnlimitedCache} if the
060: * capacity is negative (ie, disabled).
061: */
062: public final static String CACHE_ALGORITHM_KEY = "cache.algorithm";
063:
064: /**
065: * A boolean cache configuration property that indicates whether the persistent
066: * cache should be unlimited in size, or should be restricted to the same size
067: * as the in-memory cache. Set this property to <code>true</code> to allow the
068: * persistent cache to grow without bound.
069: */
070: public final static String CACHE_DISK_UNLIMITED_KEY = "cache.unlimited.disk";
071:
072: /**
073: * The configuration key that specifies whether we should block waiting for new
074: * content to be generated, or just serve the old content instead. The default
075: * behaviour is to serve the old content since that provides the best performance
076: * (at the cost of serving slightly stale data).
077: */
078: public final static String CACHE_BLOCKING_KEY = "cache.blocking";
079:
080: /**
081: * A String cache configuration property that specifies the classname that will
082: * be used to provide cache persistence. This class must extend {@link PersistenceListener}.
083: */
084: public static final String PERSISTENCE_CLASS_KEY = "cache.persistence.class";
085:
086: /**
087: * A String cache configuration property that specifies if the cache persistence
088: * will only be used in overflow mode, that is, when the memory cache capacity has been reached.
089: */
090: public static final String CACHE_PERSISTENCE_OVERFLOW_KEY = "cache.persistence.overflow.only";
091:
092: /**
093: * A String cache configuration property that holds a comma-delimited list of
094: * classnames. These classes specify the event handlers that are to be applied
095: * to the cache.
096: */
097: public static final String CACHE_ENTRY_EVENT_LISTENERS_KEY = "cache.event.listeners";
098: protected Config config = null;
099:
100: /**
101: * Holds a list of all the registered event listeners. Event listeners are specified
102: * using the {@link #CACHE_ENTRY_EVENT_LISTENERS_KEY} configuration key.
103: */
104: protected EventListenerList listenerList = new EventListenerList();
105:
106: /**
107: * The algorithm class being used, as specified by the {@link #CACHE_ALGORITHM_KEY}
108: * configuration property.
109: */
110: protected String algorithmClass = null;
111:
112: /**
113: * The cache capacity (number of entries), as specified by the {@link #CACHE_CAPACITY_KEY}
114: * configuration property.
115: */
116: protected int cacheCapacity = -1;
117:
118: /**
119: * Whether the cache blocks waiting for content to be build, or serves stale
120: * content instead. This value can be specified using the {@link #CACHE_BLOCKING_KEY}
121: * configuration property.
122: */
123: private boolean blocking = false;
124:
125: /**
126: * Whether or not to store the cache entries in memory. This is configurable using the
127: * {@link com.opensymphony.oscache.base.AbstractCacheAdministrator#CACHE_MEMORY_KEY} property.
128: */
129: private boolean memoryCaching = true;
130:
131: /**
132: * Whether the persistent cache should be used immediately or only when the memory capacity
133: * has been reached, ie. overflow only.
134: * This can be set via the {@link #CACHE_PERSISTENCE_OVERFLOW_KEY} configuration property.
135: */
136: private boolean overflowPersistence;
137:
138: /**
139: * Whether the disk cache should be unlimited in size, or matched 1-1 to the memory cache.
140: * This can be set via the {@link #CACHE_DISK_UNLIMITED_KEY} configuration property.
141: */
142: private boolean unlimitedDiskCache;
143:
144: /**
145: * Create the AbstractCacheAdministrator.
146: * This will initialize all values and load the properties from oscache.properties.
147: */
148: protected AbstractCacheAdministrator() {
149: this (null);
150: }
151:
152: /**
153: * Create the AbstractCacheAdministrator.
154: *
155: * @param p the configuration properties for this cache.
156: */
157: protected AbstractCacheAdministrator(Properties p) {
158: loadProps(p);
159: initCacheParameters();
160:
161: if (log.isDebugEnabled()) {
162: log.debug("Constructed AbstractCacheAdministrator()");
163: }
164: }
165:
166: /**
167: * Sets the algorithm to use for the cache.
168: *
169: * @see com.opensymphony.oscache.base.algorithm.LRUCache
170: * @see com.opensymphony.oscache.base.algorithm.FIFOCache
171: * @see com.opensymphony.oscache.base.algorithm.UnlimitedCache
172: * @param newAlgorithmClass The class to use (eg.
173: * <code>"com.opensymphony.oscache.base.algorithm.LRUCache"</code>)
174: */
175: public void setAlgorithmClass(String newAlgorithmClass) {
176: algorithmClass = newAlgorithmClass;
177: }
178:
179: /**
180: * Indicates whether the cache will block waiting for new content to
181: * be built, or serve stale content instead of waiting. Regardless of this
182: * setting, the cache will <em>always</em> block if new content is being
183: * created, ie, there's no stale content in the cache that can be served.
184: */
185: public boolean isBlocking() {
186: return blocking;
187: }
188:
189: /**
190: * Sets the cache capacity (number of items). Administrator implementations
191: * should override this method to ensure that their {@link Cache} objects
192: * are updated correctly (by calling {@link AbstractConcurrentReadCache#setMaxEntries(int)}}}.
193: *
194: * @param newCacheCapacity The new capacity
195: */
196: protected void setCacheCapacity(int newCacheCapacity) {
197: cacheCapacity = newCacheCapacity;
198: }
199:
200: /**
201: * Whether entries are cached in memory or not.
202: * Default is true.
203: * Set by the <code>cache.memory</code> property.
204: *
205: * @return Status whether or not memory caching is used.
206: */
207: public boolean isMemoryCaching() {
208: return memoryCaching;
209: }
210:
211: /**
212: * Retrieves the value of one of the configuration properties.
213: *
214: * @param key The key assigned to the property
215: * @return Property value, or <code>null</code> if the property could not be found.
216: */
217: public String getProperty(String key) {
218: return config.getProperty(key);
219: }
220:
221: /**
222: * Indicates whether the unlimited disk cache is enabled or not.
223: */
224: public boolean isUnlimitedDiskCache() {
225: return unlimitedDiskCache;
226: }
227:
228: /**
229: * Check if we use overflowPersistence
230: *
231: * @return Returns the overflowPersistence.
232: */
233: public boolean isOverflowPersistence() {
234: return this .overflowPersistence;
235: }
236:
237: /**
238: * Sets the overflowPersistence flag
239: *
240: * @param overflowPersistence The overflowPersistence to set.
241: */
242: public void setOverflowPersistence(boolean overflowPersistence) {
243: this .overflowPersistence = overflowPersistence;
244: }
245:
246: /**
247: * Retrieves an array containing instances all of the {@link CacheEventListener}
248: * classes that are specified in the OSCache configuration file.
249: */
250: protected CacheEventListener[] getCacheEventListeners() {
251: List classes = StringUtil.split(config
252: .getProperty(CACHE_ENTRY_EVENT_LISTENERS_KEY), ',');
253: CacheEventListener[] listeners = new CacheEventListener[classes
254: .size()];
255:
256: for (int i = 0; i < classes.size(); i++) {
257: String className = (String) classes.get(i);
258:
259: try {
260: Class clazz = Class.forName(className);
261:
262: if (!CacheEventListener.class.isAssignableFrom(clazz)) {
263: log
264: .error("Specified listener class '"
265: + className
266: + "' does not implement CacheEventListener. Ignoring this listener.");
267: } else {
268: listeners[i] = (CacheEventListener) clazz
269: .newInstance();
270: }
271: } catch (ClassNotFoundException e) {
272: log.error("CacheEventListener class '" + className
273: + "' not found. Ignoring this listener.", e);
274: } catch (InstantiationException e) {
275: log
276: .error(
277: "CacheEventListener class '"
278: + className
279: + "' could not be instantiated because it is not a concrete class. Ignoring this listener.",
280: e);
281: } catch (IllegalAccessException e) {
282: log
283: .error(
284: "CacheEventListener class '"
285: + className
286: + "' could not be instantiated because it is not public. Ignoring this listener.",
287: e);
288: }
289: }
290:
291: return listeners;
292: }
293:
294: /**
295: * If there is a <code>PersistenceListener</code> in the configuration
296: * it will be instantiated and applied to the given cache object. If the
297: * <code>PersistenceListener</code> cannot be found or instantiated, an
298: * error will be logged but the cache will not have a persistence listener
299: * applied to it and no exception will be thrown.<p>
300: *
301: * A cache can only have one <code>PersistenceListener</code>.
302: *
303: * @param cache the cache to apply the <code>PersistenceListener</code> to.
304: *
305: * @return the same cache object that was passed in.
306: */
307: protected Cache setPersistenceListener(Cache cache) {
308: String persistenceClassname = config
309: .getProperty(PERSISTENCE_CLASS_KEY);
310:
311: try {
312: Class clazz = Class.forName(persistenceClassname);
313: PersistenceListener persistenceListener = (PersistenceListener) clazz
314: .newInstance();
315:
316: cache.setPersistenceListener(persistenceListener
317: .configure(config));
318: } catch (ClassNotFoundException e) {
319: log.error("PersistenceListener class '"
320: + persistenceClassname
321: + "' not found. Check your configuration.", e);
322: } catch (Exception e) {
323: log.error("Error instantiating class '"
324: + persistenceClassname + "'", e);
325: }
326:
327: return cache;
328: }
329:
330: /**
331: * Applies all of the recognised listener classes to the supplied
332: * cache object. Recognised classes are {@link CacheEntryEventListener}
333: * and {@link CacheMapAccessEventListener}.<p>
334: *
335: * @param cache The cache to apply the configuration to.
336: * @return cache The configured cache object.
337: */
338: protected Cache configureStandardListeners(Cache cache) {
339: if (config.getProperty(PERSISTENCE_CLASS_KEY) != null) {
340: cache = setPersistenceListener(cache);
341: }
342:
343: if (config.getProperty(CACHE_ENTRY_EVENT_LISTENERS_KEY) != null) {
344: // Grab all the specified listeners and add them to the cache's
345: // listener list. Note that listeners that implement more than
346: // one of the event interfaces will be added multiple times.
347: CacheEventListener[] listeners = getCacheEventListeners();
348:
349: for (int i = 0; i < listeners.length; i++) {
350: // Pass through the configuration to those listeners that require it
351: if (listeners[i] instanceof LifecycleAware) {
352: try {
353: ((LifecycleAware) listeners[i]).initialize(
354: cache, config);
355: } catch (InitializationException e) {
356: log.error("Could not initialize listener '"
357: + listeners[i].getClass().getName()
358: + "'. Listener ignored.", e);
359:
360: continue;
361: }
362: }
363:
364: if (listeners[i] instanceof CacheEntryEventListener) {
365: cache.addCacheEventListener(listeners[i]);
366: } else if (listeners[i] instanceof CacheMapAccessEventListener) {
367: cache.addCacheEventListener(listeners[i]);
368: }
369: }
370: }
371:
372: return cache;
373: }
374:
375: /**
376: * Finalizes all the listeners that are associated with the given cache object.
377: * Any <code>FinalizationException</code>s that are thrown by the listeners will
378: * be caught and logged.
379: */
380: protected void finalizeListeners(Cache cache) {
381: // It's possible for cache to be null if getCache() was never called (CACHE-63)
382: if (cache == null) {
383: return;
384: }
385:
386: Object[] listeners = cache.listenerList.getListenerList();
387:
388: for (int i = listeners.length - 2; i >= 0; i -= 2) {
389: if (listeners[i + 1] instanceof LifecycleAware) {
390: try {
391: ((LifecycleAware) listeners[i + 1]).finialize();
392: } catch (FinalizationException e) {
393: log.error("Listener could not be finalized", e);
394: }
395: }
396: }
397: }
398:
399: /**
400: * Initialize the core cache parameters from the configuration properties.
401: * The parameters that are initialized are:
402: * <ul>
403: * <li>the algorithm class ({@link #CACHE_ALGORITHM_KEY})</li>
404: * <li>the cache size ({@link #CACHE_CAPACITY_KEY})</li>
405: * <li>whether the cache is blocking or non-blocking ({@link #CACHE_BLOCKING_KEY})</li>
406: * <li>whether caching to memory is enabled ({@link #CACHE_MEMORY_KEY})</li>
407: * <li>whether the persistent cache is unlimited in size ({@link #CACHE_DISK_UNLIMITED_KEY})</li>
408: * </ul>
409: */
410: private void initCacheParameters() {
411: algorithmClass = getProperty(CACHE_ALGORITHM_KEY);
412:
413: blocking = "true"
414: .equalsIgnoreCase(getProperty(CACHE_BLOCKING_KEY));
415:
416: String cacheMemoryStr = getProperty(CACHE_MEMORY_KEY);
417:
418: if ((cacheMemoryStr != null)
419: && cacheMemoryStr.equalsIgnoreCase("false")) {
420: memoryCaching = false;
421: }
422:
423: unlimitedDiskCache = Boolean.valueOf(
424: config.getProperty(CACHE_DISK_UNLIMITED_KEY))
425: .booleanValue();
426: overflowPersistence = Boolean.valueOf(
427: config.getProperty(CACHE_PERSISTENCE_OVERFLOW_KEY))
428: .booleanValue();
429:
430: String cacheSize = getProperty(CACHE_CAPACITY_KEY);
431:
432: try {
433: if ((cacheSize != null) && (cacheSize.length() > 0)) {
434: cacheCapacity = Integer.parseInt(cacheSize);
435: }
436: } catch (NumberFormatException e) {
437: log
438: .error("The value supplied for the cache capacity, '"
439: + cacheSize
440: + "', is not a valid number. The cache capacity setting is being ignored.");
441: }
442: }
443:
444: /**
445: * Load the properties file from the classpath.
446: */
447: private void loadProps(Properties p) {
448: config = new Config(p);
449: }
450: }
|