001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010: package org.mmbase.cache;
011:
012: import java.util.*;
013:
014: import org.mmbase.util.*;
015: import org.mmbase.util.logging.Logger;
016: import org.mmbase.util.logging.Logging;
017: import org.mmbase.util.xml.DocumentReader;
018: import org.w3c.dom.Element;
019:
020: import java.util.concurrent.ConcurrentHashMap;
021:
022: /**
023: * Cache manager manages the static methods of {@link Cache}. If you prefer you can call them on this in stead.
024: *
025: * @since MMBase-1.8
026: * @version $Id: CacheManager.java,v 1.24 2008/02/23 14:03:23 andre Exp $
027: */
028: public class CacheManager {
029:
030: private static final Logger log = Logging
031: .getLoggerInstance(CacheManager.class);
032:
033: /**
034: * All registered caches
035: */
036: //private static final NavigableMap<String, Cache<?,?>> caches = new ConcurrentSkipListMap<String, Cache<?,?>>();
037: private static final Map<String, Cache<?, ?>> caches = new ConcurrentHashMap<String, Cache<?, ?>>();
038:
039: /**
040: * Returns the Cache with a certain name. To be used in combination with getCaches(). If you
041: * need a certain cache, you can just as well call the non-static 'getCache' which is normally
042: * in cache singletons.
043: *
044: * @see #getCaches
045: */
046: public static Cache getCache(String name) {
047: return caches.get(name);
048: }
049:
050: /**
051: * Returns a cache wrapped in a 'Bean', so it is not a Map any more. This makes it easier
052: * accesible by tools which want that (like EL).
053: * @since MMBase-1.9
054: */
055: public static Bean getBean(String name) {
056: return new Bean(getCache(name));
057: }
058:
059: public static Set<Bean> getCaches(String className) {
060: Set<Bean> result = new HashSet<Bean>();
061: for (Cache c : caches.values()) {
062: try {
063: if (className == null
064: || Class.forName(className).isInstance(c)) {
065: result.add(new Bean(c));
066: }
067: } catch (Exception e) {
068: throw new RuntimeException(e);
069: }
070: }
071: return result;
072: }
073:
074: /**
075: * Returns the names of all caches.
076: *
077: * @return A Set containing the names of all caches.
078: */
079: public static Set<String> getCaches() {
080: return Collections.unmodifiableSet(caches.keySet());
081: }
082:
083: /**
084: * @since MMBase-1.8.6
085: */
086: public static Map<String, Cache<?, ?>> getMap() {
087: return Collections.unmodifiableMap(caches);
088: }
089:
090: /**
091: * Puts a cache in the caches repository. This function will be
092: * called in the static of childs, therefore it is protected.
093: *
094: * @param cache A cache.
095: * @return The previous cache of the same type (stored under the same name)
096: */
097: public static <K, V> Cache<K, V> putCache(Cache<K, V> cache) {
098: Cache old = caches.put(cache.getName(), cache);
099: configure(configReader, cache.getName());
100: return old;
101: }
102:
103: /**
104: * Configures the caches using a config File. There is only one
105: * config file now so the argument is a little overdone, but it
106: * doesn't harm.
107: */
108:
109: private static void configure(DocumentReader file) {
110: configure(file, null);
111: }
112:
113: private static DocumentReader configReader = null;
114:
115: /**
116: * As configure, but it only changes the configuration of the cache 'only'.
117: * This is called on first use of a cache.
118: */
119: private static void configure(DocumentReader xmlReader, String only) {
120: if (xmlReader == null) {
121: return; // nothing can be done...
122: }
123:
124: if (only == null) {
125: log.service("Configuring caches with "
126: + xmlReader.getSystemId());
127: } else {
128: if (log.isDebugEnabled())
129: log.debug("Configuring cache " + only + " with file "
130: + xmlReader.getSystemId());
131: }
132:
133: for (Element cacheElement : xmlReader.getChildElements(
134: "caches", "cache")) {
135: String cacheName = cacheElement.getAttribute("name");
136: if (only != null && !only.equals(cacheName)) {
137: continue;
138: }
139: // TODO: fix again when everybody runs 1.5.0_08, because of
140: // generics bug http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4916620
141: Cache cache = getCache(cacheName);
142: if (cache == null) {
143: log.service("No cache " + cacheName
144: + " is present (perhaps not used yet?)");
145: } else {
146: String clazz = xmlReader.getElementValue(xmlReader
147: .getElementByPath(cacheElement,
148: "cache.implementation.class"));
149: if (!"".equals(clazz)) {
150: Element cacheImpl = xmlReader.getElementByPath(
151: cacheElement, "cache.implementation");
152: Map<String, String> configValues = new HashMap<String, String>();
153: for (Element attrNode : xmlReader.getChildElements(
154: cacheImpl, "param")) {
155: String paramName = xmlReader
156: .getElementAttributeValue(attrNode,
157: "name");
158: String paramValue = xmlReader
159: .getElementValue(attrNode);
160: configValues.put(paramName, paramValue);
161: }
162: cache.setImplementation(clazz, configValues);
163: }
164: String status = xmlReader
165: .getElementValue(xmlReader.getElementByPath(
166: cacheElement, "cache.status"));
167: cache.setActive(status.equalsIgnoreCase("active"));
168: try {
169: Integer size = Integer.valueOf(xmlReader
170: .getElementValue(xmlReader
171: .getElementByPath(cacheElement,
172: "cache.size")));
173: cache.setMaxSize(size.intValue());
174: log.service("Setting " + cacheName + " " + status
175: + " with size " + size);
176: } catch (NumberFormatException nfe) {
177: log.error("Could not configure cache " + cacheName
178: + " because the size was wrong: "
179: + nfe.toString());
180: }
181: String maxSize = xmlReader.getElementValue(xmlReader
182: .getElementByPath(cacheElement,
183: "cache.maxEntrySize"));
184: if (!"".equals(maxSize)) {
185: try {
186: cache.maxEntrySize = Integer.parseInt(maxSize);
187: log.service("Setting maximum entry size on "
188: + cacheName + ": " + cache.maxEntrySize
189: + " bytes ");
190: } catch (NumberFormatException nfe2) {
191: log
192: .error("Could not set max entry size cache of "
193: + cacheName
194: + " because "
195: + nfe2.toString());
196: }
197: } else {
198: if (cache.getDefaultMaxEntrySize() > 0) {
199: log
200: .service("No max entry size specified for this cache taking default "
201: + cache
202: .getDefaultMaxEntrySize()
203: + " bytes");
204: }
205: cache.maxEntrySize = cache.getDefaultMaxEntrySize();
206: //now see if we have to load cache release strategies for this lovely cache...
207: if (cache instanceof QueryResultCache) {
208: QueryResultCache queryCache = (QueryResultCache) cache;
209: //first remove all present strategies (this might be a reconfiguration)
210: queryCache.getReleaseStrategy()
211: .removeAllStrategies();
212: log.debug("found a SearchQueryCache: "
213: + cacheName);
214: //see if there are globally configured release strategies
215: List<ReleaseStrategy> strategies = findReleaseStrategies(
216: xmlReader, xmlReader
217: .getElementByPath("caches"));
218: if (strategies != null) {
219: log
220: .debug("found "
221: + strategies.size()
222: + " globally configured strategies");
223: queryCache.addReleaseStrategies(strategies);
224: }
225:
226: //see if there are strategies configured for this cache
227: strategies = findReleaseStrategies(xmlReader,
228: cacheElement);
229: if (strategies != null) {
230: log.debug("found " + strategies.size()
231: + " strategies for cache "
232: + cache.getName());
233: queryCache.addReleaseStrategies(strategies);
234: }
235: if (queryCache.getReleaseStrategy().size() == 0) {
236: log
237: .warn("No release-strategies configured for cache "
238: + queryCache
239: + " (nor globally configured); falling back to basic release strategy");
240: queryCache
241: .addReleaseStrategy(new BasicReleaseStrategy());
242: }
243: }
244: }
245: }
246: }
247: }
248:
249: /**
250: * @param reader xml document reader instance
251: * @param parentElement the parent of the releaseStrategies element
252: * @return List of ReleaseStrategy instances
253: * @since 1.8
254: */
255: private static List<ReleaseStrategy> findReleaseStrategies(
256: DocumentReader reader, Element parentElement) {
257: List<ReleaseStrategy> result = new ArrayList<ReleaseStrategy>();
258:
259: List<Element> strategyParentIterator = reader.getChildElements(
260: parentElement, "releaseStrategies");
261: if (strategyParentIterator.size() == 0) {
262: return null;
263: } else {
264: parentElement = strategyParentIterator.get(0);
265:
266: //now find the strategies
267: for (Element strategy : reader.getChildElements(
268: parentElement, "strategy")) {
269: String strategyClassName = reader
270: .getElementValue(strategy);
271: log.debug("found strategy in configuration: "
272: + strategyClassName);
273: try {
274: ReleaseStrategy releaseStrategy = getStrategyInstance(strategyClassName);
275: log
276: .debug("still there after trying to get a strategy instance... Instance is "
277: + releaseStrategy == null ? "null"
278: : "not null");
279:
280: //check if we got something
281: if (releaseStrategy != null) {
282: result.add(releaseStrategy);
283: log.debug("Successfully created and added "
284: + releaseStrategy.getName()
285: + " instance");
286: } else {
287: log.error("release strategy instance is null.");
288: }
289:
290: } catch (CacheConfigurationException e1) {
291: // here we throw a runtime exception, because there is
292: // no way we can deal with this error.
293: throw new RuntimeException(
294: "Cache configuration error: "
295: + e1.toString(), e1);
296: }
297: }
298: }
299: return result;
300: }
301:
302: /**
303: * I moved this code away from <code>configure()</code> just to
304: * clean up a little, and keep the code readable
305: * XXX: Who is I?
306: * @param strategyClassName
307: * @since 1.8
308: */
309: private static ReleaseStrategy getStrategyInstance(
310: String strategyClassName)
311: throws CacheConfigurationException {
312: log.debug("getStrategyInstance()");
313: Class strategyClass;
314: ReleaseStrategy strategy = null;
315: try {
316: strategyClass = Class.forName(strategyClassName);
317: strategy = (ReleaseStrategy) strategyClass.newInstance();
318: log
319: .debug("created strategy instance: "
320: + strategyClassName);
321:
322: } catch (ClassCastException e) {
323: log.debug(strategyClassName
324: + " can not be cast to strategy");
325: throw new CacheConfigurationException(strategyClassName
326: + " can not be cast to strategy");
327: } catch (ClassNotFoundException e) {
328: log.debug("exception getStrategyInstance()");
329: throw new CacheConfigurationException("Class "
330: + strategyClassName + "was not found");
331: } catch (InstantiationException e) {
332: log.debug("exception getStrategyInstance()");
333: throw new CacheConfigurationException("A new instance of "
334: + strategyClassName + "could not be created: "
335: + e.toString());
336: } catch (IllegalAccessException e) {
337: log.debug("exception getStrategyInstance()");
338: throw new CacheConfigurationException("A new instance of "
339: + strategyClassName + "could not be created: "
340: + e.toString());
341: }
342: log.debug("exit getStrategyInstance()");
343: return strategy;
344: }
345:
346: /**
347: * The caches can be configured with an XML file, this file can
348: * be changed which causes the caches to be reconfigured automaticly.
349: */
350: private static ResourceWatcher configWatcher = new ResourceWatcher() {
351: public void onChange(String resource) {
352: try {
353: configReader = new DocumentReader(ResourceLoader
354: .getConfigurationRoot()
355: .getInputSource(resource), Cache.class);
356: } catch (Exception e) {
357: log.error(e);
358: return;
359: }
360: configure(configReader);
361: }
362: };
363:
364: static { // configure
365: log.debug("Static init of Caches");
366: configWatcher.add("caches.xml");
367: configWatcher.onChange("caches.xml");
368: configWatcher.setDelay(10 * 1000); // check every 10 secs if config changed
369: configWatcher.start();
370:
371: }
372:
373: public static int getTotalByteSize() {
374: int len = 0;
375: SizeOf sizeof = new SizeOf();
376: for (Map.Entry entry : caches.entrySet()) {
377: len += sizeof.sizeof(entry.getKey())
378: + sizeof.sizeof(entry.getValue());
379: }
380: return len;
381: }
382:
383: /**
384: * Clears and dereferences all caches. To be used on shutdown of MMBase.
385: * @since MMBase-1.8.1
386: */
387: public static void shutdown() {
388: log.info("Clearing all caches");
389: for (Cache<?, ?> cache : caches.values()) {
390: cache.clear();
391: }
392: caches.clear();
393: }
394:
395: /**
396: * Used in config/functions/caches.xml
397: */
398: public Object remove(String name, Object key) {
399: Cache cache = getCache(name);
400: if (cache == null)
401: throw new IllegalArgumentException();
402: return cache.remove(key);
403: }
404:
405: public static class Bean<K, V> {
406: /* private final Cache<K, V> cache; // this line prevents building in Java 1.5.0_07 probably because of http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4916620 */
407: private final Cache cache;
408:
409: public Bean(Cache<K, V> c) {
410: cache = c;
411: }
412:
413: public String getName() {
414: return cache.getName();
415: }
416:
417: public String getDescription() {
418: return cache.getDescription();
419: }
420:
421: public int getMaxEntrySize() {
422: return cache.getMaxEntrySize();
423: }
424:
425: public Set<Map.Entry<K, V>> getEntrySet() {
426: return new HashSet<Map.Entry<K, V>>(cache.entrySet());
427: }
428:
429: public Set<K> getKeySet() {
430: return new HashSet<K>(cache.keySet());
431: }
432:
433: public long getHits() {
434: return cache.getHits();
435: }
436:
437: public long getMisses() {
438: return cache.getMisses();
439: }
440:
441: public long getPuts() {
442: return cache.getPuts();
443: }
444:
445: public int getMaxSize() {
446: return cache.maxSize();
447: }
448:
449: public int getSize() {
450: return cache.size();
451: }
452:
453: public double getRatio() {
454: return cache.getRatio();
455: }
456:
457: public String getStats() {
458: return cache.getStats();
459: }
460:
461: public String toString() {
462: return cache.toString();
463: }
464:
465: public boolean isActive() {
466: return cache.isActive();
467: }
468:
469: public int getByteSize() {
470: return cache.getByteSize();
471: }
472:
473: public int getCheapByteSize() {
474: return cache.getCheapByteSize();
475: }
476:
477: public boolean isEmpty() {
478: return cache.isEmpty();
479: }
480:
481: public ReleaseStrategy getReleaseStrategy() {
482: return cache instanceof QueryResultCache ? ((QueryResultCache) cache)
483: .getReleaseStrategy()
484: : null;
485: }
486:
487: public Map<K, V> getMap() {
488: return cache;
489: }
490:
491: public Map<K, Integer> getCounts() {
492: return new AbstractMap<K, Integer>() {
493: public Set<Map.Entry<K, Integer>> entrySet() {
494: return new AbstractSet<Map.Entry<K, Integer>>() {
495: public int size() {
496: return cache.size();
497: }
498:
499: public Iterator<Map.Entry<K, Integer>> iterator() {
500: return new Iterator<Map.Entry<K, Integer>>() {
501: private Iterator<K> iterator = new HashSet<K>(
502: cache.keySet()).iterator();
503:
504: public boolean hasNext() {
505: return iterator.hasNext();
506: }
507:
508: public Map.Entry<K, Integer> next() {
509: K key = iterator.next();
510: return new org.mmbase.util.Entry<K, Integer>(
511: key, cache.getCount(key));
512: }
513:
514: public void remove() {
515: throw new UnsupportedOperationException();
516: }
517: };
518: }
519: };
520: }
521: };
522: }
523:
524: public boolean equals(Object o) {
525: return o instanceof Bean && ((Bean) o).cache.equals(cache);
526: }
527:
528: public int hashCode() {
529: return cache.hashCode();
530: }
531: }
532: }
|