001: /*
002: Copyright 2004 Philip Jacob <phil@whirlycott.com>
003: Seth Fitzsimmons <seth@note.amherst.edu>
004:
005: Licensed under the Apache License, Version 2.0 (the "License");
006: you may not use this file except in compliance with the License.
007: You may obtain a copy of the License at
008:
009: http://www.apache.org/licenses/LICENSE-2.0
010:
011: Unless required by applicable law or agreed to in writing, software
012: distributed under the License is distributed on an "AS IS" BASIS,
013: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: See the License for the specific language governing permissions and
015: limitations under the License.
016: */
017:
018: package com.whirlycott.cache;
019:
020: import java.io.InputStream;
021: import java.util.ArrayList;
022: import java.util.HashMap;
023: import java.util.Map;
024: import java.util.concurrent.ScheduledExecutorService;
025: import java.util.concurrent.ScheduledFuture;
026: import java.util.concurrent.TimeUnit;
027: import java.util.concurrent.Executors;
028: import javax.xml.parsers.DocumentBuilderFactory;
029:
030: import org.apache.commons.logging.Log;
031: import org.apache.commons.logging.LogFactory;
032:
033: import org.w3c.dom.Document;
034: import org.w3c.dom.Element;
035: import org.w3c.dom.NodeList;
036:
037: /**
038: * The CacheManager manages caches by creating and deleting them.
039: *
040: * @author Phil Jacob
041: */
042: public class CacheManager {
043:
044: /**
045: * Holds a representation of the configuration.
046: */
047: protected static final Map<String, CacheConfiguration> configuration = new HashMap<String, CacheConfiguration>();
048:
049: private String defaultCacheName = null;
050:
051: /**
052: * Logger.
053: */
054: private static final Log log = LogFactory
055: .getLog(CacheManager.class);
056:
057: /*
058: * Got a better idea?
059: * http://www-106.ibm.com/developerworks/java/library/j-dcl.html?dwzone=java
060: */
061: protected static final CacheManager singleton;
062:
063: /**
064: * Amount of time to have the tuner threads sleep for
065: */
066: private int tunerSleepTime;
067:
068: private ScheduledExecutorService scheduledExecutorService;
069:
070: static {
071: singleton = new CacheManager();
072: }
073:
074: /**
075: * @return Returns the configurationProperties.
076: */
077: public static Map<String, CacheConfiguration> getConfiguration() {
078: return configuration;
079: }
080:
081: /**
082: * Returns an instance of the CacheManager.
083: *
084: * @return An instance of the CacheManager.
085: */
086: public static CacheManager getInstance() {
087: return singleton;
088: }
089:
090: /**
091: * Holds a Map of the caches that have been created from the config file.
092: */
093: protected final Map<String, CacheDecorator> namedCaches = new HashMap<String, CacheDecorator>();
094:
095: /**
096: * Holds a Map of the ScheduledFuture's
097: */
098: private Map<String, ScheduledFuture<?>> scheduledFutures = new HashMap<String, ScheduledFuture<?>>();
099:
100: /**
101: * Private constructor (singleton pattern).
102: */
103: private CacheManager() {
104: log
105: .info(Messages
106: .getString("CacheManager.creating_new_cache_manager_singleton")); //$NON-NLS-1$
107:
108: try {
109: configure();
110:
111: } catch (final CacheException e) {
112: // This should never happen.
113: log.fatal(e.getMessage(), e);
114: }
115: }
116:
117: /**
118: * Initializes based on the whirlycache.xml configuration file.
119: *
120: * @throws CacheException When the CacheManager cannot be configured.
121: */
122: protected void configure() throws CacheException {
123: log.debug(Messages
124: .getString("CacheManager.configuring_the_whirlycache")); //$NON-NLS-1$
125:
126: // Try to load the specified conf file.
127: Document doc = loadConfigFile(Constants.CONFIG_FILE);
128:
129: if (doc == null) {
130: log.warn("Could not load " + Constants.CONFIG_FILE
131: + " file. Falling back to defaults."); //$NON-NLS-1$ //$NON-NLS-2$
132:
133: // Ok, so go and load up the whirlycache-default.xml file.
134: doc = loadConfigFile(Constants.DEFAULT_CONFIG_FILE);
135:
136: // Now this is unusual... can't find *any* config files.
137: if (doc == null) {
138: final String msg = Messages
139: .getString("CacheManager.cannot_load_default_config_file"); //$NON-NLS-1$
140: // log.fatal(msg);
141: throw new CacheException(msg);
142: }
143: }
144:
145: /*
146: * Build a Map of Maps containing key/value pairs of the options in the
147: * config file. Admittedly, this kind of sucks and I would be open to
148: * seeing another way of doing this. - phil.
149: */
150: final Element root = doc.getDocumentElement();
151:
152: tunerSleepTime = getIntegerValue(root,
153: Constants.CONFIG_TUNER_SLEEPTIME, 10);
154:
155: int corePoolSize = getIntegerValue(root,
156: Constants.CONFIG_TUNER_THREADS, 2);
157:
158: scheduledExecutorService = Executors
159: .newScheduledThreadPool(corePoolSize);
160:
161: defaultCacheName = root
162: .getElementsByTagName("default-cache").item(0).getTextContent(); //$NON-NLS-1$
163: log
164: .debug(Messages
165: .getString("CacheManager.default_cache_name") + defaultCacheName); //$NON-NLS-1$
166:
167: final NodeList caches = root
168: .getElementsByTagName(Constants.CONFIG_CACHE);
169: for (int i = 0; i < caches.getLength(); i++) {
170: final CacheConfiguration configuration = new CacheConfiguration();
171: final Element elementCache = (Element) caches.item(i);
172: final String cacheName = elementCache
173: .getAttribute(Constants.CONFIG_NAME);
174: log
175: .debug(Messages
176: .getString("CacheManager.cache_name") + cacheName); //$NON-NLS-1$
177: configuration.setName(cacheName);
178:
179: final NodeList elementCacheAttributes = elementCache
180: .getElementsByTagName("*"); //$NON-NLS-1$
181: for (int j = 0; j < elementCacheAttributes.getLength(); j++) {
182: final Element att = (Element) elementCacheAttributes
183: .item(j);
184:
185: final String nodeName = att.getNodeName();
186: final String nodeValue = att.getTextContent();
187:
188: if (Constants.CONFIG_BACKEND.equals(nodeName)) {
189: configuration.setBackend(nodeValue);
190:
191: } else if (Constants.CONFIG_POLICY.equals(nodeName)) {
192: configuration.setPolicy(nodeValue);
193:
194: } else if (Constants.CONFIG_MAXSIZE.equals(nodeName)) {
195: configuration.setMaxSize(Integer
196: .parseInt(nodeValue));
197:
198: } else {
199: throw new CacheException("Unknown parameter: "
200: + nodeName); // TODO put in Messages
201: }
202:
203: log.debug(" - " + nodeName + "=" + nodeValue); //$NON-NLS-1$ //$NON-NLS-2$
204: }
205:
206: // Make all the caches listed in the config file.
207: log.debug(Messages
208: .getString("CacheManager.making_named_caches")); //$NON-NLS-1$
209: createCache(configuration);
210:
211: // Store the configured information.
212: CacheManager.configuration
213: .put(elementCache
214: .getAttribute(Constants.CONFIG_NAME),
215: configuration);
216: }
217:
218: //Verify that the default cache has been created.
219: if (namedCaches.get(defaultCacheName) == null) {
220: final Object[] args = { defaultCacheName };
221: throw new CacheException(Messages.getCompoundString(
222: "CacheManager.nonexistent_default_cache", args)); //$NON-NLS-1$
223: }
224: }
225:
226: private int getIntegerValue(Element root, String nodeName,
227: int defaultValue) throws CacheException {
228: NodeList list = root.getElementsByTagName(nodeName);
229:
230: switch (list.getLength()) {
231: case 0:
232: return defaultValue;
233: case 1:
234: return Integer.parseInt(list.item(0).getTextContent());
235: default:
236: throw new CacheException("Can only specify " + nodeName
237: + " once."); //TODO Messages
238: }
239: }
240:
241: /**
242: * Initialize a Cache.
243: *
244: * @param _configuration Configuration to initialize cache with.
245: *
246: * @return Empty Cache, ready for use.
247: *
248: * @throws CacheException When the cache cannot be initialized.
249: */
250: public Cache createCache(final CacheConfiguration _configuration)
251: throws CacheException {
252: if (_configuration == null || _configuration.getName() == null) {
253: final String msg = Messages
254: .getString("CacheManager.cache_name_cannot_be_null"); //$NON-NLS-1$
255: log.error(msg);
256: throw new CacheException(msg);
257: }
258:
259: log.debug("Creating cache: " + _configuration.getName());
260:
261: CacheDecorator c = namedCaches.get(_configuration.getName());
262:
263: // If it's null, create one.
264: if (c == null) {
265:
266: try {
267: final String backend = _configuration.getBackend();
268: log.debug("Cache backend is " + backend); //$NON-NLS-1$
269:
270: final Object possiblyC = Class.forName(backend)
271: .newInstance();
272:
273: if (!(possiblyC instanceof ManagedCache))
274: throw new CacheException(
275: "Problem creating an instance of "
276: + backend
277: + " because it does not implement the ManagedCache interface."); //$NON-NLS-1$ //$NON-NLS-2$
278:
279: final ManagedCache<Object, Item<Object>> managedCache = (ManagedCache<Object, Item<Object>>) possiblyC;
280: final CacheMaintenancePolicy<Object, Item<Object>> policy = createPolicy(_configuration);
281:
282: c = new CacheDecorator<Object, Object>(managedCache,
283: _configuration, policy);
284:
285: ScheduledFuture<?> future = scheduledExecutorService
286: .scheduleWithFixedDelay(c, 0, tunerSleepTime,
287: TimeUnit.SECONDS);
288: scheduledFutures.put(_configuration.getName(), future);
289:
290: } catch (final Exception e) {
291: log
292: .fatal(
293: Messages
294: .getString("CacheManager.cannot_create_instance_of_impl"), e); //$NON-NLS-1$
295: throw new CacheException(e);
296: }
297:
298: namedCaches.put(_configuration.getName(), c);
299: }
300:
301: return c;
302: }
303:
304: /**
305: * Create a cache policy.
306: *
307: * @param configuration
308: */
309: private CacheMaintenancePolicy<Object, Item<Object>> createPolicy(
310: final CacheConfiguration configuration)
311: throws CacheException {
312: final String policyClass = configuration.getPolicy();
313:
314: if (null == policyClass)
315: throw new IllegalArgumentException(
316: Messages
317: .getString("CacheManager.cache_config_get_policy_cannot_be_null")); //$NON-NLS-1$
318:
319: try {
320: return (CacheMaintenancePolicy<Object, Item<Object>>) Class
321: .forName(policyClass).newInstance();
322: } catch (Exception e) {
323: throw new CacheException(
324: "Cannot make an instance of policy class " + policyClass, e); //$NON-NLS-1$
325: }
326: }
327:
328: /**
329: * Destroys the default cache.
330: */
331: public void destroy() throws CacheException {
332: destroy(defaultCacheName);
333: }
334:
335: /**
336: * Shut down an individual Cache.
337: *
338: * @param _cacheName cache to shut down.
339: *
340: * @throws CacheException if there was a problem shutting down the cache.
341: */
342: public void destroy(final String _cacheName) throws CacheException {
343: // If there is a cache, we ought to clear it first.
344: final CacheDecorator c = (CacheDecorator) getCache(_cacheName);
345: c.clear();
346: c.dispose();
347: namedCaches.remove(_cacheName);
348: scheduledFutures.remove(_cacheName).cancel(true);
349: }
350:
351: /**
352: * Gets a reference to the default Cache.
353: *
354: * @return A reference to the default Cache.
355: */
356: public Cache getCache() throws CacheException {
357: return getCache(defaultCacheName);
358: }
359:
360: /**
361: * Get a Cache by name.
362: *
363: * @param _name Name of cache to retrieve.
364: *
365: * @return Cache, specified by name.
366: */
367: public Cache getCache(final String _name) throws CacheException {
368: // Short circuit an invalid name.
369: if (_name == null)
370: throw new IllegalArgumentException(
371: Messages
372: .getString("CacheManager.cannot_get_cache_with_null_name")); //$NON-NLS-1$
373:
374: final Cache c = namedCaches.get(_name);
375:
376: if (c == null)
377: throw new CacheException(
378: "There is no cache called '" + _name + "'"); //$NON-NLS-1$ //$NON-NLS-2$
379:
380: return c;
381: }
382:
383: /**
384: * Loads the specified config file.
385: *
386: * @param _filename
387: *
388: * @return a Document object representing the structure of the config file
389: */
390: protected Document loadConfigFile(final String _filename)
391: throws CacheException {
392: if (_filename == null) {
393: throw new CacheException(
394: Messages
395: .getString("CacheManager.cannot_load_null_config_file")); //$NON-NLS-1$
396: }
397:
398: final InputStream is = getClass()
399: .getResourceAsStream(_filename);
400: if (is != null) {
401: try {
402: return DocumentBuilderFactory.newInstance()
403: .newDocumentBuilder().parse(is);
404: } catch (final Exception e) {
405: log
406: .error("Problem loading " + _filename + ", " + e.getMessage()); //$NON-NLS-1$
407: }
408: }
409:
410: // If we get this far, it means we haven't found the file.
411: return null;
412: }
413:
414: /**
415: * Shuts down the manager of all of the Caches, which in turn shuts down all of the Caches.
416: *
417: * @throws CacheException When the Cache cannot be shutdown properly.
418: */
419: public void shutdown() throws CacheException {
420: synchronized (namedCaches) {
421: // Loop and shut down all the individual caches.
422: for (String select : new ArrayList<String>(namedCaches
423: .keySet())) {
424: try {
425: destroy(select);
426: } catch (CacheException e) {
427: throw new CacheException(
428: Messages
429: .getString("CacheManager.problem_shutting_down"), e); //$NON-NLS-1$
430: }
431: }
432: }
433:
434: scheduledExecutorService.shutdown();
435: }
436:
437: /**
438: * Returns an array of the named caches that this CacheManager knows about (including the default cache).
439: *
440: * @return array of named caches
441: */
442: public String[] getCacheNames() {
443: final Object[] caches = namedCaches.keySet().toArray();
444: final int size = caches.length;
445: final String[] names = new String[size];
446: for (int i = 0; i < size; i++) {
447: names[i] = (String) caches[i];
448: }
449: return names;
450: }
451: }
|