001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. The ASF licenses this file to You
004: * under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License. For additional information regarding
015: * copyright in this work, please see the NOTICE file in the top level
016: * directory of this distribution.
017: */
018:
019: package org.apache.roller.util.cache;
020:
021: import java.util.Date;
022: import java.util.HashMap;
023: import java.util.HashSet;
024: import java.util.Iterator;
025: import java.util.Map;
026: import java.util.Set;
027: import org.apache.commons.logging.Log;
028: import org.apache.commons.logging.LogFactory;
029: import org.apache.roller.RollerException;
030: import org.apache.roller.business.runnable.ContinuousWorkerThread;
031: import org.apache.roller.business.runnable.Job;
032: import org.apache.roller.config.RollerConfig;
033: import org.apache.roller.business.RollerFactory;
034: import org.apache.roller.business.UserManager;
035: import org.apache.roller.pojos.BookmarkData;
036: import org.apache.roller.pojos.CommentData;
037: import org.apache.roller.pojos.FolderData;
038: import org.apache.roller.pojos.RefererData;
039: import org.apache.roller.pojos.UserData;
040: import org.apache.roller.pojos.WeblogCategoryData;
041: import org.apache.roller.pojos.WeblogEntryData;
042: import org.apache.roller.pojos.WeblogTemplate;
043: import org.apache.roller.pojos.WebsiteData;
044:
045: /**
046: * A governing class for Roller cache objects.
047: *
048: * The purpose of the CacheManager is to provide a level of abstraction between
049: * classes that use a cache and the implementations of a cache. This allows
050: * us to create easily pluggable cache implementations.
051: *
052: * The other purpose is to provide a single interface for interacting with all
053: * Roller caches at the same time. This is beneficial because as data
054: * changes in the system we often need to notify all caches that some part of
055: * their cached data needs to be invalidated, and the CacheManager makes that
056: * process easier.
057: */
058: public class CacheManager {
059:
060: private static Log log = LogFactory.getLog(CacheManager.class);
061:
062: private static final String DEFAULT_FACTORY = "org.apache.roller.util.cache.ExpiringLRUCacheFactoryImpl";
063:
064: // a reference to the cache factory in use
065: private static CacheFactory cacheFactory = null;
066:
067: // a set of all registered cache handlers
068: private static Set cacheHandlers = new HashSet();
069:
070: // a map of all registered caches
071: private static Map caches = new HashMap();
072:
073: private static ContinuousWorkerThread futureInvalidationsThread = null;
074:
075: static {
076: // lookup what cache factory we want to use
077: String classname = RollerConfig
078: .getProperty("cache.defaultFactory");
079:
080: // use reflection to instantiate our factory class
081: try {
082: Class factoryClass = Class.forName(classname);
083: cacheFactory = (CacheFactory) factoryClass.newInstance();
084: } catch (ClassCastException cce) {
085: log.error(
086: "It appears that your factory does not implement "
087: + "the CacheFactory interface", cce);
088: } catch (Exception e) {
089: log.error("Unable to instantiate cache factory ["
090: + classname + "]" + " falling back on default", e);
091: }
092:
093: if (cacheFactory == null)
094: try {
095: // hmm ... failed to load the specified cache factory
096: // lets try our default
097: Class factoryClass = Class.forName(DEFAULT_FACTORY);
098: cacheFactory = (CacheFactory) factoryClass
099: .newInstance();
100: } catch (Exception e) {
101: log.fatal("Failed to instantiate a cache factory", e);
102: throw new RuntimeException(e);
103: }
104:
105: log.info("Cache Manager Initialized.");
106: log
107: .info("Cache Factory = "
108: + cacheFactory.getClass().getName());
109:
110: // add custom handlers
111: String customHandlers = RollerConfig
112: .getProperty("cache.customHandlers");
113: if (customHandlers != null
114: && customHandlers.trim().length() > 0) {
115:
116: String[] cHandlers = customHandlers.split(",");
117: for (int i = 0; i < cHandlers.length; i++) {
118: // use reflection to instantiate the handler class
119: try {
120: Class handlerClass = Class.forName(cHandlers[i]);
121: CacheHandler customHandler = (CacheHandler) handlerClass
122: .newInstance();
123:
124: cacheHandlers.add(customHandler);
125: } catch (ClassCastException cce) {
126: log
127: .error(
128: "It appears that your handler does not implement "
129: + "the CacheHandler interface",
130: cce);
131: } catch (Exception e) {
132: log.error("Unable to instantiate cache handler ["
133: + cHandlers[i] + "]", e);
134: }
135: }
136: }
137:
138: // determine future invalidations peering window
139: Integer peerTime = new Integer(5);
140: String peerTimeString = RollerConfig
141: .getProperty("cache.futureInvalidations.peerTime");
142: try {
143: peerTime = new Integer(peerTimeString);
144: } catch (NumberFormatException nfe) {
145: // bad input from config file, default already set
146: }
147:
148: // thread time is always 10 secs less than peer time to make sure
149: // there is a little overlap so we don't miss any entries
150: // this means every XX seconds we peer XX + 10 seconds into the future
151: int threadTime = (peerTime.intValue() * 60 * 1000)
152: - (10 * 1000);
153:
154: // start up future invalidations job, running continuously
155: futureInvalidationsThread = new ContinuousWorkerThread(
156: "future invalidations thread", threadTime);
157: Job futureInvalidationsJob = new FuturePostingsInvalidationJob();
158:
159: // inputs
160: Map inputs = new HashMap();
161: inputs.put("peerTime", peerTime);
162: futureInvalidationsJob.input(inputs);
163:
164: // set job and start it
165: futureInvalidationsThread.setJob(futureInvalidationsJob);
166: futureInvalidationsThread.start();
167: }
168:
169: // a non-instantiable class
170: private CacheManager() {
171: }
172:
173: /**
174: * Ask the CacheManager to construct a cache.
175: *
176: * Normally the CacheManager will use whatever CacheFactory has been
177: * chosen for the system via the cache.defaultFactory property.
178: * However, it is possible to override the use of the default factory by
179: * supplying a "factory" property to this method. The value should
180: * be the full classname for the factory you want to use for constructing
181: * the cache.
182: *
183: * example:
184: * factory -> org.apache.roller.util.cache.LRUCacheFactoryImpl
185: *
186: * This allows Roller admins the ability to choose a caching strategy to
187: * use for the whole system, but override it in certain places where they
188: * see fit. It also allows users to write their own caching modifications
189: * and have them used only by specific caches.
190: */
191: public static Cache constructCache(CacheHandler handler,
192: Map properties) {
193:
194: log.debug("Constructing new cache with props " + properties);
195:
196: Cache cache = null;
197:
198: if (properties != null && properties.containsKey("factory")) {
199: // someone wants a custom cache instance
200: String classname = (String) properties.get("factory");
201:
202: try {
203: // use reflection to instantiate the factory class
204: Class factoryClass = Class.forName(classname);
205: CacheFactory factory = (CacheFactory) factoryClass
206: .newInstance();
207:
208: // now ask for a new cache
209: cache = factory.constructCache(properties);
210: } catch (ClassCastException cce) {
211: log
212: .error(
213: "It appears that your factory ["
214: + classname
215: + "] does not implement the CacheFactory interface",
216: cce);
217: } catch (Exception e) {
218: log.error("Unable to instantiate cache factory ["
219: + classname + "] falling back on default", e);
220: }
221: }
222:
223: if (cache == null) {
224: // ask our default cache factory for a new cache instance
225: cache = cacheFactory.constructCache(properties);
226: }
227:
228: if (cache != null) {
229: caches.put(cache.getId(), cache);
230:
231: // register the handler for this new cache
232: if (handler != null) {
233: cacheHandlers.add(handler);
234: }
235: }
236:
237: return cache;
238: }
239:
240: /**
241: * Register a CacheHandler to listen for object invalidations.
242: *
243: * This is here so that it's possible to to add classes which would respond
244: * to object invalidations without necessarily having to create a cache.
245: *
246: * An example would be a handler designed to notify other machines in a
247: * cluster when an object has been invalidated, or possibly the search
248: * index management classes are interested in knowing when objects are
249: * invalidated.
250: */
251: public static void registerHandler(CacheHandler handler) {
252:
253: log.debug("Registering handler " + handler);
254:
255: if (handler != null) {
256: cacheHandlers.add(handler);
257: }
258: }
259:
260: public static void invalidate(WeblogEntryData entry) {
261:
262: log.debug("invalidating entry = " + entry.getAnchor());
263:
264: Iterator handlers = cacheHandlers.iterator();
265: while (handlers.hasNext()) {
266: ((CacheHandler) handlers.next()).invalidate(entry);
267: }
268: }
269:
270: public static void invalidate(WebsiteData website) {
271:
272: log.debug("invalidating website = " + website.getHandle());
273:
274: Iterator handlers = cacheHandlers.iterator();
275: while (handlers.hasNext()) {
276: ((CacheHandler) handlers.next()).invalidate(website);
277: }
278: }
279:
280: public static void invalidate(BookmarkData bookmark) {
281:
282: log.debug("invalidating bookmark = " + bookmark.getId());
283:
284: Iterator handlers = cacheHandlers.iterator();
285: while (handlers.hasNext()) {
286: ((CacheHandler) handlers.next()).invalidate(bookmark);
287: }
288: }
289:
290: public static void invalidate(FolderData folder) {
291:
292: log.debug("invalidating folder = " + folder.getId());
293:
294: Iterator handlers = cacheHandlers.iterator();
295: while (handlers.hasNext()) {
296: ((CacheHandler) handlers.next()).invalidate(folder);
297: }
298: }
299:
300: public static void invalidate(CommentData comment) {
301:
302: log.debug("invalidating comment = " + comment.getId());
303:
304: Iterator handlers = cacheHandlers.iterator();
305: while (handlers.hasNext()) {
306: ((CacheHandler) handlers.next()).invalidate(comment);
307: }
308: }
309:
310: public static void invalidate(RefererData referer) {
311:
312: log.debug("invalidating referer = " + referer.getId());
313:
314: // NOTE: Invalidating an entire website for each referer is not
315: // good for our caching. This may need reevaluation later.
316: //lastExpiredCache.put(referer.getWebsite().getHandle(), new Date());
317:
318: Iterator handlers = cacheHandlers.iterator();
319: while (handlers.hasNext()) {
320: ((CacheHandler) handlers.next()).invalidate(referer);
321: }
322: }
323:
324: public static void invalidate(UserData user) {
325:
326: log.debug("invalidating user = " + user.getUserName());
327:
328: Iterator handlers = cacheHandlers.iterator();
329: while (handlers.hasNext()) {
330: ((CacheHandler) handlers.next()).invalidate(user);
331: }
332: }
333:
334: public static void invalidate(WeblogCategoryData category) {
335:
336: log.debug("invalidating category = " + category.getId());
337:
338: Iterator handlers = cacheHandlers.iterator();
339: while (handlers.hasNext()) {
340: ((CacheHandler) handlers.next()).invalidate(category);
341: }
342: }
343:
344: public static void invalidate(WeblogTemplate template) {
345:
346: log.debug("invalidating template = " + template.getId());
347:
348: Iterator handlers = cacheHandlers.iterator();
349: while (handlers.hasNext()) {
350: ((CacheHandler) handlers.next()).invalidate(template);
351: }
352: }
353:
354: /**
355: * Flush the entire cache system.
356: */
357: public static void clear() {
358:
359: // loop through all caches and trigger a clear
360: Cache cache = null;
361: Iterator cachesIT = caches.values().iterator();
362: while (cachesIT.hasNext()) {
363: cache = (Cache) cachesIT.next();
364:
365: cache.clear();
366: }
367: }
368:
369: /**
370: * Flush a single cache.
371: */
372: public static void clear(String cacheId) {
373:
374: Cache cache = (Cache) caches.get(cacheId);
375: if (cache != null) {
376: cache.clear();
377: }
378: }
379:
380: /**
381: * Compile stats from all registered caches.
382: *
383: * This is basically a hacky version of instrumentation which is being
384: * thrown in because we don't have a full instrumentation strategy yet.
385: * This is here with the full expectation that it will be replaced by
386: * something a bit more elaborate, like JMX.
387: */
388: public static Map getStats() {
389:
390: Map allStats = new HashMap();
391:
392: Cache cache = null;
393: Iterator cachesIT = caches.values().iterator();
394: while (cachesIT.hasNext()) {
395: cache = (Cache) cachesIT.next();
396:
397: allStats.put(cache.getId(), cache.getStats());
398: }
399:
400: return allStats;
401: }
402:
403: /**
404: * Place to do any cleanup tasks for cache system.
405: */
406: public static void shutdown() {
407:
408: // stop our future invalidations thread
409: if (futureInvalidationsThread != null) {
410: futureInvalidationsThread.interrupt();
411: }
412: }
413:
414: }
|