blob: a7495e083dfbeb08ff45eb7b307b009245d9c0a0 [file] [log] [blame]
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);
}
}
}
}