| package org.apache.turbine.services.cache; |
| |
| /* ==================================================================== |
| * The Apache Software License, Version 1.1 |
| * |
| * Copyright (c) 2001-2003 The Apache Software Foundation. All rights |
| * reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * |
| * 3. The end-user documentation included with the redistribution, |
| * if any, must include the following acknowledgment: |
| * "This product includes software developed by the |
| * Apache Software Foundation (http://www.apache.org/)." |
| * Alternately, this acknowledgment may appear in the software itself, |
| * if and wherever such third-party acknowledgments normally appear. |
| * |
| * 4. The names "Apache" and "Apache Software Foundation" and |
| * "Apache Turbine" must not be used to endorse or promote products |
| * derived from this software without prior written permission. For |
| * written permission, please contact apache@apache.org. |
| * |
| * 5. Products derived from this software may not be called "Apache", |
| * "Apache Turbine", nor may "Apache" appear in their name, without |
| * prior written permission of the Apache Software Foundation. |
| * |
| * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR |
| * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
| * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| * ==================================================================== |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals on behalf of the Apache Software Foundation. For more |
| * information on the Apache Software Foundation, please see |
| * <http://www.apache.org/>. |
| */ |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.ObjectOutputStream; |
| |
| import java.util.Enumeration; |
| import java.util.Hashtable; |
| import java.util.Vector; |
| |
| import org.apache.commons.configuration.Configuration; |
| |
| import org.apache.turbine.services.InitializationException; |
| import org.apache.turbine.services.TurbineBaseService; |
| |
| /** |
| * This Service functions as a Global Cache. A global cache is a good |
| * place to store items that you may need to access often but don't |
| * necessarily need (or want) to fetch from the database everytime. A |
| * good example would be a look up table of States that you store in a |
| * database and use throughout your application. Since information |
| * about States doesn't change very often, you could store this |
| * information in the Global Cache and decrease the overhead of |
| * hitting the database everytime you need State information. |
| * |
| * The following properties are needed to configure this service:<br> |
| * |
| * <code><pre> |
| * services.GlobalCacheService.classname=org.apache.turbine.services.cache.TurbineGlobalCacheService |
| * services.GlobalCacheService.cache.initial.size=20 |
| * services.GlobalCacheService.cache.check.frequency=5000 |
| * </pre></code> |
| * |
| * <dl> |
| * <dt>classname</dt><dd>the classname of this service</dd> |
| * <dt>cache.initial.size</dt><dd>Initial size of hash table use to store cached |
| objects. If this property is not present, the default value is 20</dd> |
| * <dt>cache.check.frequency</dt><dd>Cache check frequency in Millis (1000 |
| Millis = 1 second). If this property is not present, the default value is 5000</dd> |
| * </dl> |
| * |
| * @deprecated Use the Fulcrum Cache component instead. |
| * @author <a href="mailto:mbryson@mont.mindspring.com">Dave Bryson</a> |
| * @author <a href="mailto:jon@clearink.com">Jon S. Stevens</a> |
| * @author <a href="mailto:john@zenplex.com">John Thorhauer</a> |
| * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a> |
| * @version $Id$ |
| */ |
| public class TurbineGlobalCacheService |
| extends TurbineBaseService |
| implements GlobalCacheService, |
| Runnable |
| { |
| /** |
| * Initial size of hash table |
| * Value must be > 0. |
| * Default = 20 |
| */ |
| public static final int DEFAULT_INITIAL_CACHE_SIZE = 20; |
| |
| /** |
| * The property for the InitalCacheSize |
| */ |
| public static final String INITIAL_CACHE_SIZE = "cache.initial.size"; |
| |
| /** |
| * The property for the Cache check frequency |
| */ |
| public static final String CACHE_CHECK_FREQUENCY = "cache.check.frequency"; |
| |
| /** |
| * Cache check frequency in Millis (1000 Millis = 1 second). |
| * Value must be > 0. |
| * Default = 5 seconds |
| */ |
| public static final long DEFAULT_CACHE_CHECK_FREQUENCY = 5000; // 5 seconds |
| |
| /** The cache. **/ |
| private Hashtable cache = null; |
| |
| /** cacheCheckFrequency (default - 5 seconds) */ |
| private long cacheCheckFrequency = DEFAULT_CACHE_CHECK_FREQUENCY; |
| |
| /** |
| * Constructor. |
| */ |
| public TurbineGlobalCacheService() |
| { |
| } |
| |
| /** |
| * Called the first time the Service is used. |
| */ |
| public void init() |
| throws InitializationException |
| { |
| int cacheInitialSize = DEFAULT_INITIAL_CACHE_SIZE; |
| Configuration conf = getConfiguration(); |
| if (conf != null) |
| { |
| try |
| { |
| cacheInitialSize = conf.getInt(INITIAL_CACHE_SIZE, DEFAULT_INITIAL_CACHE_SIZE); |
| if (cacheInitialSize <= 0) |
| { |
| throw new IllegalArgumentException(INITIAL_CACHE_SIZE + " must be >0"); |
| } |
| cacheCheckFrequency = conf.getLong(CACHE_CHECK_FREQUENCY, DEFAULT_CACHE_CHECK_FREQUENCY); |
| if (cacheCheckFrequency <= 0) |
| { |
| throw new IllegalArgumentException(CACHE_CHECK_FREQUENCY + " must be >0"); |
| } |
| } |
| catch (Exception x) |
| { |
| throw new InitializationException( |
| "Failed to initialize TurbineGlobalCacheService", x); |
| } |
| } |
| |
| try |
| { |
| cache = new Hashtable(cacheInitialSize); |
| |
| // Start housekeeping thread. |
| Thread housekeeping = new Thread(this); |
| // Indicate that this is a system thread. JVM will quit only when there |
| // are no more active user threads. Settings threads spawned internally |
| // by Turbine as daemons allows commandline applications using Turbine |
| // to terminate in an orderly manner. |
| housekeeping.setDaemon(true); |
| housekeeping.start(); |
| |
| setInit(true); |
| } |
| catch (Exception e) |
| { |
| throw new InitializationException( |
| "TurbineGlobalCacheService failed to initialize", e); |
| } |
| } |
| |
| /** |
| * Returns an item from the cache. RefreshableCachedObject will be |
| * refreshed if it is expired and not untouched. |
| * |
| * @param id The key of the stored object. |
| * @return The object from the cache. |
| * @exception ObjectExpiredException when either the object is |
| * not in the cache or it has expired. |
| */ |
| public CachedObject getObject(String id) |
| throws ObjectExpiredException |
| { |
| CachedObject obj = null; |
| |
| obj = (CachedObject) cache.get(id); |
| |
| if (obj == null) |
| { |
| // Not in the cache. |
| throw new ObjectExpiredException(); |
| } |
| |
| if (obj.isStale()) |
| { |
| if (obj instanceof RefreshableCachedObject) |
| { |
| RefreshableCachedObject rco = (RefreshableCachedObject) obj; |
| if (rco.isUntouched()) |
| // Do not refresh an object that has exceeded TimeToLive |
| throw new ObjectExpiredException(); |
| // Refresh Object |
| rco.refresh(); |
| if (rco.isStale()) |
| // Object is Expired. |
| throw new ObjectExpiredException(); |
| } |
| else |
| { |
| // Expired. |
| throw new ObjectExpiredException(); |
| } |
| } |
| |
| if (obj instanceof RefreshableCachedObject) |
| { |
| // notify it that it's being accessed. |
| RefreshableCachedObject rco = (RefreshableCachedObject) obj; |
| rco.touch(); |
| } |
| |
| return obj; |
| } |
| |
| /** |
| * Adds an object to the cache. |
| * |
| * @param id The key to store the object by. |
| * @param o The object to cache. |
| */ |
| public void addObject(String id, |
| CachedObject o) |
| { |
| // If the cache already contains the key, remove it and add |
| // the fresh one. |
| if (cache.containsKey(id)) |
| { |
| cache.remove(id); |
| } |
| cache.put(id, o); |
| } |
| |
| /** |
| * Removes an object from the cache. |
| * |
| * @param id The String id for the object. |
| */ |
| public void removeObject(String id) |
| { |
| cache.remove(id); |
| } |
| |
| /** |
| * Circle through the cache and remove stale objects. Frequency |
| * is determined by the cacheCheckFrequency property. |
| */ |
| public void run() |
| { |
| while (true) |
| { |
| // Sleep for amount of time set in cacheCheckFrequency - |
| // default = 5 seconds. |
| try |
| { |
| Thread.sleep(cacheCheckFrequency); |
| } |
| catch (InterruptedException exc) |
| { |
| } |
| |
| clearCache(); |
| } |
| } |
| |
| /** |
| * Iterate through the cache and remove or refresh stale objects. |
| */ |
| public void clearCache() |
| { |
| Vector refreshThese = new Vector(20); |
| // Sync on this object so that other threads do not |
| // change the Hashtable while enumerating over it. |
| synchronized (this) |
| { |
| for (Enumeration e = cache.keys(); e.hasMoreElements();) |
| { |
| String key = (String) e.nextElement(); |
| CachedObject co = (CachedObject) cache.get(key); |
| if (co instanceof RefreshableCachedObject) |
| { |
| RefreshableCachedObject rco = (RefreshableCachedObject) co; |
| if (rco.isUntouched()) |
| cache.remove(key); |
| else if (rco.isStale()) |
| // Do refreshing outside of sync block so as not |
| // to prolong holding the lock on this object |
| refreshThese.addElement(key); |
| } |
| else if (co.isStale()) |
| { |
| cache.remove(key); |
| } |
| } |
| } |
| |
| for (Enumeration e = refreshThese.elements(); e.hasMoreElements();) |
| { |
| String key = (String) e.nextElement(); |
| CachedObject co = (CachedObject) cache.get(key); |
| RefreshableCachedObject rco = (RefreshableCachedObject) co; |
| rco.refresh(); |
| } |
| } |
| |
| /** |
| * Returns the number of objects currently stored in the cache |
| * |
| * @return int number of object in the cache |
| */ |
| public int getNumberOfObjects() |
| { |
| return cache.size(); |
| } |
| |
| /** |
| * Returns the current size of the cache. |
| * |
| * @return int representing current cache size in number of bytes |
| */ |
| public int getCacheSize() |
| throws IOException |
| { |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| ObjectOutputStream out = new ObjectOutputStream(baos); |
| out.writeObject(cache); |
| out.flush(); |
| // |
| // Subtract 4 bytes from the length, because the serialization |
| // magic number (2 bytes) and version number (2 bytes) are |
| // both written to the stream before the object |
| // |
| int objectsize = baos.toByteArray().length - 4; |
| return objectsize; |
| } |
| |
| /** |
| * Flush the cache of all objects. |
| */ |
| public void flushCache() |
| { |
| |
| synchronized (this) |
| { |
| for (Enumeration e = cache.keys(); e.hasMoreElements();) |
| { |
| String key = (String) e.nextElement(); |
| cache.remove(key); |
| } |
| } |
| } |
| } |