Initial move to Gradle.
This commit is contained in:
parent
bd8f23d7d6
commit
993384c173
147 changed files with 673 additions and 1 deletions
|
@ -0,0 +1,411 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base;
|
||||
|
||||
import com.opensymphony.oscache.base.algorithm.AbstractConcurrentReadCache;
|
||||
import com.opensymphony.oscache.base.events.*;
|
||||
import com.opensymphony.oscache.base.persistence.PersistenceListener;
|
||||
import com.opensymphony.oscache.util.StringUtil;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.event.EventListenerList;
|
||||
|
||||
/**
|
||||
* An AbstractCacheAdministrator defines an abstract cache administrator, implementing all
|
||||
* the basic operations related to the configuration of a cache, including assigning
|
||||
* any configured event handlers to cache objects.<p>
|
||||
*
|
||||
* Extend this class to implement a custom cache administrator.
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author a href="mailto:mike@atlassian.com">Mike Cannon-Brookes</a>
|
||||
* @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
|
||||
* @author <a href="mailto:abergevin@pyxis-tech.com">Alain Bergevin</a>
|
||||
* @author <a href="mailto:fabian.crabus@gurulogic.de">Fabian Crabus</a>
|
||||
* @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
|
||||
*/
|
||||
public abstract class AbstractCacheAdministrator implements java.io.Serializable {
|
||||
private static transient final Log log = LogFactory.getLog(AbstractCacheAdministrator.class);
|
||||
|
||||
/**
|
||||
* A boolean cache configuration property that indicates whether the cache
|
||||
* should cache objects in memory. Set this property to <code>false</code>
|
||||
* to disable in-memory caching.
|
||||
*/
|
||||
public final static String CACHE_MEMORY_KEY = "cache.memory";
|
||||
|
||||
/**
|
||||
* An integer cache configuration property that specifies the maximum number
|
||||
* of objects to hold in the cache. Setting this to a negative value will
|
||||
* disable the capacity functionality - there will be no limit to the number
|
||||
* of objects that are held in cache.
|
||||
*/
|
||||
public final static String CACHE_CAPACITY_KEY = "cache.capacity";
|
||||
|
||||
/**
|
||||
* A String cache configuration property that specifies the classname of
|
||||
* an alternate caching algorithm. This class must extend
|
||||
* {@link com.opensymphony.oscache.base.algorithm.AbstractConcurrentReadCache}
|
||||
* By default caches will use {@link com.opensymphony.oscache.base.algorithm.LRUCache} as
|
||||
* the default algorithm if the cache capacity is set to a postive value, or
|
||||
* {@link com.opensymphony.oscache.base.algorithm.UnlimitedCache} if the
|
||||
* capacity is negative (ie, disabled).
|
||||
*/
|
||||
public final static String CACHE_ALGORITHM_KEY = "cache.algorithm";
|
||||
|
||||
/**
|
||||
* A boolean cache configuration property that indicates whether the persistent
|
||||
* cache should be unlimited in size, or should be restricted to the same size
|
||||
* as the in-memory cache. Set this property to <code>true</code> to allow the
|
||||
* persistent cache to grow without bound.
|
||||
*/
|
||||
public final static String CACHE_DISK_UNLIMITED_KEY = "cache.unlimited.disk";
|
||||
|
||||
/**
|
||||
* The configuration key that specifies whether we should block waiting for new
|
||||
* content to be generated, or just serve the old content instead. The default
|
||||
* behaviour is to serve the old content since that provides the best performance
|
||||
* (at the cost of serving slightly stale data).
|
||||
*/
|
||||
public final static String CACHE_BLOCKING_KEY = "cache.blocking";
|
||||
|
||||
/**
|
||||
* A String cache configuration property that specifies the classname that will
|
||||
* be used to provide cache persistence. This class must extend {@link PersistenceListener}.
|
||||
*/
|
||||
public static final String PERSISTENCE_CLASS_KEY = "cache.persistence.class";
|
||||
|
||||
/**
|
||||
* A String cache configuration property that specifies if the cache persistence
|
||||
* will only be used in overflow mode, that is, when the memory cache capacity has been reached.
|
||||
*/
|
||||
public static final String CACHE_PERSISTENCE_OVERFLOW_KEY = "cache.persistence.overflow.only";
|
||||
|
||||
/**
|
||||
* A String cache configuration property that holds a comma-delimited list of
|
||||
* classnames. These classes specify the event handlers that are to be applied
|
||||
* to the cache.
|
||||
*/
|
||||
public static final String CACHE_ENTRY_EVENT_LISTENERS_KEY = "cache.event.listeners";
|
||||
protected Config config = null;
|
||||
|
||||
/**
|
||||
* Holds a list of all the registered event listeners. Event listeners are specified
|
||||
* using the {@link #CACHE_ENTRY_EVENT_LISTENERS_KEY} configuration key.
|
||||
*/
|
||||
protected EventListenerList listenerList = new EventListenerList();
|
||||
|
||||
/**
|
||||
* The algorithm class being used, as specified by the {@link #CACHE_ALGORITHM_KEY}
|
||||
* configuration property.
|
||||
*/
|
||||
protected String algorithmClass = null;
|
||||
|
||||
/**
|
||||
* The cache capacity (number of entries), as specified by the {@link #CACHE_CAPACITY_KEY}
|
||||
* configuration property.
|
||||
*/
|
||||
protected int cacheCapacity = -1;
|
||||
|
||||
/**
|
||||
* Whether the cache blocks waiting for content to be build, or serves stale
|
||||
* content instead. This value can be specified using the {@link #CACHE_BLOCKING_KEY}
|
||||
* configuration property.
|
||||
*/
|
||||
private boolean blocking = false;
|
||||
|
||||
/**
|
||||
* Whether or not to store the cache entries in memory. This is configurable using the
|
||||
* {@link com.opensymphony.oscache.base.AbstractCacheAdministrator#CACHE_MEMORY_KEY} property.
|
||||
*/
|
||||
private boolean memoryCaching = true;
|
||||
|
||||
/**
|
||||
* Whether the persistent cache should be used immediately or only when the memory capacity
|
||||
* has been reached, ie. overflow only.
|
||||
* This can be set via the {@link #CACHE_PERSISTENCE_OVERFLOW_KEY} configuration property.
|
||||
*/
|
||||
private boolean overflowPersistence;
|
||||
|
||||
/**
|
||||
* Whether the disk cache should be unlimited in size, or matched 1-1 to the memory cache.
|
||||
* This can be set via the {@link #CACHE_DISK_UNLIMITED_KEY} configuration property.
|
||||
*/
|
||||
private boolean unlimitedDiskCache;
|
||||
|
||||
/**
|
||||
* Create the AbstractCacheAdministrator.
|
||||
* This will initialize all values and load the properties from oscache.properties.
|
||||
*/
|
||||
protected AbstractCacheAdministrator() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the AbstractCacheAdministrator.
|
||||
*
|
||||
* @param p the configuration properties for this cache.
|
||||
*/
|
||||
protected AbstractCacheAdministrator(Properties p) {
|
||||
loadProps(p);
|
||||
initCacheParameters();
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Constructed AbstractCacheAdministrator()");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the algorithm to use for the cache.
|
||||
*
|
||||
* @see com.opensymphony.oscache.base.algorithm.LRUCache
|
||||
* @see com.opensymphony.oscache.base.algorithm.FIFOCache
|
||||
* @see com.opensymphony.oscache.base.algorithm.UnlimitedCache
|
||||
* @param newAlgorithmClass The class to use (eg.
|
||||
* <code>"com.opensymphony.oscache.base.algorithm.LRUCache"</code>)
|
||||
*/
|
||||
public void setAlgorithmClass(String newAlgorithmClass) {
|
||||
algorithmClass = newAlgorithmClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the cache will block waiting for new content to
|
||||
* be built, or serve stale content instead of waiting. Regardless of this
|
||||
* setting, the cache will <em>always</em> block if new content is being
|
||||
* created, ie, there's no stale content in the cache that can be served.
|
||||
*/
|
||||
public boolean isBlocking() {
|
||||
return blocking;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cache capacity (number of items). Administrator implementations
|
||||
* should override this method to ensure that their {@link Cache} objects
|
||||
* are updated correctly (by calling {@link AbstractConcurrentReadCache#setMaxEntries(int)}}}.
|
||||
*
|
||||
* @param newCacheCapacity The new capacity
|
||||
*/
|
||||
protected void setCacheCapacity(int newCacheCapacity) {
|
||||
cacheCapacity = newCacheCapacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether entries are cached in memory or not.
|
||||
* Default is true.
|
||||
* Set by the <code>cache.memory</code> property.
|
||||
*
|
||||
* @return Status whether or not memory caching is used.
|
||||
*/
|
||||
public boolean isMemoryCaching() {
|
||||
return memoryCaching;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of one of the configuration properties.
|
||||
*
|
||||
* @param key The key assigned to the property
|
||||
* @return Property value, or <code>null</code> if the property could not be found.
|
||||
*/
|
||||
public String getProperty(String key) {
|
||||
return config.getProperty(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the unlimited disk cache is enabled or not.
|
||||
*/
|
||||
public boolean isUnlimitedDiskCache() {
|
||||
return unlimitedDiskCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we use overflowPersistence
|
||||
*
|
||||
* @return Returns the overflowPersistence.
|
||||
*/
|
||||
public boolean isOverflowPersistence() {
|
||||
return this.overflowPersistence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the overflowPersistence flag
|
||||
*
|
||||
* @param overflowPersistence The overflowPersistence to set.
|
||||
*/
|
||||
public void setOverflowPersistence(boolean overflowPersistence) {
|
||||
this.overflowPersistence = overflowPersistence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an array containing instances all of the {@link CacheEventListener}
|
||||
* classes that are specified in the OSCache configuration file.
|
||||
*/
|
||||
protected CacheEventListener[] getCacheEventListeners() {
|
||||
List classes = StringUtil.split(config.getProperty(CACHE_ENTRY_EVENT_LISTENERS_KEY), ',');
|
||||
CacheEventListener[] listeners = new CacheEventListener[classes.size()];
|
||||
|
||||
for (int i = 0; i < classes.size(); i++) {
|
||||
String className = (String) classes.get(i);
|
||||
|
||||
try {
|
||||
Class clazz = Class.forName(className);
|
||||
|
||||
if (!CacheEventListener.class.isAssignableFrom(clazz)) {
|
||||
log.error("Specified listener class '" + className + "' does not implement CacheEventListener. Ignoring this listener.");
|
||||
} else {
|
||||
listeners[i] = (CacheEventListener) clazz.newInstance();
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
log.error("CacheEventListener class '" + className + "' not found. Ignoring this listener.", e);
|
||||
} catch (InstantiationException e) {
|
||||
log.error("CacheEventListener class '" + className + "' could not be instantiated because it is not a concrete class. Ignoring this listener.", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
log.error("CacheEventListener class '" + className + "' could not be instantiated because it is not public. Ignoring this listener.", e);
|
||||
}
|
||||
}
|
||||
|
||||
return listeners;
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is a <code>PersistenceListener</code> in the configuration
|
||||
* it will be instantiated and applied to the given cache object. If the
|
||||
* <code>PersistenceListener</code> cannot be found or instantiated, an
|
||||
* error will be logged but the cache will not have a persistence listener
|
||||
* applied to it and no exception will be thrown.<p>
|
||||
*
|
||||
* A cache can only have one <code>PersistenceListener</code>.
|
||||
*
|
||||
* @param cache the cache to apply the <code>PersistenceListener</code> to.
|
||||
*
|
||||
* @return the same cache object that was passed in.
|
||||
*/
|
||||
protected Cache setPersistenceListener(Cache cache) {
|
||||
String persistenceClassname = config.getProperty(PERSISTENCE_CLASS_KEY);
|
||||
|
||||
try {
|
||||
Class clazz = Class.forName(persistenceClassname);
|
||||
PersistenceListener persistenceListener = (PersistenceListener) clazz.newInstance();
|
||||
|
||||
cache.setPersistenceListener(persistenceListener.configure(config));
|
||||
} catch (ClassNotFoundException e) {
|
||||
log.error("PersistenceListener class '" + persistenceClassname + "' not found. Check your configuration.", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Error instantiating class '" + persistenceClassname + "'", e);
|
||||
}
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all of the recognised listener classes to the supplied
|
||||
* cache object. Recognised classes are {@link CacheEntryEventListener}
|
||||
* and {@link CacheMapAccessEventListener}.<p>
|
||||
*
|
||||
* @param cache The cache to apply the configuration to.
|
||||
* @return cache The configured cache object.
|
||||
*/
|
||||
protected Cache configureStandardListeners(Cache cache) {
|
||||
if (config.getProperty(PERSISTENCE_CLASS_KEY) != null) {
|
||||
cache = setPersistenceListener(cache);
|
||||
}
|
||||
|
||||
if (config.getProperty(CACHE_ENTRY_EVENT_LISTENERS_KEY) != null) {
|
||||
// Grab all the specified listeners and add them to the cache's
|
||||
// listener list. Note that listeners that implement more than
|
||||
// one of the event interfaces will be added multiple times.
|
||||
CacheEventListener[] listeners = getCacheEventListeners();
|
||||
|
||||
for (int i = 0; i < listeners.length; i++) {
|
||||
// Pass through the configuration to those listeners that require it
|
||||
if (listeners[i] instanceof LifecycleAware) {
|
||||
try {
|
||||
((LifecycleAware) listeners[i]).initialize(cache, config);
|
||||
} catch (InitializationException e) {
|
||||
log.error("Could not initialize listener '" + listeners[i].getClass().getName() + "'. Listener ignored.", e);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (listeners[i] instanceof CacheEventListener) {
|
||||
cache.addCacheEventListener(listeners[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizes all the listeners that are associated with the given cache object.
|
||||
* Any <code>FinalizationException</code>s that are thrown by the listeners will
|
||||
* be caught and logged.
|
||||
*/
|
||||
protected void finalizeListeners(Cache cache) {
|
||||
// It's possible for cache to be null if getCache() was never called (CACHE-63)
|
||||
if (cache == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object[] listeners = cache.listenerList.getListenerList();
|
||||
|
||||
for (int i = listeners.length - 2; i >= 0; i -= 2) {
|
||||
if (listeners[i + 1] instanceof LifecycleAware) {
|
||||
try {
|
||||
((LifecycleAware) listeners[i + 1]).finialize();
|
||||
} catch (FinalizationException e) {
|
||||
log.error("Listener could not be finalized", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the core cache parameters from the configuration properties.
|
||||
* The parameters that are initialized are:
|
||||
* <ul>
|
||||
* <li>the algorithm class ({@link #CACHE_ALGORITHM_KEY})</li>
|
||||
* <li>the cache size ({@link #CACHE_CAPACITY_KEY})</li>
|
||||
* <li>whether the cache is blocking or non-blocking ({@link #CACHE_BLOCKING_KEY})</li>
|
||||
* <li>whether caching to memory is enabled ({@link #CACHE_MEMORY_KEY})</li>
|
||||
* <li>whether the persistent cache is unlimited in size ({@link #CACHE_DISK_UNLIMITED_KEY})</li>
|
||||
* </ul>
|
||||
*/
|
||||
private void initCacheParameters() {
|
||||
algorithmClass = getProperty(CACHE_ALGORITHM_KEY);
|
||||
|
||||
blocking = "true".equalsIgnoreCase(getProperty(CACHE_BLOCKING_KEY));
|
||||
|
||||
String cacheMemoryStr = getProperty(CACHE_MEMORY_KEY);
|
||||
|
||||
if ((cacheMemoryStr != null) && cacheMemoryStr.equalsIgnoreCase("false")) {
|
||||
memoryCaching = false;
|
||||
}
|
||||
|
||||
unlimitedDiskCache = Boolean.valueOf(config.getProperty(CACHE_DISK_UNLIMITED_KEY)).booleanValue();
|
||||
overflowPersistence = Boolean.valueOf(config.getProperty(CACHE_PERSISTENCE_OVERFLOW_KEY)).booleanValue();
|
||||
|
||||
String cacheSize = getProperty(CACHE_CAPACITY_KEY);
|
||||
|
||||
try {
|
||||
if ((cacheSize != null) && (cacheSize.length() > 0)) {
|
||||
cacheCapacity = Integer.parseInt(cacheSize);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
log.error("The value supplied for the cache capacity, '" + cacheSize + "', is not a valid number. The cache capacity setting is being ignored.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the properties file from the classpath.
|
||||
*/
|
||||
private void loadProps(Properties p) {
|
||||
config = new Config(p);
|
||||
}
|
||||
}
|
1014
src/main/java/com/opensymphony/oscache/base/Cache.java
Normal file
1014
src/main/java/com/opensymphony/oscache/base/Cache.java
Normal file
File diff suppressed because it is too large
Load diff
311
src/main/java/com/opensymphony/oscache/base/CacheEntry.java
Normal file
311
src/main/java/com/opensymphony/oscache/base/CacheEntry.java
Normal file
|
@ -0,0 +1,311 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base;
|
||||
|
||||
import com.opensymphony.oscache.web.filter.ResponseContent;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A CacheEntry instance represents one entry in the cache. It holds the object that
|
||||
* is being cached, along with a host of information about that entry such as the
|
||||
* cache key, the time it was cached, whether the entry has been flushed or not and
|
||||
* the groups it belongs to.
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:mike@atlassian.com">Mike Cannon-Brookes</a>
|
||||
* @author <a href="mailto:tgochenour@peregrine.com">Todd Gochenour</a>
|
||||
* @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
|
||||
*/
|
||||
public class CacheEntry implements Serializable {
|
||||
/**
|
||||
* Default initialization value for the creation time and the last
|
||||
* update time. This is a placeholder that indicates the value has
|
||||
* not been set yet.
|
||||
*/
|
||||
private static final byte NOT_YET = -1;
|
||||
|
||||
/**
|
||||
* Specifying this as the refresh period for the
|
||||
* {@link #needsRefresh(int)} method will ensure
|
||||
* an entry does not become stale until it is
|
||||
* either explicitly flushed or a custom refresh
|
||||
* policy causes the entry to expire.
|
||||
*/
|
||||
public static final int INDEFINITE_EXPIRY = -1;
|
||||
|
||||
/**
|
||||
* The entry refresh policy object to use for this cache entry. This is optional.
|
||||
*/
|
||||
private EntryRefreshPolicy policy = null;
|
||||
|
||||
/**
|
||||
* The actual content that is being cached. Wherever possible this object
|
||||
* should be serializable. This allows <code>PersistenceListener</code>s
|
||||
* to serialize the cache entries to disk or database.
|
||||
*/
|
||||
private Object content = null;
|
||||
|
||||
/**
|
||||
* The set of cache groups that this cache entry belongs to, if any.
|
||||
*/
|
||||
private Set groups = null;
|
||||
|
||||
/**
|
||||
* The unique cache key for this entry
|
||||
*/
|
||||
private String key;
|
||||
|
||||
/**
|
||||
* <code>true</code> if this entry was flushed
|
||||
*/
|
||||
private boolean wasFlushed = false;
|
||||
|
||||
/**
|
||||
* The time this entry was created.
|
||||
*/
|
||||
private long created = NOT_YET;
|
||||
|
||||
/**
|
||||
* The time this emtry was last updated.
|
||||
*/
|
||||
private long lastUpdate = NOT_YET;
|
||||
|
||||
/**
|
||||
* Construct a new CacheEntry using the key provided.
|
||||
*
|
||||
* @param key The key of this CacheEntry
|
||||
*/
|
||||
public CacheEntry(String key) {
|
||||
this(key, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a CacheEntry.
|
||||
*
|
||||
* @param key The unique key for this <code>CacheEntry</code>.
|
||||
* @param policy Object that implements refresh policy logic. This parameter
|
||||
* is optional.
|
||||
*/
|
||||
public CacheEntry(String key, EntryRefreshPolicy policy) {
|
||||
this(key, policy, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a CacheEntry.
|
||||
*
|
||||
* @param key The unique key for this <code>CacheEntry</code>.
|
||||
* @param policy The object that implements the refresh policy logic. This
|
||||
* parameter is optional.
|
||||
* @param groups The groups that this <code>CacheEntry</code> belongs to. This
|
||||
* parameter is optional.
|
||||
*/
|
||||
public CacheEntry(String key, EntryRefreshPolicy policy, String[] groups) {
|
||||
this.key = key;
|
||||
|
||||
if (groups != null) {
|
||||
this.groups = new HashSet(groups.length);
|
||||
|
||||
for (int i = 0; i < groups.length; i++) {
|
||||
this.groups.add(groups[i]);
|
||||
}
|
||||
}
|
||||
|
||||
this.policy = policy;
|
||||
this.created = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the actual content that is being cached. Wherever possible this
|
||||
* object should be <code>Serializable</code>, however it is not an
|
||||
* absolute requirement when using a memory-only cache. Being <code>Serializable</code>
|
||||
* allows <code>PersistenceListener</code>s to serialize the cache entries to disk
|
||||
* or database.
|
||||
*
|
||||
* @param value The content to store in this CacheEntry.
|
||||
*/
|
||||
public synchronized void setContent(Object value) {
|
||||
content = value;
|
||||
lastUpdate = System.currentTimeMillis();
|
||||
wasFlushed = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cached content from this CacheEntry.
|
||||
*
|
||||
* @return The content of this CacheEntry.
|
||||
*/
|
||||
public Object getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the date this CacheEntry was created.
|
||||
*
|
||||
* @return The date this CacheEntry was created.
|
||||
*/
|
||||
public long getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cache groups for this entry.
|
||||
*
|
||||
* @param groups A string array containing all the group names
|
||||
*/
|
||||
public synchronized void setGroups(String[] groups) {
|
||||
if (groups != null) {
|
||||
this.groups = new HashSet(groups.length);
|
||||
|
||||
for (int i = 0; i < groups.length; i++) {
|
||||
this.groups.add(groups[i]);
|
||||
}
|
||||
} else {
|
||||
this.groups = null;
|
||||
}
|
||||
|
||||
lastUpdate = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cache groups for this entry
|
||||
*
|
||||
* @param groups A collection containing all the group names
|
||||
*/
|
||||
public synchronized void setGroups(Collection groups) {
|
||||
if (groups != null) {
|
||||
this.groups = new HashSet(groups);
|
||||
} else {
|
||||
this.groups = null;
|
||||
}
|
||||
|
||||
lastUpdate = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cache groups that this cache entry belongs to.
|
||||
* These returned groups should be treated as immuatable.
|
||||
*
|
||||
* @return A set containing the names of all the groups that
|
||||
* this cache entry belongs to.
|
||||
*/
|
||||
public Set getGroups() {
|
||||
return groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the key of this CacheEntry
|
||||
*
|
||||
* @return The key of this CacheEntry
|
||||
*/
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the date this CacheEntry was last updated.
|
||||
*
|
||||
* @param update The time (in milliseconds) this CacheEntry was last updated.
|
||||
*/
|
||||
public void setLastUpdate(long update) {
|
||||
lastUpdate = update;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the date this CacheEntry was last updated.
|
||||
*
|
||||
* @return The date this CacheEntry was last updated.
|
||||
*/
|
||||
public long getLastUpdate() {
|
||||
return lastUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this CacheEntry is a freshly created one and
|
||||
* has not yet been assigned content or placed in a cache.
|
||||
*
|
||||
* @return <code>true</code> if this entry is newly created
|
||||
*/
|
||||
public boolean isNew() {
|
||||
return lastUpdate == NOT_YET;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of the cache entry in bytes (roughly).<p>
|
||||
*
|
||||
* Currently this method only handles <code>String<code>s and
|
||||
* {@link ResponseContent} objects.
|
||||
*
|
||||
* @return The approximate size of the entry in bytes, or -1 if the
|
||||
* size could not be estimated.
|
||||
*/
|
||||
public int getSize() {
|
||||
// a char is two bytes
|
||||
int size = (key.length() * 2) + 4;
|
||||
|
||||
if (content.getClass() == String.class) {
|
||||
size += ((content.toString().length() * 2) + 4);
|
||||
} else if (content instanceof ResponseContent) {
|
||||
size += ((ResponseContent) content).getSize();
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
//add created, lastUpdate, and wasFlushed field sizes (1, 8, and 8)
|
||||
return size + 17;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush the entry from cache.
|
||||
* note that flushing the cache doesn't actually remove the cache contents
|
||||
* it just tells the CacheEntry that it needs a refresh next time it is asked
|
||||
* this is so that the content is still there for a <usecached />.
|
||||
*/
|
||||
public void flush() {
|
||||
wasFlushed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this CacheEntry needs to be refreshed.
|
||||
*
|
||||
* @param refreshPeriod The period of refresh (in seconds). Passing in
|
||||
* {@link #INDEFINITE_EXPIRY} will result in the content never becoming
|
||||
* stale unless it is explicitly flushed, or expired by a custom
|
||||
* {@link EntryRefreshPolicy}. Passing in 0 will always result in a
|
||||
* refresh being required.
|
||||
*
|
||||
* @return Whether or not this CacheEntry needs refreshing.
|
||||
*/
|
||||
public boolean needsRefresh(int refreshPeriod) {
|
||||
boolean needsRefresh;
|
||||
|
||||
// needs a refresh if it has never been updated
|
||||
if (lastUpdate == NOT_YET) {
|
||||
needsRefresh = true;
|
||||
}
|
||||
// Was it flushed from cache?
|
||||
else if (wasFlushed) {
|
||||
needsRefresh = true;
|
||||
} else if (refreshPeriod == 0) {
|
||||
needsRefresh = true;
|
||||
}
|
||||
// check what the policy has to say if there is one
|
||||
else if (policy != null) {
|
||||
needsRefresh = policy.needsRefresh(this);
|
||||
}
|
||||
// check if the last update + update period is in the past
|
||||
else if ((refreshPeriod >= 0) && (System.currentTimeMillis() >= (lastUpdate + (refreshPeriod * 1000L)))) {
|
||||
needsRefresh = true;
|
||||
} else {
|
||||
needsRefresh = false;
|
||||
}
|
||||
|
||||
return needsRefresh;
|
||||
}
|
||||
}
|
189
src/main/java/com/opensymphony/oscache/base/Config.java
Normal file
189
src/main/java/com/opensymphony/oscache/base/Config.java
Normal file
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2007 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Responsible for holding the Cache configuration properties. If the default
|
||||
* constructor is used, this class will load the properties from the
|
||||
* <code>cache.configuration</code>.
|
||||
*
|
||||
* @author <a href="mailto:fabian.crabus@gurulogic.de">Fabian Crabus</a>
|
||||
* @version $Revision$
|
||||
*/
|
||||
public class Config implements java.io.Serializable {
|
||||
|
||||
private static final transient Log log = LogFactory.getLog(Config.class);
|
||||
|
||||
/**
|
||||
* Name of the properties file.
|
||||
*/
|
||||
private final static String PROPERTIES_FILENAME = "/oscache.properties";
|
||||
|
||||
/**
|
||||
* Properties map to hold the cache configuration.
|
||||
*/
|
||||
private Properties properties = null;
|
||||
|
||||
/**
|
||||
* Create an OSCache Config that loads properties from oscache.properties.
|
||||
* The file must be present in the root of OSCache's classpath. If the file
|
||||
* cannot be loaded, an error will be logged and the configuration will
|
||||
* remain empty.
|
||||
*/
|
||||
public Config() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an OSCache configuration with the specified properties.
|
||||
* Note that it is the responsibility of the caller to provide valid
|
||||
* properties as no error checking is done to ensure that required
|
||||
* keys are present. If you're unsure of what keys should be present,
|
||||
* have a look at a sample oscache.properties file.
|
||||
*
|
||||
* @param p The properties to use for this configuration. If null,
|
||||
* then the default properties are loaded from the <code>oscache.properties</code>
|
||||
* file.
|
||||
*/
|
||||
public Config(Properties p) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("OSCache: Config called");
|
||||
}
|
||||
|
||||
if (p == null) {
|
||||
this.properties = loadProperties(PROPERTIES_FILENAME, "the default configuration");
|
||||
} else {
|
||||
this.properties = p;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the value of the named configuration property. If the property
|
||||
* cannot be found this method will return <code>null</code>.
|
||||
*
|
||||
* @param key The name of the property.
|
||||
* @return The property value, or <code>null</code> if the value could
|
||||
* not be found.
|
||||
*
|
||||
* @throws IllegalArgumentException if the supplied key is null.
|
||||
*/
|
||||
public String getProperty(String key) {
|
||||
if (key == null) {
|
||||
throw new IllegalArgumentException("key is null");
|
||||
}
|
||||
|
||||
if (properties == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return properties.getProperty(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all of the configuration properties. This property set
|
||||
* should be treated as immutable.
|
||||
*
|
||||
* @return The configuration properties.
|
||||
*/
|
||||
public Properties getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
public Object get(Object key) {
|
||||
return properties.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a configuration property.
|
||||
*
|
||||
* @param key The unique name for this property.
|
||||
* @param value The value assigned to this property.
|
||||
*
|
||||
* @throws IllegalArgumentException if the supplied key is null.
|
||||
*/
|
||||
public void set(Object key, Object value) {
|
||||
if (key == null) {
|
||||
throw new IllegalArgumentException("key is null");
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (properties == null) {
|
||||
properties = new Properties();
|
||||
}
|
||||
|
||||
properties.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the properties from the specified URL.
|
||||
* @param url a non null value of the URL to the properties
|
||||
* @param info additional logger information if the properties can't be read
|
||||
* @return the loaded properties specified by the URL
|
||||
* @since 2.4
|
||||
*/
|
||||
public static Properties loadProperties(URL url, String info) {
|
||||
log.info("OSCache: Getting properties from URL " + url + " for " + info);
|
||||
|
||||
Properties properties = new Properties();
|
||||
InputStream in = null;
|
||||
|
||||
try {
|
||||
in = url.openStream();
|
||||
properties.load(in);
|
||||
log.info("OSCache: Properties read " + properties);
|
||||
} catch (Exception e) {
|
||||
log.error("OSCache: Error reading from " + url, e);
|
||||
log.error("OSCache: Ensure the properties information in " + url+ " is readable and in your classpath.");
|
||||
} finally {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
log.warn("OSCache: IOException while closing InputStream: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the specified properties file from the classpath. If the file
|
||||
* cannot be found or loaded, an error will be logged and no
|
||||
* properties will be set.
|
||||
* @param filename the properties file with path
|
||||
* @param info additional logger information if file can't be read
|
||||
* @return the loaded properties specified by the filename
|
||||
* @since 2.4
|
||||
*/
|
||||
public static Properties loadProperties(String filename, String info) {
|
||||
URL url = null;
|
||||
|
||||
ClassLoader threadContextClassLoader = Thread.currentThread().getContextClassLoader();
|
||||
if (threadContextClassLoader != null) {
|
||||
url = threadContextClassLoader.getResource(filename);
|
||||
}
|
||||
if (url == null) {
|
||||
url = Config.class.getResource(filename);
|
||||
if (url == null) {
|
||||
log.warn("OSCache: No properties file found in the classpath by filename " + filename);
|
||||
return new Properties();
|
||||
}
|
||||
}
|
||||
|
||||
return loadProperties(url, info);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Interface that allows custom code to be called when checking to see if a cache entry
|
||||
* has expired. This is useful when the rules that determine when content needs refreshing
|
||||
* are beyond the base funtionality offered by OSCache.
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
|
||||
*/
|
||||
public interface EntryRefreshPolicy extends Serializable {
|
||||
/**
|
||||
* Indicates whether the supplied <code>CacheEntry</code> needs to be refreshed.
|
||||
* This will be called when retrieving an entry from the cache - if this method
|
||||
* returns <code>true</code> then a <code>NeedsRefreshException</code> will be
|
||||
* thrown.
|
||||
*
|
||||
* @param entry The cache entry that is being tested.
|
||||
* @return <code>true</code> if the content needs refreshing, <code>false</code> otherwise.
|
||||
*
|
||||
* @see NeedsRefreshException
|
||||
* @see CacheEntry
|
||||
*/
|
||||
public boolean needsRefresh(CacheEntry entry);
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base;
|
||||
|
||||
|
||||
/**
|
||||
* Holds the state of a Cache Entry that is in the process of being (re)generated.
|
||||
* This is not synchronized; the synchronization must be handled by the calling
|
||||
* classes.
|
||||
*
|
||||
* @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
|
||||
* @author Author: $
|
||||
* @version Revision: $
|
||||
*/
|
||||
public class EntryUpdateState {
|
||||
/**
|
||||
* The initial state when this object is first created
|
||||
*/
|
||||
public static final int NOT_YET_UPDATING = -1;
|
||||
|
||||
/**
|
||||
* Update in progress state
|
||||
*/
|
||||
public static final int UPDATE_IN_PROGRESS = 0;
|
||||
|
||||
/**
|
||||
* Update complete state
|
||||
*/
|
||||
public static final int UPDATE_COMPLETE = 1;
|
||||
|
||||
/**
|
||||
* Update cancelled state
|
||||
*/
|
||||
public static final int UPDATE_CANCELLED = 2;
|
||||
|
||||
/**
|
||||
* Current update state
|
||||
*/
|
||||
int state = NOT_YET_UPDATING;
|
||||
|
||||
/**
|
||||
* A counter of the number of threads that are coordinated through this instance. When this counter gets to zero, then the reference to this
|
||||
* instance may be released from the Cache instance.
|
||||
* This is counter is protected by the EntryStateUpdate instance monitor.
|
||||
*/
|
||||
private int nbConcurrentUses = 1;
|
||||
|
||||
/**
|
||||
* This is the initial state when an instance this object is first created.
|
||||
* It indicates that a cache entry needs updating, but no thread has claimed
|
||||
* responsibility for updating it yet.
|
||||
*/
|
||||
public boolean isAwaitingUpdate() {
|
||||
return state == NOT_YET_UPDATING;
|
||||
}
|
||||
|
||||
/**
|
||||
* The thread that was responsible for updating the cache entry (ie, the thread
|
||||
* that managed to grab the update lock) has decided to give up responsibility
|
||||
* for performing the update. OSCache will notify any other threads that are
|
||||
* waiting on the update so one of them can take over the responsibility.
|
||||
*/
|
||||
public boolean isCancelled() {
|
||||
return state == UPDATE_CANCELLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* The update of the cache entry has been completed.
|
||||
*/
|
||||
public boolean isComplete() {
|
||||
return state == UPDATE_COMPLETE;
|
||||
}
|
||||
|
||||
/**
|
||||
* The cache entry is currently being generated by the thread that got hold of
|
||||
* the update lock.
|
||||
*/
|
||||
public boolean isUpdating() {
|
||||
return state == UPDATE_IN_PROGRESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the state to <code>UPDATE_CANCELLED</code>. This should <em>only<em>
|
||||
* be called by the thread that managed to get the update lock.
|
||||
* @return the counter value after the operation completed
|
||||
*/
|
||||
public int cancelUpdate() {
|
||||
if (state != UPDATE_IN_PROGRESS) {
|
||||
throw new IllegalStateException("Cannot cancel cache update - current state (" + state + ") is not UPDATE_IN_PROGRESS");
|
||||
}
|
||||
|
||||
state = UPDATE_CANCELLED;
|
||||
return decrementUsageCounter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the state to <code>UPDATE_COMPLETE</code>. This should <em>only</em>
|
||||
* be called by the thread that managed to get the update lock.
|
||||
* @return the counter value after the operation completed
|
||||
*/
|
||||
public int completeUpdate() {
|
||||
if (state != UPDATE_IN_PROGRESS) {
|
||||
throw new IllegalStateException("Cannot complete cache update - current state (" + state + ") is not UPDATE_IN_PROGRESS");
|
||||
}
|
||||
|
||||
state = UPDATE_COMPLETE;
|
||||
return decrementUsageCounter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to change the state to <code>UPDATE_IN_PROGRESS</code>. Calls
|
||||
* to this method must be synchronized on the EntryUpdateState instance.
|
||||
* @return the counter value after the operation completed
|
||||
*/
|
||||
public int startUpdate() {
|
||||
if ((state != NOT_YET_UPDATING) && (state != UPDATE_CANCELLED)) {
|
||||
throw new IllegalStateException("Cannot begin cache update - current state (" + state + ") is not NOT_YET_UPDATING or UPDATE_CANCELLED");
|
||||
}
|
||||
|
||||
state = UPDATE_IN_PROGRESS;
|
||||
return incrementUsageCounter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the usage counter by one
|
||||
* @return the counter value after the increment
|
||||
*/
|
||||
public synchronized int incrementUsageCounter() {
|
||||
nbConcurrentUses++;
|
||||
return nbConcurrentUses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current usage counter value
|
||||
* @return a positive number.
|
||||
*/
|
||||
public synchronized int getUsageCounter() {
|
||||
return nbConcurrentUses;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decrements the usage counter by one. This method may only be called when the usage number is greater than zero
|
||||
* @return the counter value after the decrement
|
||||
*/
|
||||
public synchronized int decrementUsageCounter() {
|
||||
if (nbConcurrentUses <=0) {
|
||||
throw new IllegalStateException("Cannot decrement usage counter, it is already equals to [" + nbConcurrentUses + "]");
|
||||
}
|
||||
nbConcurrentUses--;
|
||||
return nbConcurrentUses;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base;
|
||||
|
||||
|
||||
/**
|
||||
* Thrown by {@link LifecycleAware} listeners that are not able to finalize
|
||||
* themselves.
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
|
||||
*/
|
||||
public class FinalizationException extends Exception {
|
||||
public FinalizationException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public FinalizationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base;
|
||||
|
||||
|
||||
/**
|
||||
* Thrown by {@link LifecycleAware} listeners that are not able to initialize
|
||||
* themselves.
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
|
||||
*/
|
||||
public class InitializationException extends Exception {
|
||||
public InitializationException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public InitializationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base;
|
||||
|
||||
|
||||
/**
|
||||
* Event handlers implement this so they can be notified when a cache
|
||||
* is created and also when it is destroyed. This allows event handlers
|
||||
* to load any configuration and/or resources they need on startup and
|
||||
* then release them again when the cache is shut down.
|
||||
*
|
||||
* @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
|
||||
*
|
||||
* @see com.opensymphony.oscache.base.events.CacheEventListener
|
||||
*/
|
||||
public interface LifecycleAware {
|
||||
/**
|
||||
* Called by the cache administrator class when a cache is instantiated.
|
||||
*
|
||||
* @param cache the cache instance that this listener is attached to.
|
||||
* @param config The cache's configuration details. This allows the event handler
|
||||
* to initialize itself based on the cache settings, and also to receive <em>additional</em>
|
||||
* settings that were part of the cache configuration but that the cache
|
||||
* itself does not care about. If you are using <code>cache.properties</code>
|
||||
* for your configuration, simply add any additional properties that your event
|
||||
* handler requires and they will be passed through in this parameter.
|
||||
*
|
||||
* @throws InitializationException thrown when there was a problem initializing the
|
||||
* listener. The cache administrator will log this error and disable the listener.
|
||||
*/
|
||||
public void initialize(Cache cache, Config config) throws InitializationException;
|
||||
|
||||
/**
|
||||
* Called by the cache administrator class when a cache is destroyed.
|
||||
*
|
||||
* @throws FinalizationException thrown when there was a problem finalizing the
|
||||
* listener. The cache administrator will catch and log this error.
|
||||
*/
|
||||
public void finialize() throws FinalizationException;
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2007 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base;
|
||||
|
||||
/**
|
||||
* This exception is thrown when retrieving an item from cache and it is
|
||||
* expired.
|
||||
* Note that for fault tolerance purposes, it is possible to retrieve the
|
||||
* current cached object from the exception.
|
||||
*
|
||||
* <p>January, 2004 - The OSCache developers are aware of the fact that throwing
|
||||
* an exception for a perfect valid situation (cache miss) is design smell. This will
|
||||
* be removed in the near future, and other means of refreshing the cache will be
|
||||
* provided.</p>
|
||||
*
|
||||
* @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
|
||||
* @version $Revision$
|
||||
*/
|
||||
public final class NeedsRefreshException extends Exception {
|
||||
|
||||
/**
|
||||
* Current object in the cache
|
||||
*/
|
||||
private Object cacheContent = null;
|
||||
|
||||
/**
|
||||
* Create a NeedsRefreshException
|
||||
*/
|
||||
public NeedsRefreshException(String message, Object cacheContent) {
|
||||
super(message);
|
||||
this.cacheContent = cacheContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a NeedsRefreshException
|
||||
*/
|
||||
public NeedsRefreshException(Object cacheContent) {
|
||||
super();
|
||||
this.cacheContent = cacheContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve current object in the cache
|
||||
*/
|
||||
public Object getCacheContent() {
|
||||
return cacheContent;
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base.algorithm;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* FIFO (First In First Out) based queue algorithm for the cache.
|
||||
*
|
||||
* No synchronization is required in this class since the
|
||||
* <code>AbstractConcurrentReadCache</code> already takes care of any
|
||||
* synchronization requirements.
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:mike@atlassian.com">Mike Cannon-Brookes</a>
|
||||
* @author <a href="mailto:abergevin@pyxis-tech.com">Alain Bergevin</a>
|
||||
* @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
|
||||
*/
|
||||
public class FIFOCache extends AbstractConcurrentReadCache {
|
||||
|
||||
private static final long serialVersionUID = -10333778645392679L;
|
||||
|
||||
/**
|
||||
* A queue containing all cache keys
|
||||
*/
|
||||
private Collection list = new LinkedHashSet();
|
||||
|
||||
/**
|
||||
* Constructs a FIFO Cache.
|
||||
*/
|
||||
public FIFOCache() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a FIFO Cache of the specified capacity.
|
||||
*
|
||||
* @param capacity The maximum cache capacity.
|
||||
*/
|
||||
public FIFOCache(int capacity) {
|
||||
this();
|
||||
maxEntries = capacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* An object was retrieved from the cache. This implementation
|
||||
* does noting since this event has no impact on the FIFO algorithm.
|
||||
*
|
||||
* @param key The cache key of the item that was retrieved.
|
||||
*/
|
||||
protected void itemRetrieved(Object key) {
|
||||
}
|
||||
|
||||
/**
|
||||
* An object was put in the cache. This implementation just adds
|
||||
* the key to the end of the list if it doesn't exist in the list
|
||||
* already.
|
||||
*
|
||||
* @param key The cache key of the item that was put.
|
||||
*/
|
||||
protected void itemPut(Object key) {
|
||||
if (!list.contains(key)) {
|
||||
list.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An item needs to be removed from the cache. The FIFO implementation
|
||||
* removes the first element in the list (ie, the item that has been in
|
||||
* the cache for the longest time).
|
||||
*
|
||||
* @return The key of whichever item was removed.
|
||||
*/
|
||||
protected Object removeItem() {
|
||||
Iterator it = list.iterator();
|
||||
Object toRemove = it.next();
|
||||
it.remove();
|
||||
|
||||
return toRemove;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove specified key since that object has been removed from the cache.
|
||||
*
|
||||
* @param key The cache key of the item that was removed.
|
||||
*/
|
||||
protected void itemRemoved(Object key) {
|
||||
list.remove(key);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base.algorithm;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* <p>LRU (Least Recently Used) algorithm for the cache.</p>
|
||||
*
|
||||
* <p>Since release 2.3 this class requires Java 1.4
|
||||
* to use the <code>LinkedHashSet</code>. Use prior OSCache release which
|
||||
* require the Jakarta commons-collections <code>SequencedHashMap</code>
|
||||
* class or the <code>LinkedList</code> class if neither of the above
|
||||
* classes are available.</p>
|
||||
*
|
||||
* <p>No synchronization is required in this class since the
|
||||
* <code>AbstractConcurrentReadCache</code> already takes care of any
|
||||
* synchronization requirements.</p>
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:salaman@teknos.com">Victor Salaman</a>
|
||||
* @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
|
||||
* @author <a href="mailto:abergevin@pyxis-tech.com">Alain Bergevin</a>
|
||||
* @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
|
||||
*/
|
||||
public class LRUCache extends AbstractConcurrentReadCache {
|
||||
|
||||
private static final long serialVersionUID = -7379608101794788534L;
|
||||
|
||||
/**
|
||||
* Cache queue containing all cache keys.
|
||||
*/
|
||||
private Collection list = new LinkedHashSet();
|
||||
|
||||
/**
|
||||
* A flag indicating whether there is a removal operation in progress.
|
||||
*/
|
||||
private volatile boolean removeInProgress = false;
|
||||
|
||||
/**
|
||||
* Constructs an LRU Cache.
|
||||
*/
|
||||
public LRUCache() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructors a LRU Cache of the specified capacity.
|
||||
*
|
||||
* @param capacity The maximum cache capacity.
|
||||
*/
|
||||
public LRUCache(int capacity) {
|
||||
this();
|
||||
maxEntries = capacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* An item was retrieved from the list. The LRU implementation moves
|
||||
* the retrieved item's key to the front of the list.
|
||||
*
|
||||
* @param key The cache key of the item that was retrieved.
|
||||
*/
|
||||
protected void itemRetrieved(Object key) {
|
||||
// Prevent list operations during remove
|
||||
while (removeInProgress) {
|
||||
try {
|
||||
Thread.sleep(5);
|
||||
} catch (InterruptedException ie) {
|
||||
}
|
||||
}
|
||||
|
||||
// We need to synchronize here because AbstractConcurrentReadCache
|
||||
// doesn't prevent multiple threads from calling this method simultaneously.
|
||||
synchronized (list) {
|
||||
list.remove(key);
|
||||
list.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An object was put in the cache. This implementation adds/moves the
|
||||
* key to the end of the list.
|
||||
*
|
||||
* @param key The cache key of the item that was put.
|
||||
*/
|
||||
protected void itemPut(Object key) {
|
||||
// Since this entry was just accessed, move it to the back of the list.
|
||||
synchronized (list) { // A further fix for CACHE-44
|
||||
list.remove(key);
|
||||
list.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An item needs to be removed from the cache. The LRU implementation
|
||||
* removes the first element in the list (ie, the item that was least-recently
|
||||
* accessed).
|
||||
*
|
||||
* @return The key of whichever item was removed.
|
||||
*/
|
||||
protected Object removeItem() {
|
||||
Object toRemove = null;
|
||||
|
||||
removeInProgress = true;
|
||||
try {
|
||||
while (toRemove == null) {
|
||||
try {
|
||||
toRemove = removeFirst();
|
||||
} catch (Exception e) {
|
||||
// List is empty.
|
||||
// this is theorically possible if we have more than the size concurrent
|
||||
// thread in getItem. Remove completed but add not done yet.
|
||||
// We simply wait for add to complete.
|
||||
do {
|
||||
try {
|
||||
Thread.sleep(5);
|
||||
} catch (InterruptedException ie) {
|
||||
}
|
||||
} while (list.isEmpty());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
removeInProgress = false;
|
||||
}
|
||||
|
||||
return toRemove;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove specified key since that object has been removed from the cache.
|
||||
*
|
||||
* @param key The cache key of the item that was removed.
|
||||
*/
|
||||
protected void itemRemoved(Object key) {
|
||||
list.remove(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the first object from the list of keys.
|
||||
*
|
||||
* @return the object that was removed
|
||||
*/
|
||||
private Object removeFirst() {
|
||||
Object toRemove = null;
|
||||
|
||||
synchronized (list) { // A further fix for CACHE-44 and CACHE-246
|
||||
Iterator it = list.iterator();
|
||||
toRemove = it.next();
|
||||
it.remove();
|
||||
}
|
||||
|
||||
return toRemove;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2006 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base.algorithm;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
|
||||
/**
|
||||
* A simple unlimited cache that has no upper bound to the number of
|
||||
* cache entries it can contain.
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
|
||||
* @author <a href="mailto:abergevin@pyxis-tech.com">Alain Bergevin</a>
|
||||
*/
|
||||
public final class UnlimitedCache extends AbstractConcurrentReadCache {
|
||||
|
||||
private static transient final long serialVersionUID = 7615611393249532285L;
|
||||
|
||||
private final Log log = LogFactory.getLog(this.getClass());
|
||||
|
||||
/**
|
||||
* Creates an unlimited cache by calling the super class's constructor
|
||||
* with an <code>UNLIMITED</code> maximum number of entries.
|
||||
*/
|
||||
public UnlimitedCache() {
|
||||
super();
|
||||
maxEntries = UNLIMITED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the <code>setMaxEntries</code> with an empty implementation.
|
||||
* This property cannot be modified and is ignored for an
|
||||
* <code>UnlimitedCache</code>.
|
||||
*/
|
||||
public void setMaxEntries(int maxEntries) {
|
||||
log.warn("Cache max entries can't be set in " + this.getClass().getName() + ", ignoring value " + maxEntries + ".");
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements <code>itemRetrieved</code> with an empty implementation.
|
||||
* The unlimited cache doesn't care that an item was retrieved.
|
||||
*/
|
||||
protected void itemRetrieved(Object key) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements <code>itemPut</code> with an empty implementation.
|
||||
* The unlimited cache doesn't care that an item was put in the cache.
|
||||
*/
|
||||
protected void itemPut(Object key) {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method just returns <code>null</code> since items should
|
||||
* never end up being removed from an unlimited cache!
|
||||
*/
|
||||
protected Object removeItem() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* An empty implementation. The unlimited cache doesn't care that an
|
||||
* item was removed.
|
||||
*/
|
||||
protected void itemRemoved(Object key) {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
@(#)package.html
|
||||
|
||||
Copyright (c) 2002-2003 by OpenSymphony
|
||||
All rights reserved.
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Provides the classes that implement the caching algorithms used by OSCache, all of
|
||||
which are based on a derivative of Doug Lea's <code>ConcurrentReaderHashMap</code>.
|
||||
|
||||
|
||||
<h2>Package Specification</h2>
|
||||
|
||||
<h2>Related Documentation</h2>
|
||||
|
||||
|
||||
For overviews, tutorials, examples, guides, and tool documentation, please see:
|
||||
<ul>
|
||||
<li><a href="http://www.opensymphony.com/oscache">The OSCache Homepage</a>
|
||||
</ul>
|
||||
|
||||
For further information on Doug Lea's concurrency package, please see:
|
||||
<ul>
|
||||
<li><a href="http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html">http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html</a>
|
||||
</ul>
|
||||
|
||||
<!-- Put @see and @since tags down here. -->
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base.events;
|
||||
|
||||
import com.opensymphony.oscache.base.Cache;
|
||||
import com.opensymphony.oscache.base.CacheEntry;
|
||||
|
||||
/**
|
||||
* CacheEntryEvent is the object created when an event occurs on a
|
||||
* cache entry (Add, update, remove, flush). It contains the entry itself and
|
||||
* its map.
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
|
||||
*/
|
||||
public final class CacheEntryEvent extends CacheEvent {
|
||||
/**
|
||||
* The cache where the entry resides.
|
||||
*/
|
||||
private Cache map = null;
|
||||
|
||||
/**
|
||||
* The entry that the event applies to.
|
||||
*/
|
||||
private CacheEntry entry = null;
|
||||
|
||||
/**
|
||||
* Constructs a cache entry event object with no specified origin
|
||||
*
|
||||
* @param map The cache map of the cache entry
|
||||
* @param entry The cache entry that the event applies to
|
||||
*/
|
||||
public CacheEntryEvent(Cache map, CacheEntry entry) {
|
||||
this(map, entry, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a cache entry event object
|
||||
*
|
||||
* @param map The cache map of the cache entry
|
||||
* @param entry The cache entry that the event applies to
|
||||
* @param origin The origin of this event
|
||||
*/
|
||||
public CacheEntryEvent(Cache map, CacheEntry entry, String origin) {
|
||||
super(origin);
|
||||
this.map = map;
|
||||
this.entry = entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the cache entry that the event applies to.
|
||||
*/
|
||||
public CacheEntry getEntry() {
|
||||
return entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the cache entry key
|
||||
*/
|
||||
public String getKey() {
|
||||
return entry.getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the cache map where the entry resides.
|
||||
*/
|
||||
public Cache getMap() {
|
||||
return map;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "key=" + entry.getKey();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base.events;
|
||||
|
||||
|
||||
/**
|
||||
* This is the interface to listen to cache entry events. There is a method
|
||||
* for each event type. These methods are called via a dispatcher. If you
|
||||
* want to be notified when an event occurs on an entry, group or across a
|
||||
* pattern, register a listener and implement this interface.
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
|
||||
*/
|
||||
public interface CacheEntryEventListener extends CacheEventListener {
|
||||
/**
|
||||
* Event fired when an entry is added to the cache.
|
||||
*/
|
||||
void cacheEntryAdded(CacheEntryEvent event);
|
||||
|
||||
/**
|
||||
* Event fired when an entry is flushed from the cache.
|
||||
*/
|
||||
void cacheEntryFlushed(CacheEntryEvent event);
|
||||
|
||||
/**
|
||||
* Event fired when an entry is removed from the cache.
|
||||
*/
|
||||
void cacheEntryRemoved(CacheEntryEvent event);
|
||||
|
||||
/**
|
||||
* Event fired when an entry is updated in the cache.
|
||||
*/
|
||||
void cacheEntryUpdated(CacheEntryEvent event);
|
||||
|
||||
/**
|
||||
* Event fired when a group is flushed from the cache.
|
||||
*/
|
||||
void cacheGroupFlushed(CacheGroupEvent event);
|
||||
|
||||
/**
|
||||
* Event fired when a key pattern is flushed from the cache.
|
||||
* Note that this event will <em>not</em> be fired if the pattern
|
||||
* is <code>null</code> or an empty string, instead the flush
|
||||
* request will silently be ignored.
|
||||
*/
|
||||
void cachePatternFlushed(CachePatternEvent event);
|
||||
|
||||
/**
|
||||
* An event that is fired when an entire cache gets flushed.
|
||||
*/
|
||||
void cacheFlushed(CachewideEvent event);
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base.events;
|
||||
|
||||
|
||||
/**
|
||||
* This is all the possible events that may occur on a cache entry or
|
||||
* collection of cache entries.<p>
|
||||
* There is a corresponding interface {@link CacheEntryEventListener} for
|
||||
* handling these events.
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
|
||||
*/
|
||||
public final class CacheEntryEventType {
|
||||
/**
|
||||
* Get an event type for an entry added.
|
||||
*/
|
||||
public static final CacheEntryEventType ENTRY_ADDED = new CacheEntryEventType();
|
||||
|
||||
/**
|
||||
* Get an event type for an entry updated.
|
||||
*/
|
||||
public static final CacheEntryEventType ENTRY_UPDATED = new CacheEntryEventType();
|
||||
|
||||
/**
|
||||
* Get an event type for an entry flushed.
|
||||
*/
|
||||
public static final CacheEntryEventType ENTRY_FLUSHED = new CacheEntryEventType();
|
||||
|
||||
/**
|
||||
* Get an event type for an entry removed.
|
||||
*/
|
||||
public static final CacheEntryEventType ENTRY_REMOVED = new CacheEntryEventType();
|
||||
|
||||
/**
|
||||
* Get an event type for a group flush event.
|
||||
*/
|
||||
public static final CacheEntryEventType GROUP_FLUSHED = new CacheEntryEventType();
|
||||
|
||||
/**
|
||||
* Get an event type for a pattern flush event.
|
||||
*/
|
||||
public static final CacheEntryEventType PATTERN_FLUSHED = new CacheEntryEventType();
|
||||
|
||||
/**
|
||||
* Private constructor to ensure that no object of that type are
|
||||
* created externally.
|
||||
*/
|
||||
private CacheEntryEventType() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base.events;
|
||||
|
||||
|
||||
/**
|
||||
* The root event class for all cache events. Each subclasses of this class
|
||||
* classifies a particular type of cache event.
|
||||
*
|
||||
* @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
|
||||
* Date: 20-May-2003
|
||||
* Time: 15:25:02
|
||||
*/
|
||||
public abstract class CacheEvent {
|
||||
/**
|
||||
* An optional tag that can be attached to the event to specify the event's origin.
|
||||
*/
|
||||
protected String origin = null;
|
||||
|
||||
/**
|
||||
* No-argument constructor so subtypes can easily implement <code>Serializable</code>
|
||||
*/
|
||||
public CacheEvent() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a cache event object that came from the specified origin.
|
||||
*
|
||||
* @param origin A string that indicates where this event was fired from.
|
||||
* This value is optional; <code>null</code> can be passed in if an
|
||||
* origin is not required.
|
||||
*/
|
||||
public CacheEvent(String origin) {
|
||||
this.origin = origin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the origin of this event, if one was specified. This is most
|
||||
* useful when an event handler causes another event to fire - by checking
|
||||
* the origin the handler is able to prevent recursive events being
|
||||
* fired.
|
||||
*/
|
||||
public String getOrigin() {
|
||||
return origin;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base.events;
|
||||
|
||||
import java.util.EventListener;
|
||||
|
||||
/**
|
||||
* This is the base interface for cache events.
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
|
||||
*/
|
||||
public interface CacheEventListener extends EventListener {
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base.events;
|
||||
|
||||
import com.opensymphony.oscache.base.Cache;
|
||||
|
||||
/**
|
||||
* CacheGroupEvent is an event that occurs at the cache group level
|
||||
* (Add, update, remove, flush). It contains the group name and the
|
||||
* originating cache object.
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
|
||||
*/
|
||||
public final class CacheGroupEvent extends CacheEvent {
|
||||
/**
|
||||
* The cache where the entry resides.
|
||||
*/
|
||||
private Cache map = null;
|
||||
|
||||
/**
|
||||
* The group that the event applies to.
|
||||
*/
|
||||
private String group = null;
|
||||
|
||||
/**
|
||||
* Constructs a cache group event with no origin
|
||||
*
|
||||
* @param map The cache map of the cache entry
|
||||
* @param group The cache group that the event applies to.
|
||||
*/
|
||||
public CacheGroupEvent(Cache map, String group) {
|
||||
this(map, group, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a cache group event
|
||||
*
|
||||
* @param map The cache map of the cache entry
|
||||
* @param group The cache group that the event applies to.
|
||||
* @param origin An optional tag that can be attached to the event to
|
||||
* specify the event's origin. This is useful to prevent events from being
|
||||
* fired recursively in some situations, such as when an event handler
|
||||
* causes another event to be fired.
|
||||
*/
|
||||
public CacheGroupEvent(Cache map, String group, String origin) {
|
||||
super(origin);
|
||||
this.map = map;
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the cache group that the event applies to.
|
||||
*/
|
||||
public String getGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the cache map where the group resides.
|
||||
*/
|
||||
public Cache getMap() {
|
||||
return map;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "groupName=" + group;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base.events;
|
||||
|
||||
import com.opensymphony.oscache.base.CacheEntry;
|
||||
|
||||
/**
|
||||
* Cache map access event. This is the object created when an event occurs on a
|
||||
* cache map (cache Hit, cache miss). It contains the entry that was referenced
|
||||
* by the event and the event type.
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
|
||||
*/
|
||||
public final class CacheMapAccessEvent extends CacheEvent {
|
||||
/**
|
||||
* The cache entry that the event applies to.
|
||||
*/
|
||||
private CacheEntry entry = null;
|
||||
|
||||
/**
|
||||
* Type of the event.
|
||||
*/
|
||||
private CacheMapAccessEventType eventType = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* <p>
|
||||
* @param eventType Type of the event.
|
||||
* @param entry The cache entry that the event applies to.
|
||||
*/
|
||||
public CacheMapAccessEvent(CacheMapAccessEventType eventType, CacheEntry entry) {
|
||||
this(eventType, entry, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* <p>
|
||||
* @param eventType Type of the event.
|
||||
* @param entry The cache entry that the event applies to.
|
||||
* @param origin The origin of the event
|
||||
*/
|
||||
public CacheMapAccessEvent(CacheMapAccessEventType eventType, CacheEntry entry, String origin) {
|
||||
super(origin);
|
||||
this.eventType = eventType;
|
||||
this.entry = entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the cache entry that the event applies to.
|
||||
*/
|
||||
public CacheEntry getCacheEntry() {
|
||||
return entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the cache entry key that the event applies to.
|
||||
*/
|
||||
public String getCacheEntryKey() {
|
||||
return entry.getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the type of the event.
|
||||
*/
|
||||
public CacheMapAccessEventType getEventType() {
|
||||
return eventType;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base.events;
|
||||
|
||||
|
||||
/**
|
||||
* This is the interface to listen to cache map access events. The events are
|
||||
* cache hits and misses, and are dispatched through this interface
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
|
||||
*/
|
||||
public interface CacheMapAccessEventListener extends CacheEventListener {
|
||||
/**
|
||||
* Event fired when an entry is accessed.
|
||||
* Use getEventType to differentiate between access events.
|
||||
*/
|
||||
public void accessed(CacheMapAccessEvent event);
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base.events;
|
||||
|
||||
|
||||
/**
|
||||
* This is an enumeration of the cache events that represent the
|
||||
* various outcomes of cache accesses.
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
|
||||
*/
|
||||
public final class CacheMapAccessEventType {
|
||||
/**
|
||||
* Get an event type for a cache hit.
|
||||
*/
|
||||
public static final CacheMapAccessEventType HIT = new CacheMapAccessEventType();
|
||||
|
||||
/**
|
||||
* Get an event type for a cache miss.
|
||||
*/
|
||||
public static final CacheMapAccessEventType MISS = new CacheMapAccessEventType();
|
||||
|
||||
/**
|
||||
* Get an event type for when the data was found in the cache but was stale.
|
||||
*/
|
||||
public static final CacheMapAccessEventType STALE_HIT = new CacheMapAccessEventType();
|
||||
|
||||
/**
|
||||
* Private constructor to ensure that no object of this type are
|
||||
* created externally.
|
||||
*/
|
||||
private CacheMapAccessEventType() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base.events;
|
||||
|
||||
import com.opensymphony.oscache.base.Cache;
|
||||
|
||||
/**
|
||||
* A CachePatternEvent is fired when a pattern has been applied to a cache.
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
|
||||
*/
|
||||
public final class CachePatternEvent extends CacheEvent {
|
||||
/**
|
||||
* The cache the pattern is being applied to.
|
||||
*/
|
||||
private Cache map = null;
|
||||
|
||||
/**
|
||||
* The pattern that is being applied.
|
||||
*/
|
||||
private String pattern = null;
|
||||
|
||||
/**
|
||||
* Constructs a cache pattern event with no origin
|
||||
*
|
||||
* @param map The cache map that the pattern was applied to
|
||||
* @param pattern The pattern that was applied
|
||||
*/
|
||||
public CachePatternEvent(Cache map, String pattern) {
|
||||
this(map, pattern, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a cache pattern event
|
||||
*
|
||||
* @param map The cache map that the pattern was applied to
|
||||
* @param pattern The cache pattern that the event applies to.
|
||||
* @param origin An optional tag that can be attached to the event to
|
||||
* specify the event's origin. This is useful to prevent events from being
|
||||
* fired recursively in some situations, such as when an event handler
|
||||
* causes another event to be fired, or for logging purposes.
|
||||
*/
|
||||
public CachePatternEvent(Cache map, String pattern, String origin) {
|
||||
super(origin);
|
||||
this.map = map;
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the cache map that had the pattern applied.
|
||||
*/
|
||||
public Cache getMap() {
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the pattern that was applied to the cache.
|
||||
*/
|
||||
public String getPattern() {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "pattern=" + pattern;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base.events;
|
||||
|
||||
import com.opensymphony.oscache.base.Cache;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* A <code>CachewideEvent<code> represents and event that occurs on
|
||||
* the the entire cache, eg a cache flush or clear.
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
|
||||
*/
|
||||
public final class CachewideEvent extends CacheEvent {
|
||||
/**
|
||||
* The cache where the event occurred.
|
||||
*/
|
||||
private Cache cache = null;
|
||||
|
||||
/**
|
||||
* The date/time for when the flush is scheduled
|
||||
*/
|
||||
private Date date = null;
|
||||
|
||||
/**
|
||||
* Constructs a cachewide event with the specified origin.
|
||||
*
|
||||
* @param cache The cache map that the event occurred on.
|
||||
* @param date The date/time that this cachewide event is scheduled for
|
||||
* (eg, the date that the cache is to be flushed).
|
||||
* @param origin An optional tag that can be attached to the event to
|
||||
* specify the event's origin. This is useful to prevent events from being
|
||||
* fired recursively in some situations, such as when an event handler
|
||||
* causes another event to be fired.
|
||||
*/
|
||||
public CachewideEvent(Cache cache, Date date, String origin) {
|
||||
super(origin);
|
||||
this.date = date;
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the cache map that the event occurred on.
|
||||
*/
|
||||
public Cache getCache() {
|
||||
return cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the date/time that the cache flush is scheduled for.
|
||||
*/
|
||||
public Date getDate() {
|
||||
return date;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base.events;
|
||||
|
||||
|
||||
/**
|
||||
* This is an enumeration holding all the events that can
|
||||
* occur at the cache-wide level.
|
||||
*
|
||||
* @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
|
||||
*/
|
||||
public class CachewideEventType {
|
||||
/**
|
||||
* Get an event type for a cache flush event.
|
||||
*/
|
||||
public static final CachewideEventType CACHE_FLUSHED = new CachewideEventType();
|
||||
|
||||
/**
|
||||
* Private constructor to ensure that no object of this type are
|
||||
* created externally.
|
||||
*/
|
||||
private CachewideEventType() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base.events;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* A <code>ScopeEvent</code> is created when an event occurs across one or all scopes.
|
||||
* This type of event is only applicable to the <code>ServletCacheAdministrator</code>.
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
|
||||
*/
|
||||
public final class ScopeEvent extends CacheEvent {
|
||||
/**
|
||||
* Date that the event applies to.
|
||||
*/
|
||||
private Date date = null;
|
||||
|
||||
/**
|
||||
* Type of the event.
|
||||
*/
|
||||
private ScopeEventType eventType = null;
|
||||
|
||||
/**
|
||||
* Scope that applies to this event.
|
||||
*/
|
||||
private int scope = 0;
|
||||
|
||||
/**
|
||||
* Constructs a scope event object with no specified origin.
|
||||
*
|
||||
* @param eventType Type of the event.
|
||||
* @param scope Scope that applies to the event.
|
||||
* @param date Date that the event applies to.
|
||||
*/
|
||||
public ScopeEvent(ScopeEventType eventType, int scope, Date date) {
|
||||
this(eventType, scope, date, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a scope event object.
|
||||
*
|
||||
* @param eventType Type of the event.
|
||||
* @param scope Scope that applies to the event.
|
||||
* @param date Date that the event applies to.
|
||||
* @param origin The origin of this event.
|
||||
*/
|
||||
public ScopeEvent(ScopeEventType eventType, int scope, Date date, String origin) {
|
||||
super(origin);
|
||||
this.eventType = eventType;
|
||||
this.scope = scope;
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the event date
|
||||
*/
|
||||
public Date getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the type of the event.
|
||||
*/
|
||||
public ScopeEventType getEventType() {
|
||||
return eventType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the scope that applies to the event.
|
||||
*/
|
||||
public int getScope() {
|
||||
return scope;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base.events;
|
||||
|
||||
|
||||
/**
|
||||
* This is the interface to listen to scope events. The events are
|
||||
* scope flushed and all scope flushed, and are dispatched thru this interface
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
|
||||
*/
|
||||
public interface ScopeEventListener extends CacheEventListener {
|
||||
/**
|
||||
* Event fired when a specific or all scopes are flushed.
|
||||
* Use getEventType to differentiate between the two.
|
||||
*/
|
||||
public void scopeFlushed(ScopeEvent event);
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base.events;
|
||||
|
||||
|
||||
/**
|
||||
* This is an enumeration of all the possible events that may occur
|
||||
* at the scope level. Scope-level events are only relevant to the
|
||||
* <code>ServletCacheAdministrator</code>.
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
|
||||
*/
|
||||
public final class ScopeEventType {
|
||||
/**
|
||||
* Specifies an event type for the all scope flushed event.
|
||||
*/
|
||||
public static final ScopeEventType ALL_SCOPES_FLUSHED = new ScopeEventType();
|
||||
|
||||
/**
|
||||
* Specifies an event type for the flushing of a specific scope.
|
||||
*/
|
||||
public static final ScopeEventType SCOPE_FLUSHED = new ScopeEventType();
|
||||
|
||||
/**
|
||||
* Private constructor to ensure that no object of that type are
|
||||
* created externally.
|
||||
*/
|
||||
private ScopeEventType() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
@(#)package.html
|
||||
|
||||
Copyright (c) 2002-2003 by OpenSymphony
|
||||
All rights reserved.
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Provides the base classes and interfaces that allow pluggable event handlers to be
|
||||
incorporated into OSCache.
|
||||
|
||||
|
||||
<h2>Package Specification</h2>
|
||||
|
||||
<h2>Related Documentation</h2>
|
||||
|
||||
|
||||
For overviews, tutorials, examples, guides, and tool documentation, please see:
|
||||
<ul>
|
||||
<li><a href="http://www.opensymphony.com/oscache">The OSCache Homepage</a>
|
||||
</ul>
|
||||
|
||||
<!-- Put @see and @since tags down here. -->
|
||||
|
||||
</body>
|
||||
</html>
|
31
src/main/java/com/opensymphony/oscache/base/package.html
Normal file
31
src/main/java/com/opensymphony/oscache/base/package.html
Normal file
|
@ -0,0 +1,31 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
@(#)package.html
|
||||
|
||||
Copyright (c) 2002-2003 by OpenSymphony
|
||||
All rights reserved.
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Provides the base classes and interfaces that make up the core of OSCache.
|
||||
|
||||
|
||||
<h2>Package Specification</h2>
|
||||
|
||||
<h2>Related Documentation</h2>
|
||||
|
||||
|
||||
For overviews, tutorials, examples, guides, and tool documentation, please see:
|
||||
<ul>
|
||||
<li><a href="http://www.opensymphony.com/oscache">The OSCache Homepage</a>
|
||||
</ul>
|
||||
|
||||
<!-- Put @see and @since tags down here. -->
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base.persistence;
|
||||
|
||||
|
||||
/**
|
||||
* Exception thrown when an error occurs in a PersistenceListener implementation.
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
|
||||
*/
|
||||
public final class CachePersistenceException extends Exception {
|
||||
/**
|
||||
* Creates new CachePersistenceException without detail message.
|
||||
*/
|
||||
public CachePersistenceException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an CachePersistenceException with the specified detail message.
|
||||
*
|
||||
* @param msg the detail message.
|
||||
*/
|
||||
public CachePersistenceException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public CachePersistenceException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.base.persistence;
|
||||
|
||||
import com.opensymphony.oscache.base.Config;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Defines the methods that are required to persist cache data.
|
||||
* To provide a custom persistence mechanism you should implement this
|
||||
* interface and supply the fully-qualified classname to the cache via
|
||||
* the <code>cache.persistence.class</code> configuration property.
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
|
||||
*/
|
||||
public interface PersistenceListener {
|
||||
/**
|
||||
* Verify if an object is currently stored in the persistent cache.
|
||||
*
|
||||
* @param key The cache key of the object to check.
|
||||
*/
|
||||
public boolean isStored(String key) throws CachePersistenceException;
|
||||
|
||||
/**
|
||||
* Verify if a group is currently stored in the persistent cache.
|
||||
*
|
||||
* @param groupName The name of the group to check.
|
||||
*/
|
||||
public boolean isGroupStored(String groupName) throws CachePersistenceException;
|
||||
|
||||
/**
|
||||
* Clear the entire persistent cache (including the root)
|
||||
*/
|
||||
public void clear() throws CachePersistenceException;
|
||||
|
||||
/**
|
||||
* Allow the persistence code to initialize itself based on the supplied
|
||||
* cache configuration.
|
||||
*/
|
||||
public PersistenceListener configure(Config config);
|
||||
|
||||
/**
|
||||
* Removes an object from the persistent cache
|
||||
*/
|
||||
public void remove(String key) throws CachePersistenceException;
|
||||
|
||||
/**
|
||||
* Removes a group from the persistent cache.
|
||||
*
|
||||
* @param groupName The name of the cache group to remove.
|
||||
*/
|
||||
public void removeGroup(String groupName) throws CachePersistenceException;
|
||||
|
||||
/**
|
||||
* Retrieves an object from the persistent cache.
|
||||
*
|
||||
* @param key The unique cache key that maps to the object
|
||||
* being retrieved.
|
||||
* @return The object, or <code>null</code> if no object was found
|
||||
* matching the supplied key.
|
||||
*/
|
||||
public Object retrieve(String key) throws CachePersistenceException;
|
||||
|
||||
/**
|
||||
* Stores an object in the persistent cache.
|
||||
*
|
||||
* @param key The key to uniquely identify this object.
|
||||
* @param obj The object to persist. Most implementations
|
||||
* of this interface will require this object implements
|
||||
* <code>Serializable</code>.
|
||||
*/
|
||||
public void store(String key, Object obj) throws CachePersistenceException;
|
||||
|
||||
/**
|
||||
* Stores a group in the persistent cache.
|
||||
*
|
||||
* @param groupName The name of the group to persist.
|
||||
* @param group A set containing the keys of all the <code>CacheEntry</code>
|
||||
* objects that belong to this group.
|
||||
*/
|
||||
public void storeGroup(String groupName, Set group) throws CachePersistenceException;
|
||||
|
||||
/**
|
||||
* Retrieves a group from the persistent cache.
|
||||
*
|
||||
* @param groupName The name of the group to retrieve.
|
||||
* @return The returned set should contain the keys
|
||||
* of all the <code>CacheEntry</code> objects that belong
|
||||
* to this group.
|
||||
*/
|
||||
Set retrieveGroup(String groupName) throws CachePersistenceException;
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
@(#)package.html
|
||||
|
||||
Copyright (c) 2002-2003 by OpenSymphony
|
||||
All rights reserved.
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Provides the interfaces that provide persistence storage of cached objects.
|
||||
|
||||
|
||||
<h2>Package Specification</h2>
|
||||
|
||||
<h2>Related Documentation</h2>
|
||||
|
||||
|
||||
For overviews, tutorials, examples, guides, and tool documentation, please see:
|
||||
<ul>
|
||||
<li><a href="http://www.opensymphony.com/oscache">The OSCache Homepage</a>
|
||||
</ul>
|
||||
|
||||
<!-- Put @see and @since tags down here. -->
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.extra;
|
||||
|
||||
import com.opensymphony.oscache.base.events.*;
|
||||
|
||||
/**
|
||||
* Implementation of a CacheEntryEventListener. It use the events to count
|
||||
* the operations performed on the cache.
|
||||
* <p>
|
||||
* We are not using any synchronized so that this does not become a bottleneck.
|
||||
* The consequence is that on retrieving values, the operations that are
|
||||
* currently being done won't be counted.
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:abergevin@pyxis-tech.com">Alain Bergevin</a>
|
||||
* @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
|
||||
*/
|
||||
public class CacheEntryEventListenerImpl implements CacheEntryEventListener {
|
||||
/**
|
||||
* Counter for the cache flushes
|
||||
*/
|
||||
private int cacheFlushedCount = 0;
|
||||
|
||||
/**
|
||||
* Counter for the added entries
|
||||
*/
|
||||
private int entryAddedCount = 0;
|
||||
|
||||
/**
|
||||
* Counter for the flushed entries
|
||||
*/
|
||||
private int entryFlushedCount = 0;
|
||||
|
||||
/**
|
||||
* Counter for the removed entries
|
||||
*/
|
||||
private int entryRemovedCount = 0;
|
||||
|
||||
/**
|
||||
* Counter for the updated entries
|
||||
*/
|
||||
private int entryUpdatedCount = 0;
|
||||
|
||||
/**
|
||||
* Counter for the flushed groups
|
||||
*/
|
||||
private int groupFlushedCount = 0;
|
||||
|
||||
/**
|
||||
* Counter for the pattern flushes
|
||||
*/
|
||||
private int patternFlushedCount = 0;
|
||||
|
||||
/**
|
||||
* Constructor, empty for us
|
||||
*/
|
||||
public CacheEntryEventListenerImpl() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the add counter
|
||||
*
|
||||
* @return The added counter
|
||||
*/
|
||||
public int getEntryAddedCount() {
|
||||
return entryAddedCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the flushed counter
|
||||
*
|
||||
* @return The flushed counter
|
||||
*/
|
||||
public int getEntryFlushedCount() {
|
||||
return entryFlushedCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the removed counter
|
||||
*
|
||||
* @return The removed counter
|
||||
*/
|
||||
public int getEntryRemovedCount() {
|
||||
return entryRemovedCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the updated counter
|
||||
*
|
||||
* @return The updated counter
|
||||
*/
|
||||
public int getEntryUpdatedCount() {
|
||||
return entryUpdatedCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the group flush counter
|
||||
*
|
||||
* @return The number of group flush calls that have occurred
|
||||
*/
|
||||
public int getGroupFlushedCount() {
|
||||
return groupFlushedCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the pattern flush counter
|
||||
*
|
||||
* @return The number of pattern flush calls that have occurred
|
||||
*/
|
||||
public int getPatternFlushedCount() {
|
||||
return patternFlushedCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cache flush counter
|
||||
*
|
||||
* @return The number of times the entire cache has been flushed
|
||||
*/
|
||||
public int getCacheFlushedCount() {
|
||||
return cacheFlushedCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event fired when an entry is added in the cache.
|
||||
*
|
||||
* @param event The event triggered when a cache entry has been added
|
||||
*/
|
||||
public void cacheEntryAdded(CacheEntryEvent event) {
|
||||
entryAddedCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event fired when an entry is flushed from the cache.
|
||||
*
|
||||
* @param event The event triggered when a cache entry has been flushed
|
||||
*/
|
||||
public void cacheEntryFlushed(CacheEntryEvent event) {
|
||||
entryFlushedCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event fired when an entry is removed from the cache.
|
||||
*
|
||||
* @param event The event triggered when a cache entry has been removed
|
||||
*/
|
||||
public void cacheEntryRemoved(CacheEntryEvent event) {
|
||||
entryRemovedCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event fired when an entry is updated in the cache.
|
||||
*
|
||||
* @param event The event triggered when a cache entry has been updated
|
||||
*/
|
||||
public void cacheEntryUpdated(CacheEntryEvent event) {
|
||||
entryUpdatedCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event fired when a group is flushed from the cache.
|
||||
*
|
||||
* @param event The event triggered when a cache group has been flushed
|
||||
*/
|
||||
public void cacheGroupFlushed(CacheGroupEvent event) {
|
||||
groupFlushedCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event fired when a pattern is flushed from the cache.
|
||||
*
|
||||
* @param event The event triggered when a cache pattern has been flushed
|
||||
*/
|
||||
public void cachePatternFlushed(CachePatternEvent event) {
|
||||
patternFlushedCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event fired when a cache flush occurs.
|
||||
*
|
||||
* @param event The event triggered when an entire cache is flushed
|
||||
*/
|
||||
public void cacheFlushed(CachewideEvent event) {
|
||||
cacheFlushedCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the internal values in a string form
|
||||
*/
|
||||
public String toString() {
|
||||
return ("Added " + entryAddedCount + ", Updated " + entryUpdatedCount + ", Flushed " + entryFlushedCount + ", Removed " + entryRemovedCount + ", Groups Flushed " + groupFlushedCount + ", Patterns Flushed " + patternFlushedCount + ", Cache Flushed " + cacheFlushedCount);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.extra;
|
||||
|
||||
import com.opensymphony.oscache.base.events.CacheMapAccessEvent;
|
||||
import com.opensymphony.oscache.base.events.CacheMapAccessEventListener;
|
||||
import com.opensymphony.oscache.base.events.CacheMapAccessEventType;
|
||||
|
||||
/**
|
||||
* Implementation of a CacheMapAccessEventListener. It uses the events to count
|
||||
* the cache hit and misses.
|
||||
* <p>
|
||||
* We are not using any synchronized so that this does not become a bottleneck.
|
||||
* The consequence is that on retrieving values, the operations that are
|
||||
* currently being done won't be counted.
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:abergevin@pyxis-tech.com">Alain Bergevin</a>
|
||||
* @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
|
||||
*/
|
||||
public class CacheMapAccessEventListenerImpl implements CacheMapAccessEventListener {
|
||||
/**
|
||||
* Hit counter
|
||||
*/
|
||||
private int hitCount = 0;
|
||||
|
||||
/**
|
||||
* Miss counter
|
||||
*/
|
||||
private int missCount = 0;
|
||||
|
||||
/**
|
||||
* Stale hit counter
|
||||
*/
|
||||
private int staleHitCount = 0;
|
||||
|
||||
/**
|
||||
* Constructor, empty for us
|
||||
*/
|
||||
public CacheMapAccessEventListenerImpl() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cache's current hit count
|
||||
*
|
||||
* @return The hit count
|
||||
*/
|
||||
public int getHitCount() {
|
||||
return hitCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cache's current miss count
|
||||
*
|
||||
* @return The miss count
|
||||
*/
|
||||
public int getMissCount() {
|
||||
return missCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cache's current stale hit count
|
||||
*/
|
||||
public int getStaleHitCount() {
|
||||
return staleHitCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method handles an event each time the cache is accessed
|
||||
*
|
||||
* @param event The event triggered when the cache was accessed
|
||||
*/
|
||||
public void accessed(CacheMapAccessEvent event) {
|
||||
// Retrieve the event type and update the counters
|
||||
CacheMapAccessEventType type = event.getEventType();
|
||||
|
||||
// Handles a hit event
|
||||
if (type == CacheMapAccessEventType.HIT) {
|
||||
hitCount++;
|
||||
}
|
||||
// Handles a stale hit event
|
||||
else if (type == CacheMapAccessEventType.STALE_HIT) {
|
||||
staleHitCount++;
|
||||
}
|
||||
// Handles a miss event
|
||||
else if (type == CacheMapAccessEventType.MISS) {
|
||||
missCount++;
|
||||
} else {
|
||||
// Unknown event!
|
||||
throw new IllegalArgumentException("Unknown Cache Map Access event received");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets all of the totals to zero
|
||||
*/
|
||||
public void reset() {
|
||||
hitCount = 0;
|
||||
staleHitCount = 0;
|
||||
missCount = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the counters in a string form
|
||||
*/
|
||||
public String toString() {
|
||||
return ("Hit count = " + hitCount + ", stale hit count = " + staleHitCount + " and miss count = " + missCount);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.extra;
|
||||
|
||||
import com.opensymphony.oscache.base.events.ScopeEvent;
|
||||
import com.opensymphony.oscache.base.events.ScopeEventListener;
|
||||
import com.opensymphony.oscache.base.events.ScopeEventType;
|
||||
|
||||
/**
|
||||
* Implementation of a ScopeEventListener that keeps track of the scope flush events.
|
||||
* We are not using any synchronized so that this does not become a bottleneck.
|
||||
* The consequence is that on retrieving values, the operations that are
|
||||
* currently being done won't be counted.
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:abergevin@pyxis-tech.com">Alain Bergevin</a>
|
||||
*/
|
||||
public class ScopeEventListenerImpl implements ScopeEventListener {
|
||||
/**
|
||||
* Scope names
|
||||
*/
|
||||
public static final String[] SCOPE_NAMES = {
|
||||
null, "page", "request", "session", "application"
|
||||
};
|
||||
|
||||
/**
|
||||
* Number of known scopes
|
||||
*/
|
||||
public static final int NB_SCOPES = SCOPE_NAMES.length - 1;
|
||||
|
||||
/**
|
||||
* Page scope number
|
||||
*/
|
||||
public static final int PAGE_SCOPE = 1;
|
||||
|
||||
/**
|
||||
* Request scope number
|
||||
*/
|
||||
public static final int REQUEST_SCOPE = 2;
|
||||
|
||||
/**
|
||||
* Session scope number
|
||||
*/
|
||||
public static final int SESSION_SCOPE = 3;
|
||||
|
||||
/**
|
||||
* Application scope number
|
||||
*/
|
||||
public static final int APPLICATION_SCOPE = 4;
|
||||
|
||||
/**
|
||||
* Flush counter for all scopes.
|
||||
* Add one to the number of scope because the array is being used
|
||||
* from position 1 instead of 0 for convenience
|
||||
*/
|
||||
private int[] scopeFlushCount = new int[NB_SCOPES + 1];
|
||||
|
||||
public ScopeEventListenerImpl() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the flush count for scope {@link ScopeEventListenerImpl#APPLICATION_SCOPE}.
|
||||
* <p>
|
||||
* @return The total number of application flush
|
||||
*/
|
||||
public int getApplicationScopeFlushCount() {
|
||||
return scopeFlushCount[APPLICATION_SCOPE];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the flush count for scope {@link ScopeEventListenerImpl#PAGE_SCOPE}.
|
||||
* @return The total number of page flush
|
||||
*/
|
||||
public int getPageScopeFlushCount() {
|
||||
return scopeFlushCount[PAGE_SCOPE];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the flush count for scope {@link ScopeEventListenerImpl#REQUEST_SCOPE}.
|
||||
* @return The total number of request flush
|
||||
*/
|
||||
public int getRequestScopeFlushCount() {
|
||||
return scopeFlushCount[REQUEST_SCOPE];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the flush count for scope {@link ScopeEventListenerImpl#SESSION_SCOPE}.
|
||||
* @return The total number of session flush
|
||||
*/
|
||||
public int getSessionScopeFlushCount() {
|
||||
return scopeFlushCount[SESSION_SCOPE];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total flush count.
|
||||
* @return The total number of scope flush
|
||||
*/
|
||||
public int getTotalScopeFlushCount() {
|
||||
int total = 0;
|
||||
|
||||
for (int count = 1; count <= NB_SCOPES; count++) {
|
||||
total += scopeFlushCount[count];
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles all the scope flush events.
|
||||
* @param event The scope event
|
||||
*/
|
||||
public void scopeFlushed(ScopeEvent event) {
|
||||
// Get the event type and process it
|
||||
ScopeEventType eventType = event.getEventType();
|
||||
|
||||
if (eventType == ScopeEventType.ALL_SCOPES_FLUSHED) {
|
||||
// All 4 scopes were flushed, increment the counters
|
||||
for (int count = 1; count <= NB_SCOPES; count++) {
|
||||
scopeFlushCount[count]++;
|
||||
}
|
||||
} else if (eventType == ScopeEventType.SCOPE_FLUSHED) {
|
||||
// Get back the scope from the event and increment the flush count
|
||||
scopeFlushCount[event.getScope()]++;
|
||||
} else {
|
||||
// Unknown event!
|
||||
throw new IllegalArgumentException("Unknown Scope Event type received");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the flush counter in a string form.
|
||||
*/
|
||||
public String toString() {
|
||||
StringBuffer returnString = new StringBuffer("Flush count for ");
|
||||
|
||||
for (int count = 1; count <= NB_SCOPES; count++) {
|
||||
returnString.append("scope " + SCOPE_NAMES[count] + " = " + scopeFlushCount[count] + ", ");
|
||||
}
|
||||
|
||||
// Remove the last 2 chars, which are ", "
|
||||
returnString.setLength(returnString.length() - 2);
|
||||
|
||||
return returnString.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,295 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2007 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.extra;
|
||||
|
||||
import com.opensymphony.oscache.base.Cache;
|
||||
import com.opensymphony.oscache.base.events.CacheEntryEvent;
|
||||
import com.opensymphony.oscache.base.events.CacheEntryEventListener;
|
||||
import com.opensymphony.oscache.base.events.CacheGroupEvent;
|
||||
import com.opensymphony.oscache.base.events.CacheMapAccessEvent;
|
||||
import com.opensymphony.oscache.base.events.CacheMapAccessEventListener;
|
||||
import com.opensymphony.oscache.base.events.CacheMapAccessEventType;
|
||||
import com.opensymphony.oscache.base.events.CachePatternEvent;
|
||||
import com.opensymphony.oscache.base.events.CachewideEvent;
|
||||
import com.opensymphony.oscache.base.events.ScopeEvent;
|
||||
import com.opensymphony.oscache.base.events.ScopeEventListener;
|
||||
import com.opensymphony.oscache.extra.ScopeEventListenerImpl;
|
||||
|
||||
/**
|
||||
* A simple implementation of a statistic reporter which uses the
|
||||
* event listeners. It uses the events to count the cache hit and
|
||||
* misses and of course the flushes.
|
||||
* <p>
|
||||
* We are not using any synchronized so that this does not become a bottleneck.
|
||||
* The consequence is that on retrieving values, the operations that are
|
||||
* currently being done won't be counted.
|
||||
*/
|
||||
public class StatisticListenerImpl implements CacheMapAccessEventListener,
|
||||
CacheEntryEventListener, ScopeEventListener {
|
||||
|
||||
/**
|
||||
* Hit counter.
|
||||
*/
|
||||
private static int hitCount = 0;
|
||||
|
||||
/**
|
||||
* Miss counter.
|
||||
*/
|
||||
private static int missCount = 0;
|
||||
|
||||
/**
|
||||
* Stale hit counter.
|
||||
*/
|
||||
private static int staleHitCount = 0;
|
||||
|
||||
/**
|
||||
* Hit counter sum.
|
||||
*/
|
||||
private static int hitCountSum = 0;
|
||||
|
||||
/**
|
||||
* Miss counter sum.
|
||||
*/
|
||||
private static int missCountSum = 0;
|
||||
|
||||
/**
|
||||
* Stale hit counter.
|
||||
*/
|
||||
private static int staleHitCountSum = 0;
|
||||
|
||||
/**
|
||||
* Flush hit counter.
|
||||
*/
|
||||
private static int flushCount = 0;
|
||||
|
||||
/**
|
||||
* Miss counter sum.
|
||||
*/
|
||||
private static int entriesAdded = 0;
|
||||
|
||||
/**
|
||||
* Stale hit counter.
|
||||
*/
|
||||
private static int entriesRemoved = 0;
|
||||
|
||||
/**
|
||||
* Flush hit counter.
|
||||
*/
|
||||
private static int entriesUpdated = 0;
|
||||
|
||||
/**
|
||||
* Constructor, empty for us.
|
||||
*/
|
||||
public StatisticListenerImpl() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method handles an event each time the cache is accessed.
|
||||
*
|
||||
* @param event
|
||||
* The event triggered when the cache was accessed
|
||||
* @see com.opensymphony.oscache.base.events.CacheMapAccessEventListener#accessed(CacheMapAccessEvent)
|
||||
*/
|
||||
public void accessed(CacheMapAccessEvent event) {
|
||||
// Retrieve the event type and update the counters
|
||||
CacheMapAccessEventType type = event.getEventType();
|
||||
|
||||
// Handles a hit event
|
||||
if (type == CacheMapAccessEventType.HIT) {
|
||||
hitCount++;
|
||||
} else if (type == CacheMapAccessEventType.STALE_HIT) { // Handles a
|
||||
// stale hit
|
||||
// event
|
||||
staleHitCount++;
|
||||
} else if (type == CacheMapAccessEventType.MISS) { // Handles a miss
|
||||
// event
|
||||
missCount++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the flush of the cache.
|
||||
*
|
||||
* @param info the string to be logged.
|
||||
*/
|
||||
private void flushed(String info) {
|
||||
flushCount++;
|
||||
|
||||
hitCountSum += hitCount;
|
||||
staleHitCountSum += staleHitCount;
|
||||
missCountSum += missCount;
|
||||
|
||||
hitCount = 0;
|
||||
staleHitCount = 0;
|
||||
missCount = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event fired when a specific or all scopes are flushed.
|
||||
*
|
||||
* @param event ScopeEvent
|
||||
* @see com.opensymphony.oscache.base.events.ScopeEventListener#scopeFlushed(ScopeEvent)
|
||||
*/
|
||||
public void scopeFlushed(ScopeEvent event) {
|
||||
flushed("scope " + ScopeEventListenerImpl.SCOPE_NAMES[event.getScope()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Event fired when an entry is added to the cache.
|
||||
*
|
||||
* @param event CacheEntryEvent
|
||||
* @see com.opensymphony.oscache.base.events.CacheEntryEventListener#cacheEntryAdded(CacheEntryEvent)
|
||||
*/
|
||||
public void cacheEntryAdded(CacheEntryEvent event) {
|
||||
entriesAdded++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event fired when an entry is flushed from the cache.
|
||||
*
|
||||
* @param event CacheEntryEvent
|
||||
* @see com.opensymphony.oscache.base.events.CacheEntryEventListener#cacheEntryFlushed(CacheEntryEvent)
|
||||
*/
|
||||
public void cacheEntryFlushed(CacheEntryEvent event) {
|
||||
// do nothing, because a group or other flush is coming
|
||||
if (!Cache.NESTED_EVENT.equals(event.getOrigin())) {
|
||||
flushed("entry " + event.getKey() + " / " + event.getOrigin());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event fired when an entry is removed from the cache.
|
||||
*
|
||||
* @param event CacheEntryEvent
|
||||
* @see com.opensymphony.oscache.base.events.CacheEntryEventListener#cacheEntryRemoved(CacheEntryEvent)
|
||||
*/
|
||||
public void cacheEntryRemoved(CacheEntryEvent event) {
|
||||
entriesRemoved++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event fired when an entry is updated in the cache.
|
||||
*
|
||||
* @param event CacheEntryEvent
|
||||
* @see com.opensymphony.oscache.base.events.CacheEntryEventListener#cacheEntryUpdated(CacheEntryEvent)
|
||||
*/
|
||||
public void cacheEntryUpdated(CacheEntryEvent event) {
|
||||
entriesUpdated++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event fired when a group is flushed from the cache.
|
||||
*
|
||||
* @param event CacheGroupEvent
|
||||
* @see com.opensymphony.oscache.base.events.CacheEntryEventListener#cacheGroupFlushed(CacheGroupEvent)
|
||||
*/
|
||||
public void cacheGroupFlushed(CacheGroupEvent event) {
|
||||
flushed("group " + event.getGroup());
|
||||
}
|
||||
|
||||
/**
|
||||
* Event fired when a key pattern is flushed from the cache.
|
||||
*
|
||||
* @param event CachePatternEvent
|
||||
* @see com.opensymphony.oscache.base.events.CacheEntryEventListener#cachePatternFlushed(CachePatternEvent)
|
||||
*/
|
||||
public void cachePatternFlushed(CachePatternEvent event) {
|
||||
flushed("pattern " + event.getPattern());
|
||||
}
|
||||
|
||||
/**
|
||||
* An event that is fired when an entire cache gets flushed.
|
||||
*
|
||||
* @param event CachewideEvent
|
||||
* @see com.opensymphony.oscache.base.events.CacheEntryEventListener#cacheFlushed(CachewideEvent)
|
||||
*/
|
||||
public void cacheFlushed(CachewideEvent event) {
|
||||
flushed("wide " + event.getDate());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the counters in a string form.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String toString() {
|
||||
return "StatisticListenerImpl: Hit = " + hitCount + " / " + hitCountSum
|
||||
+ ", stale hit = " + staleHitCount + " / " + staleHitCountSum
|
||||
+ ", miss = " + missCount + " / " + missCountSum + ", flush = "
|
||||
+ flushCount + ", entries (added, removed, updates) = "
|
||||
+ entriesAdded + ", " + entriesRemoved + ", " + entriesUpdated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the entriesAdded.
|
||||
*/
|
||||
public int getEntriesAdded() {
|
||||
return entriesAdded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the entriesRemoved.
|
||||
*/
|
||||
public int getEntriesRemoved() {
|
||||
return entriesRemoved;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the entriesUpdated.
|
||||
*/
|
||||
public int getEntriesUpdated() {
|
||||
return entriesUpdated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the flushCount.
|
||||
*/
|
||||
public int getFlushCount() {
|
||||
return flushCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the hitCount.
|
||||
*/
|
||||
public int getHitCount() {
|
||||
return hitCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the hitCountSum.
|
||||
*/
|
||||
public int getHitCountSum() {
|
||||
return hitCountSum;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the missCount.
|
||||
*/
|
||||
public int getMissCount() {
|
||||
return missCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the missCountSum.
|
||||
*/
|
||||
public int getMissCountSum() {
|
||||
return missCountSum;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the staleHitCount.
|
||||
*/
|
||||
public int getStaleHitCount() {
|
||||
return staleHitCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the staleHitCountSum.
|
||||
*/
|
||||
public int getStaleHitCountSum() {
|
||||
return staleHitCountSum;
|
||||
}
|
||||
}
|
32
src/main/java/com/opensymphony/oscache/extra/package.html
Normal file
32
src/main/java/com/opensymphony/oscache/extra/package.html
Normal file
|
@ -0,0 +1,32 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
@(#)package.html
|
||||
|
||||
Copyright (c) 2002-2003 by OpenSymphony
|
||||
All rights reserved.
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Provides some basic event handler implementations that aren't essential to the core
|
||||
OSCache code, but form a useful starting point for basic logging or further development.
|
||||
|
||||
|
||||
<h2>Package Specification</h2>
|
||||
|
||||
<h2>Related Documentation</h2>
|
||||
|
||||
|
||||
For overviews, tutorials, examples, guides, and tool documentation, please see:
|
||||
<ul>
|
||||
<li><a href="http://www.opensymphony.com/oscache">The OSCache Homepage</a>
|
||||
</ul>
|
||||
|
||||
<!-- Put @see and @since tags down here. -->
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,307 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.general;
|
||||
|
||||
import com.opensymphony.oscache.base.*;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* A GeneralCacheAdministrator creates, flushes and administers the cache.
|
||||
*
|
||||
* EXAMPLES :
|
||||
* <pre><code>
|
||||
* // ---------------------------------------------------------------
|
||||
* // Typical use with fail over
|
||||
* // ---------------------------------------------------------------
|
||||
* String myKey = "myKey";
|
||||
* String myValue;
|
||||
* int myRefreshPeriod = 1000;
|
||||
* try {
|
||||
* // Get from the cache
|
||||
* myValue = (String) admin.getFromCache(myKey, myRefreshPeriod);
|
||||
* } catch (NeedsRefreshException nre) {
|
||||
* try {
|
||||
* // Get the value (probably by calling an EJB)
|
||||
* myValue = "This is the content retrieved.";
|
||||
* // Store in the cache
|
||||
* admin.putInCache(myKey, myValue);
|
||||
* } catch (Exception ex) {
|
||||
* // We have the current content if we want fail-over.
|
||||
* myValue = (String) nre.getCacheContent();
|
||||
* // It is essential that cancelUpdate is called if the
|
||||
* // cached content is not rebuilt
|
||||
* admin.cancelUpdate(myKey);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*
|
||||
*
|
||||
* // ---------------------------------------------------------------
|
||||
* // Typical use without fail over
|
||||
* // ---------------------------------------------------------------
|
||||
* String myKey = "myKey";
|
||||
* String myValue;
|
||||
* int myRefreshPeriod = 1000;
|
||||
* try {
|
||||
* // Get from the cache
|
||||
* myValue = (String) admin.getFromCache(myKey, myRefreshPeriod);
|
||||
* } catch (NeedsRefreshException nre) {
|
||||
* try {
|
||||
* // Get the value (probably by calling an EJB)
|
||||
* myValue = "This is the content retrieved.";
|
||||
* // Store in the cache
|
||||
* admin.putInCache(myKey, myValue);
|
||||
* updated = true;
|
||||
* } finally {
|
||||
* if (!updated) {
|
||||
* // It is essential that cancelUpdate is called if the
|
||||
* // cached content could not be rebuilt
|
||||
* admin.cancelUpdate(myKey);
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* // ---------------------------------------------------------------
|
||||
* // ---------------------------------------------------------------
|
||||
* </code></pre>
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
|
||||
* @author <a href="mailto:abergevin@pyxis-tech.com">Alain Bergevin</a>
|
||||
*/
|
||||
public class GeneralCacheAdministrator extends AbstractCacheAdministrator {
|
||||
private static transient final Log log = LogFactory.getLog(GeneralCacheAdministrator.class);
|
||||
|
||||
/**
|
||||
* Application cache
|
||||
*/
|
||||
private Cache applicationCache = null;
|
||||
|
||||
/**
|
||||
* Create the cache administrator.
|
||||
*/
|
||||
public GeneralCacheAdministrator() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the cache administrator with the specified properties
|
||||
*/
|
||||
public GeneralCacheAdministrator(Properties p) {
|
||||
super(p);
|
||||
log.info("Constructed GeneralCacheAdministrator()");
|
||||
createCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Grabs a cache
|
||||
*
|
||||
* @return The cache
|
||||
*/
|
||||
public Cache getCache() {
|
||||
return applicationCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an object from the cache
|
||||
*
|
||||
* @param key The key entered by the user.
|
||||
*/
|
||||
public void removeEntry(String key) {
|
||||
getCache().removeEntry(key);
|
||||
}
|
||||
/**
|
||||
* Get an object from the cache
|
||||
*
|
||||
* @param key The key entered by the user.
|
||||
* @return The object from cache
|
||||
* @throws NeedsRefreshException when no cache entry could be found with the
|
||||
* supplied key, or when an entry was found but is considered out of date. If
|
||||
* the cache entry is a new entry that is currently being constructed this method
|
||||
* will block until the new entry becomes available. Similarly, it will block if
|
||||
* a stale entry is currently being rebuilt by another thread and cache blocking is
|
||||
* enabled (<code>cache.blocking=true</code>).
|
||||
*/
|
||||
public Object getFromCache(String key) throws NeedsRefreshException {
|
||||
return getCache().getFromCache(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an object from the cache
|
||||
*
|
||||
* @param key The key entered by the user.
|
||||
* @param refreshPeriod How long the object can stay in cache in seconds. To
|
||||
* allow the entry to stay in the cache indefinitely, supply a value of
|
||||
* {@link CacheEntry#INDEFINITE_EXPIRY}
|
||||
* @return The object from cache
|
||||
* @throws NeedsRefreshException when no cache entry could be found with the
|
||||
* supplied key, or when an entry was found but is considered out of date. If
|
||||
* the cache entry is a new entry that is currently being constructed this method
|
||||
* will block until the new entry becomes available. Similarly, it will block if
|
||||
* a stale entry is currently being rebuilt by another thread and cache blocking is
|
||||
* enabled (<code>cache.blocking=true</code>).
|
||||
*/
|
||||
public Object getFromCache(String key, int refreshPeriod) throws NeedsRefreshException {
|
||||
return getCache().getFromCache(key, refreshPeriod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an object from the cache
|
||||
*
|
||||
* @param key The key entered by the user.
|
||||
* @param refreshPeriod How long the object can stay in cache in seconds. To
|
||||
* allow the entry to stay in the cache indefinitely, supply a value of
|
||||
* {@link CacheEntry#INDEFINITE_EXPIRY}
|
||||
* @param cronExpression A cron expression that the age of the cache entry
|
||||
* will be compared to. If the entry is older than the most recent match for the
|
||||
* cron expression, the entry will be considered stale.
|
||||
* @return The object from cache
|
||||
* @throws NeedsRefreshException when no cache entry could be found with the
|
||||
* supplied key, or when an entry was found but is considered out of date. If
|
||||
* the cache entry is a new entry that is currently being constructed this method
|
||||
* will block until the new entry becomes available. Similarly, it will block if
|
||||
* a stale entry is currently being rebuilt by another thread and cache blocking is
|
||||
* enabled (<code>cache.blocking=true</code>).
|
||||
*/
|
||||
public Object getFromCache(String key, int refreshPeriod, String cronExpression) throws NeedsRefreshException {
|
||||
return getCache().getFromCache(key, refreshPeriod, cronExpression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels a pending cache update. This should only be called by a thread
|
||||
* that received a {@link NeedsRefreshException} and was unable to generate
|
||||
* some new cache content.
|
||||
*
|
||||
* @param key The cache entry key to cancel the update of.
|
||||
*/
|
||||
public void cancelUpdate(String key) {
|
||||
getCache().cancelUpdate(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts down the cache administrator.
|
||||
*/
|
||||
public void destroy() {
|
||||
finalizeListeners(applicationCache);
|
||||
}
|
||||
|
||||
// METHODS THAT DELEGATES TO THE CACHE ---------------------
|
||||
|
||||
/**
|
||||
* Flush the entire cache immediately.
|
||||
*/
|
||||
public void flushAll() {
|
||||
getCache().flushAll(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush the entire cache at the given date.
|
||||
*
|
||||
* @param date The time to flush
|
||||
*/
|
||||
public void flushAll(Date date) {
|
||||
getCache().flushAll(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes a single cache entry.
|
||||
*/
|
||||
public void flushEntry(String key) {
|
||||
getCache().flushEntry(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes all items that belong to the specified group.
|
||||
*
|
||||
* @param group The name of the group to flush
|
||||
*/
|
||||
public void flushGroup(String group) {
|
||||
getCache().flushGroup(group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to flush all items that have a specified pattern in the key.
|
||||
*
|
||||
* @param pattern Pattern.
|
||||
* @deprecated For performance and flexibility reasons it is preferable to
|
||||
* store cache entries in groups and use the {@link #flushGroup(String)} method
|
||||
* instead of relying on pattern flushing.
|
||||
*/
|
||||
public void flushPattern(String pattern) {
|
||||
getCache().flushPattern(pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put an object in a cache
|
||||
*
|
||||
* @param key The key entered by the user
|
||||
* @param content The object to store
|
||||
* @param policy Object that implements refresh policy logic
|
||||
*/
|
||||
public void putInCache(String key, Object content, EntryRefreshPolicy policy) {
|
||||
Cache cache = getCache();
|
||||
cache.putInCache(key, content, policy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put an object in a cache
|
||||
*
|
||||
* @param key The key entered by the user
|
||||
* @param content The object to store
|
||||
*/
|
||||
public void putInCache(String key, Object content) {
|
||||
putInCache(key, content, (EntryRefreshPolicy) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts an object in a cache
|
||||
*
|
||||
* @param key The unique key for this cached object
|
||||
* @param content The object to store
|
||||
* @param groups The groups that this object belongs to
|
||||
*/
|
||||
public void putInCache(String key, Object content, String[] groups) {
|
||||
getCache().putInCache(key, content, groups);
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts an object in a cache
|
||||
*
|
||||
* @param key The unique key for this cached object
|
||||
* @param content The object to store
|
||||
* @param groups The groups that this object belongs to
|
||||
* @param policy The refresh policy to use
|
||||
*/
|
||||
public void putInCache(String key, Object content, String[] groups, EntryRefreshPolicy policy) {
|
||||
getCache().putInCache(key, content, groups, policy, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cache capacity (number of items). If the cache contains
|
||||
* more than <code>capacity</code> items then items will be removed
|
||||
* to bring the cache back down to the new size.
|
||||
*
|
||||
* @param capacity The new capacity of the cache
|
||||
*/
|
||||
public void setCacheCapacity(int capacity) {
|
||||
super.setCacheCapacity(capacity);
|
||||
getCache().setCapacity(capacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a cache in this admin
|
||||
*/
|
||||
private void createCache() {
|
||||
log.info("Creating new cache");
|
||||
|
||||
applicationCache = new Cache(isMemoryCaching(), isUnlimitedDiskCache(), isOverflowPersistence(), isBlocking(), algorithmClass, cacheCapacity);
|
||||
|
||||
configureStandardListeners(applicationCache);
|
||||
}
|
||||
}
|
31
src/main/java/com/opensymphony/oscache/general/package.html
Normal file
31
src/main/java/com/opensymphony/oscache/general/package.html
Normal file
|
@ -0,0 +1,31 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
@(#)package.html
|
||||
|
||||
Copyright (c) 2002-2003 by OpenSymphony
|
||||
All rights reserved.
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Provides a generic administrator class for the cache.
|
||||
|
||||
|
||||
<h2>Package Specification</h2>
|
||||
|
||||
<h2>Related Documentation</h2>
|
||||
|
||||
|
||||
For overviews, tutorials, examples, guides, and tool documentation, please see:
|
||||
<ul>
|
||||
<li><a href="http://www.opensymphony.com/oscache">The OSCache Homepage</a>
|
||||
</ul>
|
||||
|
||||
<!-- Put @see and @since tags down here. -->
|
||||
|
||||
</body>
|
||||
</html>
|
161
src/main/java/com/opensymphony/oscache/hibernate/OSCache.java
Normal file
161
src/main/java/com/opensymphony/oscache/hibernate/OSCache.java
Normal file
|
@ -0,0 +1,161 @@
|
|||
package com.opensymphony.oscache.hibernate;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.hibernate.cache.Cache;
|
||||
import org.hibernate.cache.CacheException;
|
||||
import org.hibernate.cache.Timestamper;
|
||||
|
||||
import com.opensymphony.oscache.base.NeedsRefreshException;
|
||||
import com.opensymphony.oscache.general.GeneralCacheAdministrator;
|
||||
|
||||
/**
|
||||
* Cache plugin for Hibernate 3.2 and OpenSymphony OSCache 2.4.
|
||||
* <p/>
|
||||
* The OSCache implementation assumes that identifiers have well-behaved <tt>toString()</tt> methods.
|
||||
* This implementation <b>must</b> be threadsafe.
|
||||
*
|
||||
* @version $Revision:$
|
||||
*/
|
||||
public class OSCache implements Cache {
|
||||
|
||||
/** The OSCache 2.4 cache administrator. */
|
||||
private GeneralCacheAdministrator cache;
|
||||
private final int refreshPeriod;
|
||||
private final String cron;
|
||||
private final String regionName;
|
||||
private final String[] regionGroups;
|
||||
|
||||
public OSCache(GeneralCacheAdministrator cache, int refreshPeriod, String cron, String region) {
|
||||
this.cache = cache;
|
||||
this.refreshPeriod = refreshPeriod;
|
||||
this.cron = cron;
|
||||
this.regionName = region;
|
||||
this.regionGroups = new String[] {region};
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.hibernate.cache.Cache#get(java.lang.Object)
|
||||
*/
|
||||
public Object get(Object key) throws CacheException {
|
||||
try {
|
||||
return cache.getFromCache( toString(key), refreshPeriod, cron );
|
||||
}
|
||||
catch (NeedsRefreshException e) {
|
||||
cache.cancelUpdate( toString(key) );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.hibernate.cache.Cache#put(java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
public void put(Object key, Object value) throws CacheException {
|
||||
cache.putInCache( toString(key), value, regionGroups );
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.hibernate.cache.Cache#remove(java.lang.Object)
|
||||
*/
|
||||
public void remove(Object key) throws CacheException {
|
||||
cache.flushEntry( toString(key) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.hibernate.cache.Cache#clear()
|
||||
*/
|
||||
public void clear() throws CacheException {
|
||||
cache.flushGroup(regionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.hibernate.cache.Cache#destroy()
|
||||
*/
|
||||
public void destroy() throws CacheException {
|
||||
synchronized (cache) {
|
||||
cache.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.hibernate.cache.Cache#lock(java.lang.Object)
|
||||
*/
|
||||
public void lock(Object key) throws CacheException {
|
||||
// local cache, so we use synchronization
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.hibernate.cache.Cache#unlock(java.lang.Object)
|
||||
*/
|
||||
public void unlock(Object key) throws CacheException {
|
||||
// local cache, so we use synchronization
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.hibernate.cache.Cache#nextTimestamp()
|
||||
*/
|
||||
public long nextTimestamp() {
|
||||
return Timestamper.next();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.hibernate.cache.Cache#getTimeout()
|
||||
*/
|
||||
public int getTimeout() {
|
||||
return Timestamper.ONE_MS * 60000; //ie. 60 seconds
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.hibernate.cache.Cache#toMap()
|
||||
*/
|
||||
public Map toMap() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.hibernate.cache.Cache#getElementCountOnDisk()
|
||||
*/
|
||||
public long getElementCountOnDisk() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.hibernate.cache.Cache#getElementCountInMemory()
|
||||
*/
|
||||
public long getElementCountInMemory() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.hibernate.cache.Cache#getSizeInMemory()
|
||||
*/
|
||||
public long getSizeInMemory() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.hibernate.cache.Cache#getRegionName()
|
||||
*/
|
||||
public String getRegionName() {
|
||||
return regionName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.hibernate.cache.Cache#update(java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
public void update(Object key, Object value) throws CacheException {
|
||||
put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.hibernate.cache.Cache#read(java.lang.Object)
|
||||
*/
|
||||
public Object read(Object key) throws CacheException {
|
||||
return get(key);
|
||||
}
|
||||
|
||||
private String toString(Object key) {
|
||||
return String.valueOf(key) + "." + regionName;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
package com.opensymphony.oscache.hibernate;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.hibernate.cache.Cache;
|
||||
import org.hibernate.cache.CacheException;
|
||||
import org.hibernate.cache.CacheProvider;
|
||||
import org.hibernate.cache.Timestamper;
|
||||
import org.hibernate.util.StringHelper;
|
||||
|
||||
import com.opensymphony.oscache.base.CacheEntry;
|
||||
import com.opensymphony.oscache.base.Config;
|
||||
import com.opensymphony.oscache.general.GeneralCacheAdministrator;
|
||||
import com.opensymphony.oscache.util.StringUtil;
|
||||
|
||||
/**
|
||||
* Cache provider plugin for Hibernate 3.2 and OpenSymphony OSCache 2.4.
|
||||
* <p/>
|
||||
* This implementation assumes that identifiers have well-behaved <tt>toString()</tt> methods.
|
||||
* <p/>
|
||||
* To enable OSCache for Hibernate's second level cache add the following line to Hibernate's configuration e.g. <code>hibernate.cfg.xml</code>):
|
||||
* <code>hibernate.cache.provider_class=com.opensymphony.oscache.hibernate.OSCacheProvider</code>
|
||||
* To configure a different configuration file use the following parameter in the Hibernate's configuration:
|
||||
* <code>com.opensymphony.oscache.configurationResourceName=[path to oscache-hibernate.properties]</code>
|
||||
*
|
||||
* @version $Revision:$
|
||||
*/
|
||||
public class OSCacheProvider implements CacheProvider {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(OSCacheProvider.class);
|
||||
|
||||
/** In the Hibernate system property you can specify the location of the oscache configuration file name. */
|
||||
public static final String OSCACHE_CONFIGURATION_RESOURCE_NAME = "com.opensymphony.oscache.configurationResourceName";
|
||||
|
||||
/** The <tt>OSCache</tt> refresh period property suffix. */
|
||||
public static final String OSCACHE_REFRESH_PERIOD = "refresh.period";
|
||||
|
||||
/** The <tt>OSCache</tt> CRON expression property suffix. */
|
||||
public static final String OSCACHE_CRON = "cron";
|
||||
|
||||
private static GeneralCacheAdministrator cache;
|
||||
|
||||
/**
|
||||
* Builds a new {@link Cache} instance, and gets it's properties from the
|
||||
* GeneralCacheAdministrator {@link GeneralCacheAdministrator}
|
||||
* which reads the properties file (<code>oscache.properties</code>) in the start method:
|
||||
* @see com.opensymphony.oscache.hibernate.OSCacheProvider#start(java.util.Properties)
|
||||
*
|
||||
* @param region the region of the cache
|
||||
* @param properties not used
|
||||
* @return the hibernate 2nd level cache
|
||||
* @throws CacheException
|
||||
*
|
||||
* @see org.hibernate.cache.CacheProvider#buildCache(java.lang.String, java.util.Properties)
|
||||
*/
|
||||
public Cache buildCache(String region, Properties properties) throws CacheException {
|
||||
if (cache != null) {
|
||||
LOG.debug("building cache in OSCacheProvider...");
|
||||
|
||||
String refreshPeriodString = cache.getProperty( StringHelper.qualify(region, OSCACHE_REFRESH_PERIOD) );
|
||||
int refreshPeriod = refreshPeriodString==null ? CacheEntry.INDEFINITE_EXPIRY : Integer.parseInt( refreshPeriodString.trim() );
|
||||
|
||||
String cron = cache.getProperty( StringHelper.qualify(region, OSCACHE_CRON) );
|
||||
|
||||
return new OSCache(cache, refreshPeriod, cron, region);
|
||||
}
|
||||
throw new CacheException("OSCache was stopped or wasn't configured via method start.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.hibernate.cache.CacheProvider#nextTimestamp()
|
||||
*/
|
||||
public long nextTimestamp() {
|
||||
return Timestamper.next();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method isn't documented in Hibernate:
|
||||
* @see org.hibernate.cache.CacheProvider#isMinimalPutsEnabledByDefault()
|
||||
*/
|
||||
public boolean isMinimalPutsEnabledByDefault() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.hibernate.cache.CacheProvider#stop()
|
||||
*/
|
||||
public void stop() {
|
||||
if (cache != null) {
|
||||
LOG.debug("Stopping OSCacheProvider...");
|
||||
cache.destroy();
|
||||
cache = null;
|
||||
LOG.debug("OSCacheProvider stopped.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.hibernate.cache.CacheProvider#start(java.util.Properties)
|
||||
*/
|
||||
public void start(Properties hibernateSystemProperties) throws CacheException {
|
||||
if (cache == null) {
|
||||
// construct the cache
|
||||
LOG.debug("Starting OSCacheProvider...");
|
||||
String configResourceName = null;
|
||||
if (hibernateSystemProperties != null) {
|
||||
configResourceName = (String) hibernateSystemProperties.get(OSCACHE_CONFIGURATION_RESOURCE_NAME);
|
||||
}
|
||||
if (StringUtil.isEmpty(configResourceName)) {
|
||||
cache = new GeneralCacheAdministrator();
|
||||
} else {
|
||||
Properties propertiesOSCache = Config.loadProperties(configResourceName, this.getClass().getName());
|
||||
cache = new GeneralCacheAdministrator(propertiesOSCache);
|
||||
}
|
||||
LOG.debug("OSCacheProvider started.");
|
||||
} else {
|
||||
LOG.warn("Tried to restart OSCacheProvider, which is already running.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
@(#)package.html
|
||||
|
||||
Copyright (c) 2002-2007 by OpenSymphony
|
||||
All rights reserved.
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Provides Hibernate 3.2 classes for OSCache.
|
||||
|
||||
|
||||
<h2>Package Specification</h2>
|
||||
|
||||
<h2>Related Documentation</h2>
|
||||
|
||||
|
||||
For overviews, tutorials, examples, guides, and tool documentation, please see:
|
||||
<ul>
|
||||
<li><a href="http://www.opensymphony.com/oscache">The OSCache Homepage</a>
|
||||
</ul>
|
||||
|
||||
<!-- Put @see and @since tags down here. -->
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.plugins.clustersupport;
|
||||
|
||||
import com.opensymphony.oscache.base.*;
|
||||
import com.opensymphony.oscache.base.events.*;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Implementation of a CacheEntryEventListener. It broadcasts the flush events
|
||||
* across a cluster to other listening caches. Note that this listener cannot
|
||||
* be used in conjection with session caches.
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
|
||||
*/
|
||||
public abstract class AbstractBroadcastingListener implements CacheEntryEventListener, LifecycleAware {
|
||||
private final static Log log = LogFactory.getLog(AbstractBroadcastingListener.class);
|
||||
|
||||
/**
|
||||
* The name to use for the origin of cluster events. Using this ensures
|
||||
* events are not fired recursively back over the cluster.
|
||||
*/
|
||||
protected static final String CLUSTER_ORIGIN = "CLUSTER";
|
||||
protected Cache cache = null;
|
||||
|
||||
public AbstractBroadcastingListener() {
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("AbstractBroadcastingListener registered");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event fired when an entry is flushed from the cache. This broadcasts
|
||||
* the flush message to any listening nodes on the network.
|
||||
*/
|
||||
public void cacheEntryFlushed(CacheEntryEvent event) {
|
||||
if (!Cache.NESTED_EVENT.equals(event.getOrigin()) && !CLUSTER_ORIGIN.equals(event.getOrigin())) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("cacheEntryFlushed called (" + event + ")");
|
||||
}
|
||||
|
||||
sendNotification(new ClusterNotification(ClusterNotification.FLUSH_KEY, event.getKey()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event fired when an entry is removed from the cache. This broadcasts
|
||||
* the remove method to any listening nodes on the network, as long as
|
||||
* this event wasn't from a broadcast in the first place.
|
||||
*/
|
||||
public void cacheGroupFlushed(CacheGroupEvent event) {
|
||||
if (!Cache.NESTED_EVENT.equals(event.getOrigin()) && !CLUSTER_ORIGIN.equals(event.getOrigin())) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("cacheGroupFushed called (" + event + ")");
|
||||
}
|
||||
|
||||
sendNotification(new ClusterNotification(ClusterNotification.FLUSH_GROUP, event.getGroup()));
|
||||
}
|
||||
}
|
||||
|
||||
public void cachePatternFlushed(CachePatternEvent event) {
|
||||
if (!Cache.NESTED_EVENT.equals(event.getOrigin()) && !CLUSTER_ORIGIN.equals(event.getOrigin())) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("cachePatternFushed called (" + event + ")");
|
||||
}
|
||||
|
||||
sendNotification(new ClusterNotification(ClusterNotification.FLUSH_PATTERN, event.getPattern()));
|
||||
}
|
||||
}
|
||||
|
||||
public void cacheFlushed(CachewideEvent event) {
|
||||
if (!Cache.NESTED_EVENT.equals(event.getOrigin()) && !CLUSTER_ORIGIN.equals(event.getOrigin())) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("cacheFushed called (" + event + ")");
|
||||
}
|
||||
|
||||
sendNotification(new ClusterNotification(ClusterNotification.FLUSH_CACHE, event.getDate()));
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------
|
||||
// The remaining events are of no interest to this listener
|
||||
// --------------------------------------------------------
|
||||
public void cacheEntryAdded(CacheEntryEvent event) {
|
||||
}
|
||||
|
||||
public void cacheEntryRemoved(CacheEntryEvent event) {
|
||||
}
|
||||
|
||||
public void cacheEntryUpdated(CacheEntryEvent event) {
|
||||
}
|
||||
|
||||
public void cacheGroupAdded(CacheGroupEvent event) {
|
||||
}
|
||||
|
||||
public void cacheGroupEntryAdded(CacheGroupEvent event) {
|
||||
}
|
||||
|
||||
public void cacheGroupEntryRemoved(CacheGroupEvent event) {
|
||||
}
|
||||
|
||||
public void cacheGroupRemoved(CacheGroupEvent event) {
|
||||
}
|
||||
|
||||
public void cacheGroupUpdated(CacheGroupEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the cache administrator class when a cache is instantiated.
|
||||
*
|
||||
* @param cache the cache instance that this listener is attached to.
|
||||
* @param config The cache's configuration details. This allows the event handler
|
||||
* to initialize itself based on the cache settings, and also to receive <em>additional</em>
|
||||
* settings that were part of the cache configuration but that the cache
|
||||
* itself does not care about. If you are using <code>cache.properties</code>
|
||||
* for your configuration, simply add any additional properties that your event
|
||||
* handler requires and they will be passed through in this parameter.
|
||||
*
|
||||
* @throws InitializationException thrown when there was a problem initializing the
|
||||
* listener. The cache administrator will log this error and disable the listener.
|
||||
*/
|
||||
public void initialize(Cache cache, Config config) throws InitializationException {
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles incoming notification messages. This method should be called by the
|
||||
* underlying broadcasting implementation when a message is received from another
|
||||
* node in the cluster.
|
||||
*
|
||||
* @param message The incoming cluster notification message object.
|
||||
*/
|
||||
public void handleClusterNotification(ClusterNotification message) {
|
||||
if (cache == null) {
|
||||
log.warn("A cluster notification (" + message + ") was received, but no cache is registered on this machine. Notification ignored.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("Cluster notification (" + message + ") was received.");
|
||||
}
|
||||
|
||||
switch (message.getType()) {
|
||||
case ClusterNotification.FLUSH_KEY:
|
||||
cache.flushEntry((String) message.getData(), CLUSTER_ORIGIN);
|
||||
break;
|
||||
case ClusterNotification.FLUSH_GROUP:
|
||||
cache.flushGroup((String) message.getData(), CLUSTER_ORIGIN);
|
||||
break;
|
||||
case ClusterNotification.FLUSH_PATTERN:
|
||||
cache.flushPattern((String) message.getData(), CLUSTER_ORIGIN);
|
||||
break;
|
||||
case ClusterNotification.FLUSH_CACHE:
|
||||
cache.flushAll((Date) message.getData(), CLUSTER_ORIGIN);
|
||||
break;
|
||||
default:
|
||||
log.error("The cluster notification (" + message + ") is of an unknown type. Notification ignored.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a cluster notification message is to be broadcast. Implementing
|
||||
* classes should use their underlying transport to broadcast the message across
|
||||
* the cluster.
|
||||
*
|
||||
* @param message The notification message to broadcast.
|
||||
*/
|
||||
abstract protected void sendNotification(ClusterNotification message);
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.plugins.clustersupport;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* A notification message that holds information about a cache event. This
|
||||
* class is <code>Serializable</code> to allow it to be sent across the
|
||||
* network to other machines running in a cluster.
|
||||
*
|
||||
* @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
|
||||
* @author $Author$
|
||||
* @version $Revision$
|
||||
*/
|
||||
public class ClusterNotification implements Serializable {
|
||||
/**
|
||||
* Specifies a notification message that indicates a particular cache key
|
||||
* should be flushed.
|
||||
*/
|
||||
public static final int FLUSH_KEY = 1;
|
||||
|
||||
/**
|
||||
* Specifies a notification message that indicates an entire cache group
|
||||
* should be flushed.
|
||||
*/
|
||||
public static final int FLUSH_GROUP = 2;
|
||||
|
||||
/**
|
||||
* Specifies a notification message that indicates all entries in the cache
|
||||
* that match the specified pattern should be flushed.
|
||||
*/
|
||||
public static final int FLUSH_PATTERN = 3;
|
||||
|
||||
/**
|
||||
* Specifies a notification message indicating that an entire cache should
|
||||
* be flushed.
|
||||
*/
|
||||
public static final int FLUSH_CACHE = 4;
|
||||
|
||||
/**
|
||||
* Any additional data that may be required
|
||||
*/
|
||||
protected Serializable data;
|
||||
|
||||
/**
|
||||
* The type of notification message.
|
||||
*/
|
||||
protected int type;
|
||||
|
||||
/**
|
||||
* Creates a new notification message object to broadcast to other
|
||||
* listening nodes in the cluster.
|
||||
*
|
||||
* @param type The type of notification message. Valid types are
|
||||
* {@link #FLUSH_KEY} and {@link #FLUSH_GROUP}.
|
||||
* @param data Specifies the object key or group name to flush.
|
||||
*/
|
||||
public ClusterNotification(int type, Serializable data) {
|
||||
this.type = type;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds any additional data that was required
|
||||
*/
|
||||
public Serializable getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of notification message.
|
||||
*/
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("type=").append(type).append(", data=").append(data);
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.plugins.clustersupport;
|
||||
|
||||
import com.opensymphony.oscache.base.Cache;
|
||||
import com.opensymphony.oscache.base.Config;
|
||||
import com.opensymphony.oscache.base.FinalizationException;
|
||||
import com.opensymphony.oscache.base.InitializationException;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import javax.jms.*;
|
||||
|
||||
import javax.naming.InitialContext;
|
||||
|
||||
/**
|
||||
* A JMS 1.0.x based clustering implementation. This implementation is independent of
|
||||
* the JMS provider and uses non-persistent messages on a publish subscribe protocol.
|
||||
*
|
||||
* @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
|
||||
*/
|
||||
public class JMS10BroadcastingListener extends AbstractBroadcastingListener {
|
||||
private final static Log log = LogFactory.getLog(JMS10BroadcastingListener.class);
|
||||
|
||||
/**
|
||||
* The name of this cluster. Used to identify the sender of a message.
|
||||
*/
|
||||
private String clusterNode;
|
||||
|
||||
/**
|
||||
*The JMS connection used
|
||||
*/
|
||||
private TopicConnection connection;
|
||||
|
||||
/**
|
||||
* Th object used to publish new messages
|
||||
*/
|
||||
private TopicPublisher publisher;
|
||||
|
||||
/**
|
||||
* The current JMS session
|
||||
*/
|
||||
private TopicSession publisherSession;
|
||||
|
||||
/**
|
||||
* <p>Called by the cache administrator class when a cache is instantiated.</p>
|
||||
* <p>The JMS broadcasting implementation requires the following configuration
|
||||
* properties to be specified in <code>oscache.properties</code>:
|
||||
* <ul>
|
||||
* <li><b>cache.cluster.jms.topic.factory</b> - The JMS connection factory to use</li>
|
||||
* <li><b>cache.cluster.jms.topic.name</b> - The JMS topic name</li>
|
||||
* <li><b>cache.cluster.jms.node.name</b> - The name of this node in the cluster. This should
|
||||
* be unique for each node.</li>
|
||||
* Please refer to the clustering documentation for further details on configuring
|
||||
* the JMS clustered caching.</p>
|
||||
*
|
||||
* @param cache the cache instance that this listener is attached to.
|
||||
*
|
||||
* @throws InitializationException thrown when there was a
|
||||
* problem initializing the listener. The cache administrator will log this error and
|
||||
* disable the listener.
|
||||
*/
|
||||
public void initialize(Cache cache, Config config) throws InitializationException {
|
||||
super.initialize(cache, config);
|
||||
|
||||
// Get the name of this node
|
||||
clusterNode = config.getProperty("cache.cluster.jms.node.name");
|
||||
|
||||
String topic = config.getProperty("cache.cluster.jms.topic.name");
|
||||
String topicFactory = config.getProperty("cache.cluster.jms.topic.factory");
|
||||
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("Starting JMS clustering (node name=" + clusterNode + ", topic=" + topic + ", topic factory=" + topicFactory + ")");
|
||||
}
|
||||
|
||||
try {
|
||||
// Make sure you have specified the necessary JNDI properties (usually in
|
||||
// a jndi.properties resource file, or as system properties)
|
||||
InitialContext jndi = new InitialContext();
|
||||
|
||||
// Look up a JMS connection factory
|
||||
TopicConnectionFactory connectionFactory = (TopicConnectionFactory) jndi.lookup(topicFactory);
|
||||
|
||||
// Create a JMS connection
|
||||
connection = connectionFactory.createTopicConnection();
|
||||
|
||||
// Create session objects
|
||||
publisherSession = connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
|
||||
|
||||
TopicSession subSession = connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
|
||||
|
||||
// Look up the JMS topic
|
||||
Topic chatTopic = (Topic) jndi.lookup(topic);
|
||||
|
||||
// Create the publisher and subscriber
|
||||
publisher = publisherSession.createPublisher(chatTopic);
|
||||
|
||||
TopicSubscriber subscriber = subSession.createSubscriber(chatTopic);
|
||||
|
||||
// Set the message listener
|
||||
subscriber.setMessageListener(new MessageListener() {
|
||||
public void onMessage(Message message) {
|
||||
try {
|
||||
//check the message type
|
||||
ObjectMessage objectMessage = null;
|
||||
|
||||
if (!(message instanceof ObjectMessage)) {
|
||||
log.error("Cannot handle message of type (class=" + message.getClass().getName() + "). Notification ignored.");
|
||||
return;
|
||||
}
|
||||
|
||||
objectMessage = (ObjectMessage) message;
|
||||
|
||||
//check the message content
|
||||
if (!(objectMessage.getObject() instanceof ClusterNotification)) {
|
||||
log.error("An unknown cluster notification message received (class=" + objectMessage.getObject().getClass().getName() + "). Notification ignored.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(objectMessage.getObject());
|
||||
}
|
||||
|
||||
// This prevents the notification sent by this node from being handled by itself
|
||||
if (!objectMessage.getStringProperty("nodeName").equals(clusterNode)) {
|
||||
//now handle the message
|
||||
ClusterNotification notification = (ClusterNotification) objectMessage.getObject();
|
||||
handleClusterNotification(notification);
|
||||
}
|
||||
} catch (JMSException jmsEx) {
|
||||
log.error("Cannot handle cluster Notification", jmsEx);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Start the JMS connection; allows messages to be delivered
|
||||
connection.start();
|
||||
} catch (Exception e) {
|
||||
throw new InitializationException("Initialization of the JMS10BroadcastingListener failed: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the cache administrator class when a cache is destroyed.
|
||||
*
|
||||
* @throws FinalizationException thrown when there was a problem finalizing the
|
||||
* listener. The cache administrator will catch and log this error.
|
||||
*/
|
||||
public void finialize() throws FinalizationException {
|
||||
try {
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("Shutting down JMS clustering...");
|
||||
}
|
||||
|
||||
connection.close();
|
||||
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("JMS clustering shutdown complete.");
|
||||
}
|
||||
} catch (JMSException e) {
|
||||
log.warn("A problem was encountered when closing the JMS connection", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void sendNotification(ClusterNotification message) {
|
||||
try {
|
||||
ObjectMessage objectMessage = publisherSession.createObjectMessage();
|
||||
objectMessage.setObject(message);
|
||||
|
||||
//sign the message, with the name of this node
|
||||
objectMessage.setStringProperty("nodeName", clusterNode);
|
||||
publisher.publish(objectMessage);
|
||||
} catch (JMSException e) {
|
||||
log.error("Cannot send notification " + message, e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.plugins.clustersupport;
|
||||
|
||||
import com.opensymphony.oscache.base.Cache;
|
||||
import com.opensymphony.oscache.base.Config;
|
||||
import com.opensymphony.oscache.base.FinalizationException;
|
||||
import com.opensymphony.oscache.base.InitializationException;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import javax.jms.*;
|
||||
|
||||
import javax.naming.InitialContext;
|
||||
import javax.naming.NamingException;
|
||||
|
||||
/**
|
||||
* A JMS based clustering implementation. This implementation is independent of the
|
||||
* JMS provider and uses non-persistent messages on a publish subscribe protocol.
|
||||
*
|
||||
* @author <a href="mailto:motoras@linuxmail.org">Romulus Pasca</a>
|
||||
*/
|
||||
public class JMSBroadcastingListener extends AbstractBroadcastingListener {
|
||||
|
||||
private final static Log log = LogFactory.getLog(JMSBroadcastingListener.class);
|
||||
|
||||
/**
|
||||
*The JMS connection used
|
||||
*/
|
||||
private Connection connection;
|
||||
|
||||
/**
|
||||
* Th object used to publish new messages
|
||||
*/
|
||||
private MessageProducer messagePublisher;
|
||||
|
||||
/**
|
||||
* The current JMS session
|
||||
*/
|
||||
private Session publisherSession;
|
||||
|
||||
/**
|
||||
* The name of this cluster. Used to identify the sender of a message.
|
||||
*/
|
||||
private String clusterNode;
|
||||
|
||||
/**
|
||||
* <p>Called by the cache administrator class when a cache is instantiated.</p>
|
||||
* <p>The JMS broadcasting implementation requires the following configuration
|
||||
* properties to be specified in <code>oscache.properties</code>:
|
||||
* <ul>
|
||||
* <li><b>cache.cluster.jms.topic.factory</b> - The JMS connection factory to use</li>
|
||||
* <li><b>cache.cluster.jms.topic.name</b> - The JMS topic name</li>
|
||||
* <li><b>cache.cluster.jms.node.name</b> - The name of this node in the cluster. This
|
||||
* should be unique for each node.</li>
|
||||
* Please refer to the clustering documentation for further details on configuring
|
||||
* the JMS clustered caching.</p>
|
||||
*
|
||||
* @param cache the cache instance that this listener is attached to.
|
||||
*
|
||||
* @throws com.opensymphony.oscache.base.InitializationException thrown when there was a
|
||||
* problem initializing the listener. The cache administrator will log this error and
|
||||
* disable the listener.
|
||||
*/
|
||||
public void initialize(Cache cache, Config config) throws InitializationException {
|
||||
super.initialize(cache, config);
|
||||
|
||||
// Get the name of this node
|
||||
clusterNode = config.getProperty("cache.cluster.jms.node.name");
|
||||
|
||||
String topic = config.getProperty("cache.cluster.jms.topic.name");
|
||||
String topicFactory = config.getProperty("cache.cluster.jms.topic.factory");
|
||||
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("Starting JMS clustering (node name=" + clusterNode + ", topic=" + topic + ", topic factory=" + topicFactory + ")");
|
||||
}
|
||||
|
||||
try {
|
||||
// Make sure you have specified the necessary JNDI properties (usually in
|
||||
// a jndi.properties resource file, or as system properties)
|
||||
InitialContext jndi = getInitialContext();
|
||||
|
||||
// Look up a JMS connection factory
|
||||
ConnectionFactory connectionFactory = (ConnectionFactory) jndi.lookup(topicFactory);
|
||||
|
||||
// Create a JMS connection
|
||||
connection = connectionFactory.createConnection();
|
||||
|
||||
// Create session objects
|
||||
publisherSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
|
||||
|
||||
Session subSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
|
||||
|
||||
// Look up the JMS topic
|
||||
Topic chatTopic = (Topic) jndi.lookup(topic);
|
||||
|
||||
// Create the publisher and subscriber
|
||||
messagePublisher = publisherSession.createProducer(chatTopic);
|
||||
|
||||
MessageConsumer messageConsumer = subSession.createConsumer(chatTopic);
|
||||
|
||||
// Set the message listener
|
||||
messageConsumer.setMessageListener(new MessageListener() {
|
||||
public void onMessage(Message message) {
|
||||
try {
|
||||
//check the message type
|
||||
ObjectMessage objectMessage = null;
|
||||
|
||||
if (!(message instanceof ObjectMessage)) {
|
||||
log.error("Cannot handle message of type (class=" + message.getClass().getName() + "). Notification ignored.");
|
||||
return;
|
||||
}
|
||||
|
||||
objectMessage = (ObjectMessage) message;
|
||||
|
||||
//check the message content
|
||||
if (!(objectMessage.getObject() instanceof ClusterNotification)) {
|
||||
log.error("An unknown cluster notification message received (class=" + objectMessage.getObject().getClass().getName() + "). Notification ignored.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(objectMessage.getObject());
|
||||
}
|
||||
|
||||
// This prevents the notification sent by this node from being handled by itself
|
||||
if (!objectMessage.getStringProperty("nodeName").equals(clusterNode)) {
|
||||
//now handle the message
|
||||
ClusterNotification notification = (ClusterNotification) objectMessage.getObject();
|
||||
handleClusterNotification(notification);
|
||||
}
|
||||
} catch (JMSException jmsEx) {
|
||||
log.error("Cannot handle cluster Notification", jmsEx);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Start the JMS connection; allows messages to be delivered
|
||||
connection.start();
|
||||
} catch (Exception e) {
|
||||
throw new InitializationException("Initialization of the JMSBroadcastingListener failed: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the cache administrator class when a cache is destroyed.
|
||||
*
|
||||
* @throws com.opensymphony.oscache.base.FinalizationException thrown when there was a problem finalizing the
|
||||
* listener. The cache administrator will catch and log this error.
|
||||
*/
|
||||
public void finialize() throws FinalizationException {
|
||||
try {
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("Shutting down JMS clustering...");
|
||||
}
|
||||
|
||||
connection.close();
|
||||
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("JMS clustering shutdown complete.");
|
||||
}
|
||||
} catch (JMSException e) {
|
||||
log.warn("A problem was encountered when closing the JMS connection", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void sendNotification(ClusterNotification message) {
|
||||
try {
|
||||
ObjectMessage objectMessage = publisherSession.createObjectMessage();
|
||||
objectMessage.setObject(message);
|
||||
|
||||
//sign the message, with the name of this node
|
||||
objectMessage.setStringProperty("nodeName", clusterNode);
|
||||
messagePublisher.send(objectMessage);
|
||||
} catch (JMSException e) {
|
||||
log.error("Cannot send notification " + message, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return creates a context for performing naming operations.
|
||||
* @throws NamingException if a naming exception is encountered
|
||||
*/
|
||||
protected InitialContext getInitialContext() throws NamingException {
|
||||
return new InitialContext();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.plugins.clustersupport;
|
||||
|
||||
import com.opensymphony.oscache.base.Cache;
|
||||
import com.opensymphony.oscache.base.Config;
|
||||
import com.opensymphony.oscache.base.FinalizationException;
|
||||
import com.opensymphony.oscache.base.InitializationException;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.jgroups.Address;
|
||||
import org.jgroups.Channel;
|
||||
|
||||
import org.jgroups.blocks.NotificationBus;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* <p>A concrete implementation of the {@link AbstractBroadcastingListener} based on
|
||||
* the JavaGroups library. This Class uses JavaGroups to broadcast cache flush
|
||||
* messages across a cluster.</p>
|
||||
*
|
||||
* <p>One of the following properties should be configured in <code>oscache.properties</code> for
|
||||
* this listener:
|
||||
* <ul>
|
||||
* <li><b>cache.cluster.multicast.ip</b> - The multicast IP that JavaGroups should use for broadcasting</li>
|
||||
* <li><b>cache.cluster.properties</b> - The JavaGroups channel properties to use. Allows for precise
|
||||
* control over the behaviour of JavaGroups</li>
|
||||
* </ul>
|
||||
* Please refer to the clustering documentation for further details on the configuration of this listener.</p>
|
||||
*
|
||||
* @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
|
||||
*/
|
||||
public class JavaGroupsBroadcastingListener extends AbstractBroadcastingListener implements NotificationBus.Consumer {
|
||||
private final static Log log = LogFactory.getLog(JavaGroupsBroadcastingListener.class);
|
||||
private static final String BUS_NAME = "OSCacheBus";
|
||||
private static final String CHANNEL_PROPERTIES = "cache.cluster.properties";
|
||||
private static final String MULTICAST_IP_PROPERTY = "cache.cluster.multicast.ip";
|
||||
|
||||
/**
|
||||
* The first half of the default channel properties. They default channel properties are:
|
||||
* <pre>
|
||||
* UDP(mcast_addr=*.*.*.*;mcast_port=45566;ip_ttl=32;\
|
||||
* mcast_send_buf_size=150000;mcast_recv_buf_size=80000):\
|
||||
* PING(timeout=2000;num_initial_members=3):\
|
||||
* MERGE2(min_interval=5000;max_interval=10000):\
|
||||
* FD_SOCK:VERIFY_SUSPECT(timeout=1500):\
|
||||
* pbcast.NAKACK(gc_lag=50;retransmit_timeout=300,600,1200,2400,4800;max_xmit_size=8192):\
|
||||
* UNICAST(timeout=300,600,1200,2400):\
|
||||
* pbcast.STABLE(desired_avg_gossip=20000):\
|
||||
* FRAG(frag_size=8096;down_thread=false;up_thread=false):\
|
||||
* pbcast.GMS(join_timeout=5000;join_retry_timeout=2000;shun=false;print_local_addr=true)
|
||||
* </pre>
|
||||
*
|
||||
* Where <code>*.*.*.*</code> is the specified multicast IP, which defaults to <code>231.12.21.132</code>.
|
||||
*/
|
||||
private static final String DEFAULT_CHANNEL_PROPERTIES_PRE = "UDP(mcast_addr=";
|
||||
|
||||
/**
|
||||
* The second half of the default channel properties. They default channel properties are:
|
||||
* <pre>
|
||||
* UDP(mcast_addr=*.*.*.*;mcast_port=45566;ip_ttl=32;\
|
||||
* mcast_send_buf_size=150000;mcast_recv_buf_size=80000):\
|
||||
* PING(timeout=2000;num_initial_members=3):\
|
||||
* MERGE2(min_interval=5000;max_interval=10000):\
|
||||
* FD_SOCK:VERIFY_SUSPECT(timeout=1500):\
|
||||
* pbcast.NAKACK(gc_lag=50;retransmit_timeout=300,600,1200,2400,4800;max_xmit_size=8192):\
|
||||
* UNICAST(timeout=300,600,1200,2400):\
|
||||
* pbcast.STABLE(desired_avg_gossip=20000):\
|
||||
* FRAG(frag_size=8096;down_thread=false;up_thread=false):\
|
||||
* pbcast.GMS(join_timeout=5000;join_retry_timeout=2000;shun=false;print_local_addr=true)
|
||||
* </pre>
|
||||
*
|
||||
* Where <code>*.*.*.*</code> is the specified multicast IP, which defaults to <code>231.12.21.132</code>.
|
||||
*/
|
||||
private static final String DEFAULT_CHANNEL_PROPERTIES_POST = ";mcast_port=45566;ip_ttl=32;mcast_send_buf_size=150000;mcast_recv_buf_size=80000):" + "PING(timeout=2000;num_initial_members=3):MERGE2(min_interval=5000;max_interval=10000):FD_SOCK:VERIFY_SUSPECT(timeout=1500):" + "pbcast.NAKACK(gc_lag=50;retransmit_timeout=300,600,1200,2400,4800;max_xmit_size=8192):UNICAST(timeout=300,600,1200,2400):pbcast.STABLE(desired_avg_gossip=20000):" + "FRAG(frag_size=8096;down_thread=false;up_thread=false):pbcast.GMS(join_timeout=5000;join_retry_timeout=2000;shun=false;print_local_addr=true)";
|
||||
private static final String DEFAULT_MULTICAST_IP = "231.12.21.132";
|
||||
private NotificationBus bus;
|
||||
|
||||
/**
|
||||
* Initializes the broadcasting listener by starting up a JavaGroups notification
|
||||
* bus instance to handle incoming and outgoing messages.
|
||||
*
|
||||
* @param config An OSCache configuration object.
|
||||
* @throws com.opensymphony.oscache.base.InitializationException If this listener has
|
||||
* already been initialized.
|
||||
*/
|
||||
public synchronized void initialize(Cache cache, Config config) throws InitializationException {
|
||||
super.initialize(cache, config);
|
||||
|
||||
String properties = config.getProperty(CHANNEL_PROPERTIES);
|
||||
String multicastIP = config.getProperty(MULTICAST_IP_PROPERTY);
|
||||
|
||||
if ((properties == null) && (multicastIP == null)) {
|
||||
multicastIP = DEFAULT_MULTICAST_IP;
|
||||
}
|
||||
|
||||
if (properties == null) {
|
||||
properties = DEFAULT_CHANNEL_PROPERTIES_PRE + multicastIP.trim() + DEFAULT_CHANNEL_PROPERTIES_POST;
|
||||
} else {
|
||||
properties = properties.trim();
|
||||
}
|
||||
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("Starting a new JavaGroups broadcasting listener with properties=" + properties);
|
||||
}
|
||||
|
||||
try {
|
||||
bus = new NotificationBus(BUS_NAME, properties);
|
||||
bus.start();
|
||||
bus.getChannel().setOpt(Channel.LOCAL, new Boolean(false));
|
||||
bus.setConsumer(this);
|
||||
log.info("JavaGroups clustering support started successfully");
|
||||
} catch (Exception e) {
|
||||
throw new InitializationException("Initialization failed: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts down the JavaGroups being managed by this listener. This
|
||||
* occurs once the cache is shut down and this listener is no longer
|
||||
* in use.
|
||||
*
|
||||
* @throws com.opensymphony.oscache.base.FinalizationException
|
||||
*/
|
||||
public synchronized void finialize() throws FinalizationException {
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("JavaGroups shutting down...");
|
||||
}
|
||||
|
||||
// It's possible that the notification bus is null (CACHE-154)
|
||||
if (bus != null) {
|
||||
bus.stop();
|
||||
bus = null;
|
||||
} else {
|
||||
log.warn("Notification bus wasn't initialized or finialize was invoked before!");
|
||||
}
|
||||
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("JavaGroups shutdown complete.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses JavaGroups to broadcast the supplied notification message across the cluster.
|
||||
*
|
||||
* @param message The cluster nofication message to broadcast.
|
||||
*/
|
||||
protected void sendNotification(ClusterNotification message) {
|
||||
bus.sendNotification(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles incoming notification messages from JavaGroups. This method should
|
||||
* never be called directly.
|
||||
*
|
||||
* @param serializable The incoming message object. This must be a {@link ClusterNotification}.
|
||||
*/
|
||||
public void handleNotification(Serializable serializable) {
|
||||
if (!(serializable instanceof ClusterNotification)) {
|
||||
log.error("An unknown cluster notification message received (class=" + serializable.getClass().getName() + "). Notification ignored.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
handleClusterNotification((ClusterNotification) serializable);
|
||||
}
|
||||
|
||||
/**
|
||||
* We are not using the caching, so we just return something that identifies
|
||||
* us. This method should never be called directly.
|
||||
*/
|
||||
public Serializable getCache() {
|
||||
return "JavaGroupsBroadcastingListener: " + bus.getLocalAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback that is fired when a new member joins the cluster. This
|
||||
* method should never be called directly.
|
||||
*
|
||||
* @param address The address of the member who just joined.
|
||||
*/
|
||||
public void memberJoined(Address address) {
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("A new member at address '" + address + "' has joined the cluster");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback that is fired when an existing member leaves the cluster.
|
||||
* This method should never be called directly.
|
||||
*
|
||||
* @param address The address of the member who left.
|
||||
*/
|
||||
public void memberLeft(Address address) {
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("Member at address '" + address + "' left the cluster");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
@(#)package.html
|
||||
|
||||
Copyright (c) 2002-2003 by OpenSymphony
|
||||
All rights reserved.
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Provides support for broadcasting flush events so that OSCache can function across a
|
||||
cluster.
|
||||
|
||||
|
||||
<h2>Package Specification</h2>
|
||||
|
||||
<h2>Related Documentation</h2>
|
||||
|
||||
|
||||
For overviews, tutorials, examples, guides, and tool documentation, please see:
|
||||
<ul>
|
||||
<li><a href="http://www.opensymphony.com/oscache">The OSCache Homepage</a>
|
||||
<li><a href="http://www.opensymphony.com/oscache/clustering.jsp">Clustering OSCache</a>
|
||||
</ul>
|
||||
|
||||
<!-- Put @see and @since tags down here. -->
|
||||
@since 2.0
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,529 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.plugins.diskpersistence;
|
||||
|
||||
import com.opensymphony.oscache.base.Config;
|
||||
import com.opensymphony.oscache.base.persistence.CachePersistenceException;
|
||||
import com.opensymphony.oscache.base.persistence.PersistenceListener;
|
||||
import com.opensymphony.oscache.web.ServletCacheAdministrator;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.jsp.PageContext;
|
||||
|
||||
/**
|
||||
* Persist the cache data to disk.
|
||||
*
|
||||
* The code in this class is totally not thread safe it is the resonsibility
|
||||
* of the cache using this persistence listener to handle the concurrency.
|
||||
*
|
||||
* @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
|
||||
* @author <a href="mailto:abergevin@pyxis-tech.com">Alain Bergevin</a>
|
||||
* @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
|
||||
* @author <a href="mailto:amarch@soe.sony.com">Andres March</a>
|
||||
*/
|
||||
public abstract class AbstractDiskPersistenceListener implements PersistenceListener, Serializable {
|
||||
|
||||
private static final long serialVersionUID = 6679402628276452293L;
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(AbstractDiskPersistenceListener.class);
|
||||
|
||||
/** lock for workaround the Sun Bug ID 4742723 */
|
||||
private static final Object MKDIRS_LOCK = new Object();
|
||||
|
||||
public final static String CACHE_PATH_KEY = "cache.path";
|
||||
|
||||
/**
|
||||
* File extension for disk cache file
|
||||
*/
|
||||
protected final static String CACHE_EXTENSION = "cache";
|
||||
|
||||
/**
|
||||
* The directory that cache groups are stored under
|
||||
*/
|
||||
protected final static String GROUP_DIRECTORY = "__groups__";
|
||||
|
||||
/**
|
||||
* Sub path name for application cache
|
||||
*/
|
||||
protected final static String APPLICATION_CACHE_SUBPATH = "application";
|
||||
|
||||
/**
|
||||
* Sub path name for session cache
|
||||
*/
|
||||
protected final static String SESSION_CACHE_SUBPATH = "session";
|
||||
|
||||
/**
|
||||
* Property to get the temporary working directory of the servlet container.
|
||||
*/
|
||||
protected static final String CONTEXT_TMPDIR = "javax.servlet.context.tempdir";
|
||||
private static transient final Log log = LogFactory.getLog(AbstractDiskPersistenceListener.class);
|
||||
|
||||
/**
|
||||
* Base path where the disk cache reside.
|
||||
*/
|
||||
private File cachePath = null;
|
||||
private File contextTmpDir;
|
||||
|
||||
/**
|
||||
* Root path for disk cache
|
||||
*/
|
||||
private String root = null;
|
||||
|
||||
/**
|
||||
* Get the physical cache path on disk.
|
||||
*
|
||||
* @return A file representing the physical cache location.
|
||||
*/
|
||||
public File getCachePath() {
|
||||
return cachePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the root directory for persisting the cache on disk.
|
||||
* This path includes scope and sessionId, if any.
|
||||
*
|
||||
* @return A String representing the root directory.
|
||||
*/
|
||||
public String getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the servlet context tmp directory.
|
||||
*
|
||||
* @return A file representing the servlet context tmp directory.
|
||||
*/
|
||||
public File getContextTmpDir() {
|
||||
return contextTmpDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if a group exists in the cache
|
||||
*
|
||||
* @param group The group name to check
|
||||
* @return True if it exists
|
||||
* @throws CachePersistenceException
|
||||
*/
|
||||
public boolean isGroupStored(String group) throws CachePersistenceException {
|
||||
try {
|
||||
File file = getCacheGroupFile(group);
|
||||
|
||||
return file.exists();
|
||||
} catch (Exception e) {
|
||||
throw new CachePersistenceException("Unable verify group '" + group + "' exists in the cache: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if an object is currently stored in the cache
|
||||
*
|
||||
* @param key The object key
|
||||
* @return True if it exists
|
||||
* @throws CachePersistenceException
|
||||
*/
|
||||
public boolean isStored(String key) throws CachePersistenceException {
|
||||
try {
|
||||
File file = getCacheFile(key);
|
||||
|
||||
return file.exists();
|
||||
} catch (Exception e) {
|
||||
throw new CachePersistenceException("Unable verify id '" + key + "' is stored in the cache: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the whole cache directory, starting from the root
|
||||
*
|
||||
* @throws CachePersistenceException
|
||||
*/
|
||||
public void clear() throws CachePersistenceException {
|
||||
clear(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises this <tt>DiskPersistenceListener</tt> using the supplied
|
||||
* configuration.
|
||||
*
|
||||
* @param config The OSCache configuration
|
||||
*/
|
||||
public PersistenceListener configure(Config config) {
|
||||
String sessionId = null;
|
||||
int scope = 0;
|
||||
initFileCaching(config.getProperty(CACHE_PATH_KEY));
|
||||
|
||||
if (config.getProperty(ServletCacheAdministrator.HASH_KEY_SESSION_ID) != null) {
|
||||
sessionId = config.getProperty(ServletCacheAdministrator.HASH_KEY_SESSION_ID);
|
||||
}
|
||||
|
||||
if (config.getProperty(ServletCacheAdministrator.HASH_KEY_SCOPE) != null) {
|
||||
scope = Integer.parseInt(config.getProperty(ServletCacheAdministrator.HASH_KEY_SCOPE));
|
||||
}
|
||||
|
||||
StringBuffer root = new StringBuffer(getCachePath().getPath());
|
||||
root.append("/");
|
||||
root.append(getPathPart(scope));
|
||||
|
||||
if ((sessionId != null) && (sessionId.length() > 0)) {
|
||||
root.append("/");
|
||||
root.append(sessionId);
|
||||
}
|
||||
|
||||
this.root = root.toString();
|
||||
this.contextTmpDir = (File) config.get(ServletCacheAdministrator.HASH_KEY_CONTEXT_TMPDIR);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a single cache entry.
|
||||
*
|
||||
* @param key The object key to delete
|
||||
* @throws CachePersistenceException
|
||||
*/
|
||||
public void remove(String key) throws CachePersistenceException {
|
||||
File file = getCacheFile(key);
|
||||
remove(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an entire group from the cache.
|
||||
*
|
||||
* @param groupName The name of the group to delete
|
||||
* @throws CachePersistenceException
|
||||
*/
|
||||
public void removeGroup(String groupName) throws CachePersistenceException {
|
||||
File file = getCacheGroupFile(groupName);
|
||||
remove(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an object from the disk
|
||||
*
|
||||
* @param key The object key
|
||||
* @return The retrieved object
|
||||
* @throws CachePersistenceException
|
||||
*/
|
||||
public Object retrieve(String key) throws CachePersistenceException {
|
||||
return retrieve(getCacheFile(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a group from the cache, or <code>null</code> if the group
|
||||
* file could not be found.
|
||||
*
|
||||
* @param groupName The name of the group to retrieve.
|
||||
* @return A <code>Set</code> containing keys of all of the cache
|
||||
* entries that belong to this group.
|
||||
* @throws CachePersistenceException
|
||||
*/
|
||||
public Set retrieveGroup(String groupName) throws CachePersistenceException {
|
||||
File groupFile = getCacheGroupFile(groupName);
|
||||
|
||||
try {
|
||||
return (Set) retrieve(groupFile);
|
||||
} catch (ClassCastException e) {
|
||||
throw new CachePersistenceException("Group file " + groupFile + " was not persisted as a Set: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores an object in cache
|
||||
*
|
||||
* @param key The object's key
|
||||
* @param obj The object to store
|
||||
* @throws CachePersistenceException
|
||||
*/
|
||||
public void store(String key, Object obj) throws CachePersistenceException {
|
||||
File file = getCacheFile(key);
|
||||
store(file, obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a group in the persistent cache. This will overwrite any existing
|
||||
* group with the same name
|
||||
*/
|
||||
public void storeGroup(String groupName, Set group) throws CachePersistenceException {
|
||||
File groupFile = getCacheGroupFile(groupName);
|
||||
store(groupFile, group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to translate to the temp dir of the servlet container if cachePathStr
|
||||
* is javax.servlet.context.tempdir.
|
||||
*
|
||||
* @param cachePathStr Cache path read from the properties file.
|
||||
* @return Adjusted cache path
|
||||
*/
|
||||
protected String adjustFileCachePath(String cachePathStr) {
|
||||
if (cachePathStr.compareToIgnoreCase(CONTEXT_TMPDIR) == 0) {
|
||||
cachePathStr = contextTmpDir.getAbsolutePath();
|
||||
}
|
||||
|
||||
return cachePathStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set caching to file on or off.
|
||||
* If the <code>cache.path</code> property exists, we assume file caching is turned on.
|
||||
* By the same token, to turn off file caching just remove this property.
|
||||
*/
|
||||
protected void initFileCaching(String cachePathStr) {
|
||||
if (cachePathStr != null) {
|
||||
cachePath = new File(cachePathStr);
|
||||
|
||||
try {
|
||||
if (!cachePath.exists()) {
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("cache.path '" + cachePathStr + "' does not exist, creating");
|
||||
}
|
||||
|
||||
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4742723
|
||||
synchronized (MKDIRS_LOCK) {
|
||||
cachePath.mkdirs();
|
||||
}
|
||||
}
|
||||
|
||||
if (!cachePath.isDirectory()) {
|
||||
log.error("cache.path '" + cachePathStr + "' is not a directory");
|
||||
cachePath = null;
|
||||
} else if (!cachePath.canWrite()) {
|
||||
log.error("cache.path '" + cachePathStr + "' is not a writable location");
|
||||
cachePath = null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("cache.path '" + cachePathStr + "' could not be used", e);
|
||||
cachePath = null;
|
||||
}
|
||||
} else {
|
||||
// Use default value
|
||||
}
|
||||
}
|
||||
|
||||
// try 30s to delete the file
|
||||
private static final long DELETE_THREAD_SLEEP = 500;
|
||||
private static final int DELETE_COUNT = 60;
|
||||
|
||||
protected void remove(File file) throws CachePersistenceException {
|
||||
int count = DELETE_COUNT;
|
||||
try {
|
||||
// Loop until we are able to delete (No current read).
|
||||
// The cache must ensure that there are never two concurrent threads
|
||||
// doing write (store and delete) operations on the same item.
|
||||
// Delete only should be enough but file.exists prevents infinite loop
|
||||
while (file.exists() && !file.delete() && count != 0) {
|
||||
count--;
|
||||
try {
|
||||
Thread.sleep(DELETE_THREAD_SLEEP);
|
||||
} catch (InterruptedException ignore) {
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new CachePersistenceException("Unable to remove file '" + file + "' from the disk cache.", e);
|
||||
}
|
||||
if (file.exists() && count == 0) {
|
||||
throw new CachePersistenceException("Unable to delete '" + file + "' from the disk cache. "+DELETE_COUNT+" attempts at "+DELETE_THREAD_SLEEP+" milliseconds intervals.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores an object using the supplied file object
|
||||
*
|
||||
* @param file The file to use for storing the object
|
||||
* @param obj the object to store
|
||||
* @throws CachePersistenceException
|
||||
*/
|
||||
protected void store(File file, Object obj) throws CachePersistenceException {
|
||||
// check if file exists before testing if parent exists
|
||||
if (!file.exists()) {
|
||||
// check if the directory structure required exists and create it if it doesn't
|
||||
File filepath = new File(file.getParent());
|
||||
|
||||
try {
|
||||
if (!filepath.exists()) {
|
||||
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4742723
|
||||
synchronized (MKDIRS_LOCK) {
|
||||
filepath.mkdirs();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new CachePersistenceException("Unable to create the directory " + filepath, e);
|
||||
}
|
||||
}
|
||||
|
||||
// Write the object to disk
|
||||
try {
|
||||
FileOutputStream fout = new FileOutputStream(file);
|
||||
try {
|
||||
ObjectOutputStream oout = new ObjectOutputStream(new BufferedOutputStream(fout));
|
||||
try {
|
||||
oout.writeObject(obj);
|
||||
oout.flush();
|
||||
} finally {
|
||||
try {
|
||||
oout.close();
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Problem closing file of disk cache.", e);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
fout.close();
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Problem closing file of disk cache.", e);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
int count = DELETE_COUNT;
|
||||
while (file.exists() && !file.delete() && count != 0) {
|
||||
count--;
|
||||
try {
|
||||
Thread.sleep(DELETE_THREAD_SLEEP);
|
||||
} catch (InterruptedException ignore) {
|
||||
}
|
||||
}
|
||||
throw new CachePersistenceException("Unable to write file '" + file + "' in the disk cache.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build fully qualified cache file for the specified cache entry key.
|
||||
*
|
||||
* @param key Cache Entry Key.
|
||||
* @return File reference.
|
||||
*/
|
||||
protected File getCacheFile(String key) {
|
||||
char[] fileChars = getCacheFileName(key);
|
||||
|
||||
File file = new File(root, new String(fileChars) + "." + CACHE_EXTENSION);
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build cache file name for the specified cache entry key.
|
||||
*
|
||||
* @param key Cache Entry Key.
|
||||
* @return char[] file name.
|
||||
*/
|
||||
protected abstract char[] getCacheFileName(String key);
|
||||
|
||||
/**
|
||||
* Builds a fully qualified file name that specifies a cache group entry.
|
||||
*
|
||||
* @param group The name of the group
|
||||
* @return A File reference
|
||||
*/
|
||||
private File getCacheGroupFile(String group) {
|
||||
int AVERAGE_PATH_LENGTH = 30;
|
||||
|
||||
if ((group == null) || (group.length() == 0)) {
|
||||
throw new IllegalArgumentException("Invalid group '" + group + "' specified to getCacheGroupFile.");
|
||||
}
|
||||
|
||||
StringBuffer path = new StringBuffer(AVERAGE_PATH_LENGTH);
|
||||
|
||||
// Build a fully qualified file name for this group
|
||||
path.append(GROUP_DIRECTORY).append('/');
|
||||
path.append(getCacheFileName(group)).append('.').append(CACHE_EXTENSION);
|
||||
|
||||
return new File(root, path.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* This allows to persist different scopes in different path in the case of
|
||||
* file caching.
|
||||
*
|
||||
* @param scope Cache scope.
|
||||
* @return The scope subpath
|
||||
*/
|
||||
private String getPathPart(int scope) {
|
||||
if (scope == PageContext.SESSION_SCOPE) {
|
||||
return SESSION_CACHE_SUBPATH;
|
||||
} else {
|
||||
return APPLICATION_CACHE_SUBPATH;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears a whole directory, starting from the specified
|
||||
* directory
|
||||
*
|
||||
* @param baseDirName The root directory to delete
|
||||
* @throws CachePersistenceException
|
||||
*/
|
||||
private void clear(String baseDirName) throws CachePersistenceException {
|
||||
File baseDir = new File(baseDirName);
|
||||
File[] fileList = baseDir.listFiles();
|
||||
|
||||
try {
|
||||
if (fileList != null) {
|
||||
// Loop through all the files and directory to delete them
|
||||
for (int count = 0; count < fileList.length; count++) {
|
||||
if (fileList[count].isFile()) {
|
||||
fileList[count].delete();
|
||||
} else {
|
||||
// Make a recursive call to delete the directory
|
||||
clear(fileList[count].toString());
|
||||
fileList[count].delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the root directory
|
||||
baseDir.delete();
|
||||
} catch (Exception e) {
|
||||
throw new CachePersistenceException("Unable to clear the cache directory");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrives a serialized object from the supplied file, or returns
|
||||
* <code>null</code> if the file does not exist.
|
||||
*
|
||||
* @param file The file to deserialize
|
||||
* @return The deserialized object
|
||||
* @throws CachePersistenceException
|
||||
*/
|
||||
private Object retrieve(File file) throws CachePersistenceException {
|
||||
Object readContent = null;
|
||||
boolean fileExist;
|
||||
|
||||
try {
|
||||
fileExist = file.exists();
|
||||
} catch (Exception e) {
|
||||
throw new CachePersistenceException("Unable to verify if file '" + file + "' exists.", e);
|
||||
}
|
||||
|
||||
// Read the file if it exists
|
||||
if (fileExist) {
|
||||
ObjectInputStream oin = null;
|
||||
|
||||
try {
|
||||
BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));
|
||||
oin = new ObjectInputStream(in);
|
||||
readContent = oin.readObject();
|
||||
} catch (Exception e) {
|
||||
// We expect this exception to occur.
|
||||
// This is when the item will be invalidated (written or deleted)
|
||||
// during read.
|
||||
// The cache has the logic to retry reading.
|
||||
throw new CachePersistenceException("Unable to read file '" + file.getAbsolutePath() + "' from the disk cache.", e);
|
||||
} finally {
|
||||
// HHDE: no need to close in. Will be closed by oin
|
||||
try {
|
||||
oin.close();
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return readContent;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.plugins.diskpersistence;
|
||||
|
||||
|
||||
/**
|
||||
* Persist the cache data to disk.
|
||||
*
|
||||
* The code in this class is totally not thread safe it is the resonsibility
|
||||
* of the cache using this persistence listener to handle the concurrency.
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
|
||||
* @author <a href="mailto:abergevin@pyxis-tech.com">Alain Bergevin</a>
|
||||
* @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
|
||||
*/
|
||||
public class DiskPersistenceListener extends AbstractDiskPersistenceListener {
|
||||
private static final String CHARS_TO_CONVERT = "./\\ :;\"\'_?";
|
||||
|
||||
/**
|
||||
* Build cache file name for the specified cache entry key.
|
||||
*
|
||||
* @param key Cache Entry Key.
|
||||
* @return char[] file name.
|
||||
*/
|
||||
protected char[] getCacheFileName(String key) {
|
||||
if ((key == null) || (key.length() == 0)) {
|
||||
throw new IllegalArgumentException("Invalid key '" + key + "' specified to getCacheFile.");
|
||||
}
|
||||
|
||||
char[] chars = key.toCharArray();
|
||||
|
||||
StringBuffer sb = new StringBuffer(chars.length + 8);
|
||||
|
||||
for (int i = 0; i < chars.length; i++) {
|
||||
char c = chars[i];
|
||||
int pos = CHARS_TO_CONVERT.indexOf(c);
|
||||
|
||||
if (pos >= 0) {
|
||||
sb.append('_');
|
||||
sb.append(i);
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
char[] fileChars = new char[sb.length()];
|
||||
sb.getChars(0, fileChars.length, fileChars, 0);
|
||||
return fileChars;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2007 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.plugins.diskpersistence;
|
||||
|
||||
import com.opensymphony.oscache.base.Config;
|
||||
import com.opensymphony.oscache.base.persistence.PersistenceListener;
|
||||
|
||||
import java.io.File;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Persists cache data to disk. Provides a hash of the standard key name as the file name.
|
||||
*
|
||||
* A configurable hash algorithm is used to create a digest of the cache key for the
|
||||
* disk filename. This is to allow for more sane filenames for objects which dont generate
|
||||
* friendly cache keys.
|
||||
*
|
||||
* @author <a href="mailto:jparrott@soe.sony.com">Jason Parrott</a>
|
||||
*/
|
||||
public class HashDiskPersistenceListener extends AbstractDiskPersistenceListener {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(HashDiskPersistenceListener.class);
|
||||
|
||||
private static final int DIR_LEVELS = 3;
|
||||
|
||||
public final static String HASH_ALGORITHM_KEY = "cache.persistence.disk.hash.algorithm";
|
||||
public final static String DEFAULT_HASH_ALGORITHM = "MD5";
|
||||
protected MessageDigest md = null;
|
||||
|
||||
/**
|
||||
* Initializes the <tt>HashDiskPersistenceListener</tt>. Namely this involves only setting up the
|
||||
* message digester to hash the key values.
|
||||
* @see com.opensymphony.oscache.base.persistence.PersistenceListener#configure(com.opensymphony.oscache.base.Config)
|
||||
*/
|
||||
public PersistenceListener configure(Config config) {
|
||||
try {
|
||||
if (config.getProperty(HashDiskPersistenceListener.HASH_ALGORITHM_KEY) != null) {
|
||||
try {
|
||||
md = MessageDigest.getInstance(config.getProperty(HashDiskPersistenceListener.HASH_ALGORITHM_KEY));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
md = MessageDigest.getInstance(HashDiskPersistenceListener.DEFAULT_HASH_ALGORITHM);
|
||||
}
|
||||
} else {
|
||||
md = MessageDigest.getInstance(HashDiskPersistenceListener.DEFAULT_HASH_ALGORITHM);
|
||||
}
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
LOG.warn("No hash algorithm available for disk persistence", e);
|
||||
throw new RuntimeException("No hash algorithm available for disk persistence", e);
|
||||
}
|
||||
|
||||
return super.configure(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a file name for the given cache key. In this case the file name is attempted to be
|
||||
* generated from the hash of the standard key name. Cache algorithm is configured via the
|
||||
* <em>cache.persistence.disk.hash.algorithm</em> configuration variable.
|
||||
* @param key cache entry key
|
||||
* @return char[] file name
|
||||
*/
|
||||
protected synchronized char[] getCacheFileName(String key) {
|
||||
if ((key == null) || (key.length() == 0)) {
|
||||
throw new IllegalArgumentException("Invalid key '" + key + "' specified to getCacheFile.");
|
||||
}
|
||||
|
||||
String hexDigest = byteArrayToHexString(md.digest(key.getBytes()));
|
||||
|
||||
// CACHE-249: Performance improvement for large disk persistence usage
|
||||
StringBuffer filename = new StringBuffer(hexDigest.length() + 2 * DIR_LEVELS);
|
||||
for (int i=0; i < DIR_LEVELS; i++) {
|
||||
filename.append(hexDigest.charAt(i)).append(File.separator);
|
||||
}
|
||||
filename.append(hexDigest);
|
||||
|
||||
return filename.toString().toCharArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Nibble conversion. Thanks to our friends at:
|
||||
* http://www.devx.com/tips/Tip/13540
|
||||
* @param in the byte array to convert
|
||||
* @return a java.lang.String based version of they byte array
|
||||
*/
|
||||
static String byteArrayToHexString(byte[] in) {
|
||||
if ((in == null) || (in.length <= 0)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuffer out = new StringBuffer(in.length * 2);
|
||||
|
||||
for (int i = 0; i < in.length; i++) {
|
||||
byte ch = (byte) (in[i] & 0xF0); // Strip off high nibble
|
||||
ch = (byte) (ch >>> 4);
|
||||
|
||||
// shift the bits down
|
||||
ch = (byte) (ch & 0x0F);
|
||||
|
||||
// must do this is high order bit is on!
|
||||
out.append(PSEUDO[(int) ch]); // convert the nibble to a String Character
|
||||
ch = (byte) (in[i] & 0x0F); // Strip off low nibble
|
||||
out.append(PSEUDO[(int) ch]); // convert the nibble to a String Character
|
||||
}
|
||||
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
static final String[] PSEUDO = {
|
||||
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D",
|
||||
"E", "F"
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
@(#)package.html
|
||||
|
||||
Copyright (c) 2002-2003 by OpenSymphony
|
||||
All rights reserved.
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Provides support for persisting cached objects to disk.
|
||||
|
||||
|
||||
<h2>Package Specification</h2>
|
||||
|
||||
<h2>Related Documentation</h2>
|
||||
|
||||
|
||||
For overviews, tutorials, examples, guides, and tool documentation, please see:
|
||||
<ul>
|
||||
<li><a href="http://www.opensymphony.com/oscache">The OSCache Homepage</a>
|
||||
</ul>
|
||||
|
||||
<!-- Put @see and @since tags down here. -->
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.util;
|
||||
|
||||
|
||||
/**
|
||||
* <p>This code is borrowed directly from OSCore, but is duplicated
|
||||
* here to avoid having to add a dependency on the entire OSCore jar.</p>
|
||||
*
|
||||
* <p>If much more code from OSCore is needed then it might be wiser to
|
||||
* bite the bullet and add a dependency.</p>
|
||||
*/
|
||||
public class ClassLoaderUtil {
|
||||
|
||||
private ClassLoaderUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a class with a given name.
|
||||
*
|
||||
* It will try to load the class in the following order:
|
||||
* <ul>
|
||||
* <li>From Thread.currentThread().getContextClassLoader()
|
||||
* <li>Using the basic Class.forName()
|
||||
* <li>From ClassLoaderUtil.class.getClassLoader()
|
||||
* <li>From the callingClass.getClassLoader()
|
||||
* </ul>
|
||||
*
|
||||
* @param className The name of the class to load
|
||||
* @param callingClass The Class object of the calling object
|
||||
* @throws ClassNotFoundException If the class cannot be found anywhere.
|
||||
*/
|
||||
public static Class loadClass(String className, Class callingClass) throws ClassNotFoundException {
|
||||
try {
|
||||
return Thread.currentThread().getContextClassLoader().loadClass(className);
|
||||
} catch (ClassNotFoundException e) {
|
||||
try {
|
||||
return Class.forName(className);
|
||||
} catch (ClassNotFoundException ex) {
|
||||
try {
|
||||
return ClassLoaderUtil.class.getClassLoader().loadClass(className);
|
||||
} catch (ClassNotFoundException exc) {
|
||||
return callingClass.getClassLoader().loadClass(className);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1042
src/main/java/com/opensymphony/oscache/util/FastCronParser.java
Normal file
1042
src/main/java/com/opensymphony/oscache/util/FastCronParser.java
Normal file
File diff suppressed because it is too large
Load diff
67
src/main/java/com/opensymphony/oscache/util/StringUtil.java
Normal file
67
src/main/java/com/opensymphony/oscache/util/StringUtil.java
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Provides common utility methods for handling strings.
|
||||
*
|
||||
* @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
|
||||
*/
|
||||
public class StringUtil {
|
||||
|
||||
private StringUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a string into substrings based on the supplied delimiter
|
||||
* character. Each extracted substring will be trimmed of leading
|
||||
* and trailing whitespace.
|
||||
*
|
||||
* @param str The string to split
|
||||
* @param delimiter The character that delimits the string
|
||||
* @return A string array containing the resultant substrings
|
||||
*/
|
||||
public static final List split(String str, char delimiter) {
|
||||
// return no groups if we have an empty string
|
||||
if ((str == null) || "".equals(str)) {
|
||||
return new ArrayList();
|
||||
}
|
||||
|
||||
ArrayList parts = new ArrayList();
|
||||
int currentIndex;
|
||||
int previousIndex = 0;
|
||||
|
||||
while ((currentIndex = str.indexOf(delimiter, previousIndex)) > 0) {
|
||||
String part = str.substring(previousIndex, currentIndex).trim();
|
||||
parts.add(part);
|
||||
previousIndex = currentIndex + 1;
|
||||
}
|
||||
|
||||
parts.add(str.substring(previousIndex, str.length()).trim());
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param s the string to be checked
|
||||
* @return true if the string parameter contains at least one element
|
||||
*/
|
||||
public static final boolean hasLength(String s) {
|
||||
return (s != null) && (s.length() > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param s the string to be checked
|
||||
* @return true if the string parameter is null or doesn't contain any element
|
||||
* @since 2.4
|
||||
*/
|
||||
public static final boolean isEmpty(String s) {
|
||||
return (s == null) || (s.length() == 0);
|
||||
}
|
||||
|
||||
}
|
33
src/main/java/com/opensymphony/oscache/util/package.html
Normal file
33
src/main/java/com/opensymphony/oscache/util/package.html
Normal file
|
@ -0,0 +1,33 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
@(#)package.html
|
||||
|
||||
Copyright (c) 2002-2003 by OpenSymphony
|
||||
All rights reserved.
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Provides utility classes that perform fairly general-purpose functions and are required
|
||||
by OSCache.
|
||||
|
||||
|
||||
<h2>Package Specification</h2>
|
||||
|
||||
<h2>Related Documentation</h2>
|
||||
|
||||
|
||||
For overviews, tutorials, examples, guides, and tool documentation, please see:
|
||||
<ul>
|
||||
<li><a href="http://www.opensymphony.com/oscache">The OSCache Homepage</a>
|
||||
<li><a href="http://www.opensymphony.com/oscache/cron.jsp">Using Cron Expressions with OSCache</a>
|
||||
</ul>
|
||||
|
||||
<!-- Put @see and @since tags down here. -->
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.web;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletContextEvent;
|
||||
import javax.servlet.ServletContextListener;
|
||||
|
||||
/**
|
||||
* Class for a clean startup and shutdown of the ServletCacheAdministrator and its application scoped cache.
|
||||
* @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
|
||||
*/
|
||||
public class CacheContextListener implements ServletContextListener {
|
||||
|
||||
/**
|
||||
* This notification occurs when the webapp is ready to process requests.<p>
|
||||
* We use this hook to cleanly start up the {@link ServletCacheAdministrator}
|
||||
* and create the application scope cache (which will consequentially
|
||||
* initialize any listeners configured for it that implement <code>LifecycleAware</code>.)<p>
|
||||
*
|
||||
* As of Servlet 2.4, this is guaranteed to be called before any Servlet.init()
|
||||
* methods.
|
||||
*/
|
||||
public void contextInitialized(ServletContextEvent servletContextEvent) {
|
||||
ServletContext context = servletContextEvent.getServletContext();
|
||||
ServletCacheAdministrator.getInstance(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* This notification occurs when the servlet context is about to be shut down.
|
||||
* We use this hook to cleanly shut down the cache.
|
||||
*/
|
||||
public void contextDestroyed(ServletContextEvent servletContextEvent) {
|
||||
ServletContext context = servletContextEvent.getServletContext();
|
||||
ServletCacheAdministrator.destroyInstance(context);
|
||||
}
|
||||
|
||||
}
|
118
src/main/java/com/opensymphony/oscache/web/ServletCache.java
Normal file
118
src/main/java/com/opensymphony/oscache/web/ServletCache.java
Normal file
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.web;
|
||||
|
||||
import com.opensymphony.oscache.base.Cache;
|
||||
import com.opensymphony.oscache.base.CacheEntry;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import javax.servlet.http.HttpSessionBindingEvent;
|
||||
import javax.servlet.http.HttpSessionBindingListener;
|
||||
|
||||
/**
|
||||
* A simple extension of Cache that implements a session binding listener,
|
||||
* and deletes it's entries when unbound
|
||||
*
|
||||
* @author <a href="mailto:mike@atlassian.com">Mike Cannon-Brookes</a>
|
||||
* @author <a href="mailto:tgochenour@peregrine.com">Todd Gochenour</a>
|
||||
* @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
|
||||
* @version $Revision$
|
||||
*/
|
||||
public final class ServletCache extends Cache implements HttpSessionBindingListener, Serializable {
|
||||
private static transient final Log log = LogFactory.getLog(ServletCache.class);
|
||||
|
||||
/**
|
||||
* The admin for this cache
|
||||
*/
|
||||
private ServletCacheAdministrator admin;
|
||||
|
||||
/**
|
||||
* The scope of that cache.
|
||||
*/
|
||||
private int scope;
|
||||
|
||||
/**
|
||||
* Create a new ServletCache
|
||||
*
|
||||
* @param admin The ServletCacheAdministrator to administer this ServletCache.
|
||||
* @param scope The scope of all entries in this hashmap
|
||||
*/
|
||||
public ServletCache(ServletCacheAdministrator admin, int scope) {
|
||||
super(admin.isMemoryCaching(), admin.isUnlimitedDiskCache(), admin.isOverflowPersistence());
|
||||
setScope(scope);
|
||||
this.admin = admin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Cache
|
||||
*
|
||||
* @param admin The CacheAdministrator to administer this Cache.
|
||||
* @param algorithmClass The class that implement an algorithm
|
||||
* @param limit The maximum cache size in number of entries
|
||||
* @param scope The cache scope
|
||||
*/
|
||||
public ServletCache(ServletCacheAdministrator admin, String algorithmClass, int limit, int scope) {
|
||||
super(admin.isMemoryCaching(), admin.isUnlimitedDiskCache(), admin.isOverflowPersistence(), admin.isBlocking(), algorithmClass, limit);
|
||||
setScope(scope);
|
||||
this.admin = admin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache scope
|
||||
*
|
||||
* @return The cache scope
|
||||
*/
|
||||
public int getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
private void setScope(int scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
/**
|
||||
* When this Cache is bound to the session, do nothing.
|
||||
*
|
||||
* @param event The SessionBindingEvent.
|
||||
*/
|
||||
public void valueBound(HttpSessionBindingEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* When the users's session ends, all listeners are finalized and the
|
||||
* session cache directory is deleted from disk.
|
||||
*
|
||||
* @param event The event that triggered this unbinding.
|
||||
*/
|
||||
public void valueUnbound(HttpSessionBindingEvent event) {
|
||||
if (log.isInfoEnabled()) {
|
||||
// CACHE-229: don't access the session's id, because this can throw an IllegalStateException
|
||||
log.info("[Cache] Unbound from session " + event.getSession() + " using name " + event.getName());
|
||||
}
|
||||
|
||||
admin.finalizeListeners(this);
|
||||
clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether or not the cache entry is stale. This overrides the
|
||||
* {@link Cache#isStale(CacheEntry, int, String)} method to take into account any
|
||||
* flushing that may have been applied to the scope that this cache belongs to.
|
||||
*
|
||||
* @param cacheEntry The cache entry to test the freshness of.
|
||||
* @param refreshPeriod The maximum allowable age of the entry, in seconds.
|
||||
* @param cronExpiry A cron expression that defines fixed expiry dates and/or
|
||||
* times for this cache entry.
|
||||
*
|
||||
* @return <code>true</code> if the entry is stale, <code>false</code> otherwise.
|
||||
*/
|
||||
protected boolean isStale(CacheEntry cacheEntry, int refreshPeriod, String cronExpiry) {
|
||||
return super.isStale(cacheEntry, refreshPeriod, cronExpiry) || admin.isScopeFlushed(cacheEntry, scope);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,794 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.web;
|
||||
|
||||
import com.opensymphony.oscache.base.*;
|
||||
import com.opensymphony.oscache.base.events.ScopeEvent;
|
||||
import com.opensymphony.oscache.base.events.ScopeEventListener;
|
||||
import com.opensymphony.oscache.base.events.ScopeEventType;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import javax.servlet.jsp.PageContext;
|
||||
|
||||
/**
|
||||
* A ServletCacheAdministrator creates, flushes and administers the cache.
|
||||
* <p>
|
||||
* This is a "servlet Singleton". This means it's not a Singleton in the traditional sense,
|
||||
* that is stored in a static instance. It's a Singleton _per web app context_.
|
||||
* <p>
|
||||
* Once created it manages the cache path on disk through the oscache.properties
|
||||
* file, and also keeps track of the flush times.
|
||||
*
|
||||
* @author <a href="mailto:mike@atlassian.com">Mike Cannon-Brookes</a>
|
||||
* @author <a href="mailto:tgochenour@peregrine.com">Todd Gochenour</a>
|
||||
* @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
|
||||
* @author <a href="mailto:abergevin@pyxis-tech.com">Alain Bergevin</a>
|
||||
* @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
|
||||
* @version $Revision$
|
||||
*/
|
||||
public class ServletCacheAdministrator extends AbstractCacheAdministrator implements Serializable {
|
||||
private static final transient Log log = LogFactory.getLog(ServletCacheAdministrator.class);
|
||||
|
||||
/**
|
||||
* Constants for properties read/written from/to file
|
||||
*/
|
||||
private final static String CACHE_USE_HOST_DOMAIN_KEY = "cache.use.host.domain.in.key";
|
||||
private final static String CACHE_KEY_KEY = "cache.key";
|
||||
|
||||
/**
|
||||
* The default cache key that is used to store the cache in context.
|
||||
*/
|
||||
private final static String DEFAULT_CACHE_KEY = "__oscache_cache";
|
||||
|
||||
/**
|
||||
* Constants for scope's name
|
||||
*/
|
||||
public final static String SESSION_SCOPE_NAME = "session";
|
||||
public final static String APPLICATION_SCOPE_NAME = "application";
|
||||
|
||||
/**
|
||||
* The suffix added to the cache key used to store a
|
||||
* ServletCacheAdministrator will be stored in the ServletContext
|
||||
*/
|
||||
private final static String CACHE_ADMINISTRATOR_KEY_SUFFIX = "_admin";
|
||||
|
||||
/**
|
||||
* The key under which an array of all ServletCacheAdministrator objects
|
||||
* will be stored in the ServletContext
|
||||
*/
|
||||
private final static String CACHE_ADMINISTRATORS_KEY = "__oscache_admins";
|
||||
|
||||
/**
|
||||
* Key used to store the current scope in the configuration. This is a hack
|
||||
* to let the scope information get passed through to the DiskPersistenceListener,
|
||||
* and will be removed in a future release.
|
||||
*/
|
||||
public final static String HASH_KEY_SCOPE = "scope";
|
||||
|
||||
/**
|
||||
* Key used to store the current session ID in the configuration. This is a hack
|
||||
* to let the scope information get passed through to the DiskPersistenceListener,
|
||||
* and will be removed in a future release.
|
||||
*/
|
||||
public final static String HASH_KEY_SESSION_ID = "sessionId";
|
||||
|
||||
/**
|
||||
* Key used to store the servlet container temporary directory in the configuration.
|
||||
* This is a hack to let the scope information get passed through to the
|
||||
* DiskPersistenceListener, and will be removed in a future release.
|
||||
*/
|
||||
public final static String HASH_KEY_CONTEXT_TMPDIR = "context.tempdir";
|
||||
|
||||
/**
|
||||
* The string to use as a file separator.
|
||||
*/
|
||||
private final static String FILE_SEPARATOR = "/";
|
||||
|
||||
/**
|
||||
* The character to use as a file separator.
|
||||
*/
|
||||
private final static char FILE_SEPARATOR_CHAR = FILE_SEPARATOR.charAt(0);
|
||||
|
||||
/**
|
||||
* Constant for Key generation.
|
||||
*/
|
||||
private final static short AVERAGE_KEY_LENGTH = 30;
|
||||
|
||||
/**
|
||||
* Usable caracters for key generation
|
||||
*/
|
||||
private static final String m_strBase64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
/**
|
||||
* Map containing the flush times of different scopes
|
||||
*/
|
||||
private Map flushTimes;
|
||||
|
||||
/**
|
||||
* Required so we can look up the app scope cache without forcing a session creation.
|
||||
*/
|
||||
private transient ServletContext context;
|
||||
|
||||
/**
|
||||
* Key to use for storing and retrieving Object in contexts (Servlet, session).
|
||||
*/
|
||||
private String cacheKey;
|
||||
|
||||
/**
|
||||
* Set property cache.use.host.domain.in.key=true to add domain information to key
|
||||
* generation for hosting multiple sites.
|
||||
*/
|
||||
private boolean useHostDomainInKey = false;
|
||||
|
||||
/**
|
||||
* Create the cache administrator.
|
||||
*
|
||||
* This will reset all the flush times and load the properties file.
|
||||
*/
|
||||
private ServletCacheAdministrator(ServletContext context, Properties p) {
|
||||
super(p);
|
||||
config.set(HASH_KEY_CONTEXT_TMPDIR, context.getAttribute("javax.servlet.context.tempdir"));
|
||||
|
||||
flushTimes = new HashMap();
|
||||
initHostDomainInKey();
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain an instance of the CacheAdministrator
|
||||
*
|
||||
* @param context The ServletContext that this CacheAdministrator is a Singleton under
|
||||
* @return Returns the CacheAdministrator instance for this context
|
||||
*/
|
||||
public static ServletCacheAdministrator getInstance(ServletContext context) {
|
||||
return getInstance(context, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain an instance of the CacheAdministrator for the specified key
|
||||
*
|
||||
* @param context The ServletContext that this CacheAdministrator is a Singleton under
|
||||
* @param key the cachekey or admincachekey for the CacheAdministrator wanted
|
||||
* @return Returns the CacheAdministrator instance for this context, or null if no
|
||||
* CacheAdministrator exists with the key supplied
|
||||
*/
|
||||
public static ServletCacheAdministrator getInstanceFromKey(ServletContext context, String key) {
|
||||
// Note we do not bother to check if the key is null because it mustn't.
|
||||
if (!key.endsWith(CACHE_ADMINISTRATOR_KEY_SUFFIX)) {
|
||||
key = key + CACHE_ADMINISTRATOR_KEY_SUFFIX;
|
||||
}
|
||||
return (ServletCacheAdministrator) context.getAttribute(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain an instance of the CacheAdministrator
|
||||
*
|
||||
* @param context The ServletContext that this CacheAdministrator is a Singleton under
|
||||
* @param p the properties to use for the cache if the cache administrator has not been
|
||||
* created yet. Once the administrator has been created, the properties parameter is
|
||||
* ignored for all future invocations. If a null value is passed in, then the properties
|
||||
* are loaded from the oscache.properties file in the classpath.
|
||||
* @return Returns the CacheAdministrator instance for this context
|
||||
*/
|
||||
public synchronized static ServletCacheAdministrator getInstance(ServletContext context, Properties p)
|
||||
{
|
||||
String adminKey = null;
|
||||
if (p!= null) {
|
||||
adminKey = p.getProperty(CACHE_KEY_KEY);
|
||||
}
|
||||
if (adminKey == null) {
|
||||
adminKey = DEFAULT_CACHE_KEY;
|
||||
}
|
||||
adminKey += CACHE_ADMINISTRATOR_KEY_SUFFIX;
|
||||
|
||||
ServletCacheAdministrator admin = (ServletCacheAdministrator) context.getAttribute(adminKey);
|
||||
|
||||
// First time we need to create the administrator and store it in the
|
||||
// servlet context
|
||||
if (admin == null) {
|
||||
admin = new ServletCacheAdministrator(context, p);
|
||||
Map admins = (Map) context.getAttribute(CACHE_ADMINISTRATORS_KEY);
|
||||
if (admins == null) {
|
||||
admins = new HashMap();
|
||||
}
|
||||
admins.put(adminKey, admin);
|
||||
context.setAttribute(CACHE_ADMINISTRATORS_KEY, admins);
|
||||
context.setAttribute(adminKey, admin);
|
||||
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("Created new instance of ServletCacheAdministrator with key "+adminKey);
|
||||
}
|
||||
|
||||
admin.getAppScopeCache(context);
|
||||
}
|
||||
|
||||
if (admin.context == null) {
|
||||
admin.context = context;
|
||||
}
|
||||
|
||||
return admin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts down all servlet cache administrators. This should usually only
|
||||
* be called when the controlling application shuts down.
|
||||
*/
|
||||
public static void destroyInstance(ServletContext context)
|
||||
{
|
||||
ServletCacheAdministrator admin;
|
||||
Map admins = (Map) context.getAttribute(CACHE_ADMINISTRATORS_KEY);
|
||||
if (admins != null)
|
||||
{
|
||||
Set keys = admins.keySet();
|
||||
Iterator it = keys.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
String adminKey = (String) it.next();
|
||||
admin = (ServletCacheAdministrator) admins.get( adminKey );
|
||||
if (admin != null)
|
||||
{
|
||||
// Finalize the application scope cache
|
||||
Cache cache = (Cache) context.getAttribute(admin.getCacheKey());
|
||||
if (cache != null) {
|
||||
admin.finalizeListeners(cache);
|
||||
context.removeAttribute(admin.getCacheKey());
|
||||
context.removeAttribute(adminKey);
|
||||
cache = null;
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("Shut down the ServletCacheAdministrator "+adminKey);
|
||||
}
|
||||
}
|
||||
admin = null;
|
||||
}
|
||||
}
|
||||
context.removeAttribute(CACHE_ADMINISTRATORS_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Grabs the cache for the specified scope
|
||||
*
|
||||
* @param request The current request
|
||||
* @param scope The scope of this cache (<code>PageContext.APPLICATION_SCOPE</code>
|
||||
* or <code>PageContext.SESSION_SCOPE</code>)
|
||||
* @return The cache
|
||||
*/
|
||||
public Cache getCache(HttpServletRequest request, int scope) {
|
||||
if (scope == PageContext.APPLICATION_SCOPE) {
|
||||
return getAppScopeCache(context);
|
||||
}
|
||||
|
||||
if (scope == PageContext.SESSION_SCOPE) {
|
||||
return getSessionScopeCache(request.getSession(true));
|
||||
}
|
||||
|
||||
throw new RuntimeException("The supplied scope value of " + scope + " is invalid. Acceptable values are PageContext.APPLICATION_SCOPE and PageContext.SESSION_SCOPE");
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience method to retrieve the application scope cache
|
||||
|
||||
* @param context the current <code>ServletContext</code>
|
||||
* @return the application scope cache. If none is present, one will
|
||||
* be created.
|
||||
*/
|
||||
public Cache getAppScopeCache(ServletContext context) {
|
||||
Cache cache;
|
||||
Object obj = context.getAttribute(getCacheKey());
|
||||
|
||||
if ((obj == null) || !(obj instanceof Cache)) {
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("Created new application-scoped cache at key: " + getCacheKey());
|
||||
}
|
||||
|
||||
cache = createCache(PageContext.APPLICATION_SCOPE, null);
|
||||
context.setAttribute(getCacheKey(), cache);
|
||||
} else {
|
||||
cache = (Cache) obj;
|
||||
}
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience method to retrieve the session scope cache
|
||||
*
|
||||
* @param session the current <code>HttpSession</code>
|
||||
* @return the session scope cache for this session. If none is present,
|
||||
* one will be created.
|
||||
*/
|
||||
public Cache getSessionScopeCache(HttpSession session) {
|
||||
Cache cache;
|
||||
Object obj = session.getAttribute(getCacheKey());
|
||||
|
||||
if ((obj == null) || !(obj instanceof Cache)) {
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("Created new session-scoped cache in session " + session.getId() + " at key: " + getCacheKey());
|
||||
}
|
||||
|
||||
cache = createCache(PageContext.SESSION_SCOPE, session.getId());
|
||||
session.setAttribute(getCacheKey(), cache);
|
||||
} else {
|
||||
cache = (Cache) obj;
|
||||
}
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache key from the properties. Set it to a default value if it
|
||||
* is not present in the properties
|
||||
*
|
||||
* @return The cache.key property or the DEFAULT_CACHE_KEY
|
||||
*/
|
||||
public String getCacheKey() {
|
||||
if (cacheKey == null) {
|
||||
cacheKey = getProperty(CACHE_KEY_KEY);
|
||||
|
||||
if (cacheKey == null) {
|
||||
cacheKey = DEFAULT_CACHE_KEY;
|
||||
}
|
||||
}
|
||||
|
||||
return cacheKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the flush time for a specific scope to a specific time
|
||||
*
|
||||
* @param date The time to flush the scope
|
||||
* @param scope The scope to be flushed
|
||||
*/
|
||||
public void setFlushTime(Date date, int scope) {
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("Flushing scope " + scope + " at " + date);
|
||||
}
|
||||
|
||||
synchronized (flushTimes) {
|
||||
if (date != null) {
|
||||
// Trigger a SCOPE_FLUSHED event
|
||||
dispatchScopeEvent(ScopeEventType.SCOPE_FLUSHED, scope, date, null);
|
||||
flushTimes.put(new Integer(scope), date);
|
||||
} else {
|
||||
logError("setFlushTime called with a null date.");
|
||||
throw new IllegalArgumentException("setFlushTime called with a null date.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the flush time for a specific scope to the current time.
|
||||
*
|
||||
* @param scope The scope to be flushed
|
||||
*/
|
||||
public void setFlushTime(int scope) {
|
||||
setFlushTime(new Date(), scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the flush time for a particular scope.
|
||||
*
|
||||
* @param scope The scope to get the flush time for.
|
||||
* @return A date representing the time this scope was last flushed.
|
||||
* Returns null if it has never been flushed.
|
||||
*/
|
||||
public Date getFlushTime(int scope) {
|
||||
synchronized (flushTimes) {
|
||||
return (Date) flushTimes.get(new Integer(scope));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an item from the cache
|
||||
*
|
||||
* @param scope The cache scope
|
||||
* @param request The servlet request
|
||||
* @param key The key of the object to retrieve
|
||||
* @param refreshPeriod The time interval specifying if an entry needs refresh
|
||||
* @return The requested object
|
||||
* @throws NeedsRefreshException
|
||||
*/
|
||||
public Object getFromCache(int scope, HttpServletRequest request, String key, int refreshPeriod) throws NeedsRefreshException {
|
||||
Cache cache = getCache(request, scope);
|
||||
key = this.generateEntryKey(key, request, scope);
|
||||
return cache.getFromCache(key, refreshPeriod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given scope was flushed more recently than the CacheEntry provided.
|
||||
* Used to determine whether to refresh the particular CacheEntry.
|
||||
*
|
||||
* @param cacheEntry The cache entry which we're seeing whether to refresh
|
||||
* @param scope The scope we're checking
|
||||
*
|
||||
* @return Whether or not the scope has been flushed more recently than this cache entry was updated.
|
||||
*/
|
||||
public boolean isScopeFlushed(CacheEntry cacheEntry, int scope) {
|
||||
Date flushDateTime = getFlushTime(scope);
|
||||
|
||||
if (flushDateTime != null) {
|
||||
long lastUpdate = cacheEntry.getLastUpdate();
|
||||
return (flushDateTime.getTime() >= lastUpdate);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a listener for Cache Map events.
|
||||
*
|
||||
* @param listener The object that listens to events.
|
||||
*/
|
||||
public void addScopeEventListener(ScopeEventListener listener) {
|
||||
listenerList.add(ScopeEventListener.class, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels a pending cache update. This should only be called by a thread
|
||||
* that received a {@link NeedsRefreshException} and was unable to generate
|
||||
* some new cache content.
|
||||
*
|
||||
* @param scope The cache scope
|
||||
* @param request The servlet request
|
||||
* @param key The cache entry key to cancel the update of.
|
||||
*/
|
||||
public void cancelUpdate(int scope, HttpServletRequest request, String key) {
|
||||
Cache cache = getCache(request, scope);
|
||||
key = this.generateEntryKey(key, request, scope);
|
||||
cache.cancelUpdate(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush all scopes at a particular time
|
||||
*
|
||||
* @param date The time to flush the scope
|
||||
*/
|
||||
public void flushAll(Date date) {
|
||||
synchronized (flushTimes) {
|
||||
setFlushTime(date, PageContext.APPLICATION_SCOPE);
|
||||
setFlushTime(date, PageContext.SESSION_SCOPE);
|
||||
setFlushTime(date, PageContext.REQUEST_SCOPE);
|
||||
setFlushTime(date, PageContext.PAGE_SCOPE);
|
||||
}
|
||||
|
||||
// Trigger a flushAll event
|
||||
dispatchScopeEvent(ScopeEventType.ALL_SCOPES_FLUSHED, -1, date, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush all scopes instantly.
|
||||
*/
|
||||
public void flushAll() {
|
||||
flushAll(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a cache entry key.
|
||||
*
|
||||
* If the string key is not specified, the HTTP request URI and QueryString is used.
|
||||
* Operating systems that have a filename limitation less than 255 or have
|
||||
* filenames that are case insensitive may have issues with key generation where
|
||||
* two distinct pages map to the same key.
|
||||
* <p>
|
||||
* POST Requests (which have no distinguishing
|
||||
* query string) may also generate identical keys for what is actually different pages.
|
||||
* In these cases, specify an explicit key attribute for the CacheTag.
|
||||
*
|
||||
* @param key The key entered by the user
|
||||
* @param request The current request
|
||||
* @param scope The scope this cache entry is under
|
||||
* @return The generated cache key
|
||||
*/
|
||||
public String generateEntryKey(String key, HttpServletRequest request, int scope) {
|
||||
return generateEntryKey(key, request, scope, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a cache entry key.
|
||||
*
|
||||
* If the string key is not specified, the HTTP request URI and QueryString is used.
|
||||
* Operating systems that have a filename limitation less than 255 or have
|
||||
* filenames that are case insensitive may have issues with key generation where
|
||||
* two distinct pages map to the same key.
|
||||
* <p>
|
||||
* POST Requests (which have no distinguishing
|
||||
* query string) may also generate identical keys for what is actually different pages.
|
||||
* In these cases, specify an explicit key attribute for the CacheTag.
|
||||
*
|
||||
* @param key The key entered by the user
|
||||
* @param request The current request
|
||||
* @param scope The scope this cache entry is under
|
||||
* @param language The ISO-639 language code to distinguish different pages in application scope
|
||||
* @return The generated cache key
|
||||
*/
|
||||
public String generateEntryKey(String key, HttpServletRequest request, int scope, String language) {
|
||||
return generateEntryKey(key, request, scope, language, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a cache entry key.
|
||||
* <p>
|
||||
* If the string key is not specified, the HTTP request URI and QueryString is used.
|
||||
* Operating systems that have a filename limitation less than 255 or have
|
||||
* filenames that are case insensitive may have issues with key generation where
|
||||
* two distinct pages map to the same key.
|
||||
* <p>
|
||||
* POST Requests (which have no distinguishing
|
||||
* query string) may also generate identical keys for what is actually different pages.
|
||||
* In these cases, specify an explicit key attribute for the CacheTag.
|
||||
*
|
||||
* @param key The key entered by the user
|
||||
* @param request The current request
|
||||
* @param scope The scope this cache entry is under
|
||||
* @param language The ISO-639 language code to distinguish different pages in application scope
|
||||
* @param suffix The ability to put a suffix at the end of the key
|
||||
* @return The generated cache key
|
||||
*/
|
||||
public String generateEntryKey(String key, HttpServletRequest request, int scope, String language, String suffix) {
|
||||
/**
|
||||
* Used for generating cache entry keys.
|
||||
*/
|
||||
StringBuffer cBuffer = new StringBuffer(AVERAGE_KEY_LENGTH);
|
||||
|
||||
// Append the language if available
|
||||
if (language != null) {
|
||||
cBuffer.append(FILE_SEPARATOR).append(language);
|
||||
}
|
||||
|
||||
// Servers for multiple host domains need this distinction in the key
|
||||
if (useHostDomainInKey) {
|
||||
cBuffer.append(FILE_SEPARATOR).append(request.getServerName());
|
||||
}
|
||||
|
||||
if (key != null) {
|
||||
cBuffer.append(FILE_SEPARATOR).append(key);
|
||||
} else {
|
||||
String generatedKey = request.getRequestURI();
|
||||
|
||||
if (generatedKey.charAt(0) != FILE_SEPARATOR_CHAR) {
|
||||
cBuffer.append(FILE_SEPARATOR_CHAR);
|
||||
}
|
||||
|
||||
cBuffer.append(generatedKey);
|
||||
cBuffer.append("_").append(request.getMethod()).append("_");
|
||||
|
||||
generatedKey = getSortedQueryString(request);
|
||||
|
||||
if (generatedKey != null) {
|
||||
try {
|
||||
java.security.MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
|
||||
byte[] b = digest.digest(generatedKey.getBytes());
|
||||
cBuffer.append('_');
|
||||
|
||||
// Base64 encoding allows for unwanted slash characters.
|
||||
cBuffer.append(toBase64(b).replace('/', '_'));
|
||||
} catch (Exception e) {
|
||||
// Ignore query string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do we want a suffix
|
||||
if ((suffix != null) && (suffix.length() > 0)) {
|
||||
cBuffer.append(suffix);
|
||||
}
|
||||
|
||||
return cBuffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a string that contains all of the request parameters and their
|
||||
* values in a single string. This is very similar to
|
||||
* <code>HttpServletRequest.getQueryString()</code> except the parameters are
|
||||
* sorted by name, and if there is a <code>jsessionid</code> parameter it is
|
||||
* filtered out.<p>
|
||||
* If the request has no parameters, this method returns <code>null</code>.
|
||||
*/
|
||||
protected String getSortedQueryString(HttpServletRequest request) {
|
||||
Map paramMap = request.getParameterMap();
|
||||
|
||||
if (paramMap.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Set paramSet = new TreeMap(paramMap).entrySet();
|
||||
|
||||
StringBuffer buf = new StringBuffer();
|
||||
|
||||
boolean first = true;
|
||||
|
||||
for (Iterator it = paramSet.iterator(); it.hasNext();) {
|
||||
Map.Entry entry = (Map.Entry) it.next();
|
||||
String[] values = (String[]) entry.getValue();
|
||||
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
String key = (String) entry.getKey();
|
||||
|
||||
if ((key.length() != 10) || !"jsessionid".equals(key)) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
buf.append('&');
|
||||
}
|
||||
|
||||
buf.append(key).append('=').append(values[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We get a 0 length buffer if the only parameter was a jsessionid
|
||||
if (buf.length() == 0) {
|
||||
return null;
|
||||
} else {
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log error messages to commons logging.
|
||||
*
|
||||
* @param message Message to log.
|
||||
*/
|
||||
public void logError(String message) {
|
||||
log.error("[oscache]: " + message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put an object in the cache. This should only be called by a thread
|
||||
* that received a {@link NeedsRefreshException}. Using session scope
|
||||
* the thread has to insure that the session wasn't invalidated in
|
||||
* the meantime. CacheTag and CacheFilter guarantee that the same
|
||||
* cache is used in cancelUpdate and getFromCache.
|
||||
*
|
||||
* @param scope The cache scope
|
||||
* @param request The servlet request
|
||||
* @param key The object key
|
||||
* @param content The object to add
|
||||
*/
|
||||
public void putInCache(int scope, HttpServletRequest request, String key, Object content) {
|
||||
putInCache(scope, request, key, content, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put an object in the cache. This should only be called by a thread
|
||||
* that received a {@link NeedsRefreshException}. Using session scope
|
||||
* the thread has to insure that the session wasn't invalidated in
|
||||
* the meantime. CacheTag and CacheFilter guarantee that the same
|
||||
* cache is used in cancelUpdate and getFromCache.
|
||||
*
|
||||
* @param scope The cache scope
|
||||
* @param request The servlet request
|
||||
* @param key The object key
|
||||
* @param content The object to add
|
||||
* @param policy The refresh policy
|
||||
*/
|
||||
public void putInCache(int scope, HttpServletRequest request, String key, Object content, EntryRefreshPolicy policy) {
|
||||
Cache cache = getCache(request, scope);
|
||||
key = this.generateEntryKey(key, request, scope);
|
||||
cache.putInCache(key, content, policy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cache capacity (number of items). If the cache contains
|
||||
* more than <code>capacity</code> items then items will be removed
|
||||
* to bring the cache back down to the new size.
|
||||
*
|
||||
* @param scope The cache scope
|
||||
* @param request The servlet request
|
||||
* @param capacity The new capacity
|
||||
*/
|
||||
public void setCacheCapacity(int scope, HttpServletRequest request, int capacity) {
|
||||
setCacheCapacity(capacity);
|
||||
getCache(request, scope).setCapacity(capacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a listener for Cache Map events.
|
||||
*
|
||||
* @param listener The object that currently listens to events.
|
||||
*/
|
||||
public void removeScopeEventListener(ScopeEventListener listener) {
|
||||
listenerList.remove(ScopeEventListener.class, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizes all the listeners that are associated with the given cache object
|
||||
*/
|
||||
protected void finalizeListeners(Cache cache) {
|
||||
super.finalizeListeners(cache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a byte array into a Base64 string (as used in mime formats)
|
||||
*/
|
||||
private static String toBase64(byte[] aValue) {
|
||||
int byte1;
|
||||
int byte2;
|
||||
int byte3;
|
||||
int iByteLen = aValue.length;
|
||||
StringBuffer tt = new StringBuffer();
|
||||
|
||||
for (int i = 0; i < iByteLen; i += 3) {
|
||||
boolean bByte2 = (i + 1) < iByteLen;
|
||||
boolean bByte3 = (i + 2) < iByteLen;
|
||||
byte1 = aValue[i] & 0xFF;
|
||||
byte2 = (bByte2) ? (aValue[i + 1] & 0xFF) : 0;
|
||||
byte3 = (bByte3) ? (aValue[i + 2] & 0xFF) : 0;
|
||||
|
||||
tt.append(m_strBase64Chars.charAt(byte1 / 4));
|
||||
tt.append(m_strBase64Chars.charAt((byte2 / 16) + ((byte1 & 0x3) * 16)));
|
||||
tt.append(((bByte2) ? m_strBase64Chars.charAt((byte3 / 64) + ((byte2 & 0xF) * 4)) : '='));
|
||||
tt.append(((bByte3) ? m_strBase64Chars.charAt(byte3 & 0x3F) : '='));
|
||||
}
|
||||
|
||||
return tt.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a cache
|
||||
*
|
||||
* @param scope The cache scope
|
||||
* @param sessionId The sessionId for with the cache will be created
|
||||
* @return A new cache
|
||||
*/
|
||||
private ServletCache createCache(int scope, String sessionId) {
|
||||
ServletCache newCache = new ServletCache(this, algorithmClass, cacheCapacity, scope);
|
||||
|
||||
// TODO - Fix me please!
|
||||
// Hack! This is nasty - if two sessions are created within a short
|
||||
// space of time it is possible they will end up with duplicate
|
||||
// session IDs being passed to the DiskPersistenceListener!...
|
||||
config.set(HASH_KEY_SCOPE, "" + scope);
|
||||
config.set(HASH_KEY_SESSION_ID, sessionId);
|
||||
|
||||
newCache = (ServletCache) configureStandardListeners(newCache);
|
||||
|
||||
return newCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch a scope event to all registered listeners.
|
||||
*
|
||||
* @param eventType The type of event
|
||||
* @param scope Scope that was flushed (Does not apply for FLUSH_ALL event)
|
||||
* @param date Date of flushing
|
||||
* @param origin The origin of the event
|
||||
*/
|
||||
private void dispatchScopeEvent(ScopeEventType eventType, int scope, Date date, String origin) {
|
||||
// Create the event
|
||||
ScopeEvent event = new ScopeEvent(eventType, scope, date, origin);
|
||||
|
||||
// Guaranteed to return a non-null array
|
||||
Object[] listeners = listenerList.getListenerList();
|
||||
|
||||
// Process the listeners last to first, notifying
|
||||
// those that are interested in this event
|
||||
for (int i = listeners.length - 2; i >= 0; i -= 2) {
|
||||
if (listeners[i+1] instanceof ScopeEventListener) {
|
||||
((ScopeEventListener) listeners[i + 1]).scopeFlushed(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set property cache.use.host.domain.in.key=true to add domain information to key
|
||||
* generation for hosting multiple sites
|
||||
*/
|
||||
private void initHostDomainInKey() {
|
||||
String propStr = getProperty(CACHE_USE_HOST_DOMAIN_KEY);
|
||||
|
||||
useHostDomainInKey = "true".equalsIgnoreCase(propStr);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.web;
|
||||
|
||||
import com.opensymphony.oscache.base.EntryRefreshPolicy;
|
||||
|
||||
/**
|
||||
* Interface to implement an entry refresh policy.
|
||||
* Specify the name of the implementing class using the refreshpolicyclass
|
||||
* attribute of the cache tag. If any additional parameters are required,
|
||||
* they should be supplied using the refreshpolicyparam attribute.<p>
|
||||
*
|
||||
* For example:
|
||||
* <pre>
|
||||
* <cache:cache key="mykey"
|
||||
* refreshpolicyclass="com.mycompany.cache.policy.MyRefreshPolicy"
|
||||
* refreshpolicyparam="...additional data...">
|
||||
My cached content
|
||||
* </cache:cache>
|
||||
* </pre>
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
|
||||
*/
|
||||
public interface WebEntryRefreshPolicy extends EntryRefreshPolicy {
|
||||
/**
|
||||
* Initializes the refresh policy.
|
||||
*
|
||||
* @param key The cache key that is being checked.
|
||||
* @param param Any optional parameters that were supplied
|
||||
*/
|
||||
public void init(String key, String param);
|
||||
}
|
|
@ -0,0 +1,823 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2009 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.web.filter;
|
||||
|
||||
import com.opensymphony.oscache.base.Cache;
|
||||
import com.opensymphony.oscache.base.Config;
|
||||
import com.opensymphony.oscache.base.EntryRefreshPolicy;
|
||||
import com.opensymphony.oscache.base.NeedsRefreshException;
|
||||
import com.opensymphony.oscache.util.ClassLoaderUtil;
|
||||
import com.opensymphony.oscache.util.StringUtil;
|
||||
import com.opensymphony.oscache.web.ServletCacheAdministrator;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.jsp.PageContext;
|
||||
|
||||
/**
|
||||
* CacheFilter is a filter that allows for server-side caching of post-processed servlet content.<p>
|
||||
*
|
||||
* It also gives great programatic control over refreshing, flushing and updating the cache.<p>
|
||||
*
|
||||
* @author <a href="mailto:sergek [ AT ] lokitech.com">Serge Knystautas</a>
|
||||
* @author <a href="mailto:mike [ AT ] atlassian.com">Mike Cannon-Brookes</a>
|
||||
* @author <a href="mailto:ltorunski [ AT ] t-online.de">Lars Torunski</a>
|
||||
* @version $Revision$
|
||||
*/
|
||||
public class CacheFilter implements Filter, ICacheKeyProvider, ICacheGroupsProvider {
|
||||
// Header
|
||||
public static final String HEADER_LAST_MODIFIED = "Last-Modified";
|
||||
public static final String HEADER_CONTENT_TYPE = "Content-Type";
|
||||
public static final String HEADER_CONTENT_ENCODING = "Content-Encoding";
|
||||
public static final String HEADER_EXPIRES = "Expires";
|
||||
public static final String HEADER_IF_MODIFIED_SINCE = "If-Modified-Since";
|
||||
public static final String HEADER_CACHE_CONTROL = "Cache-Control";
|
||||
public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding";
|
||||
public static final String HEADER_ETAG = "ETag";
|
||||
public static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition";
|
||||
|
||||
// Fragment parameter
|
||||
public static final int FRAGMENT_AUTODETECT = -1;
|
||||
public static final int FRAGMENT_NO = 0;
|
||||
public static final int FRAGMENT_YES = 1;
|
||||
|
||||
// No cache parameter
|
||||
public static final int NOCACHE_OFF = 0;
|
||||
public static final int NOCACHE_SESSION_ID_IN_URL = 1;
|
||||
|
||||
// Last Modified parameter
|
||||
public static final long LAST_MODIFIED_OFF = 0;
|
||||
public static final long LAST_MODIFIED_ON = 1;
|
||||
public static final long LAST_MODIFIED_INITIAL = -1;
|
||||
|
||||
// Expires parameter
|
||||
public static final long EXPIRES_OFF = 0;
|
||||
public static final long EXPIRES_ON = 1;
|
||||
public static final long EXPIRES_TIME = -1;
|
||||
|
||||
// ETag parameter
|
||||
public static final int ETAG_OFF = 0;
|
||||
public static final int ETAG_WEAK = 1;
|
||||
//public static final int ETAG_STRONG = 2;
|
||||
|
||||
// Cache Control
|
||||
public static final long MAX_AGE_NO_INIT = Long.MIN_VALUE;
|
||||
public static final long MAX_AGE_TIME = Long.MAX_VALUE;
|
||||
|
||||
// request attribute to avoid reentrance
|
||||
private final static String REQUEST_FILTERED = "__oscache_filtered__";
|
||||
private String requestFiltered;
|
||||
|
||||
// the policy for the expires header
|
||||
private EntryRefreshPolicy expiresRefreshPolicy;
|
||||
|
||||
// the logger
|
||||
private final Log log = LogFactory.getLog(this.getClass());
|
||||
|
||||
// filter variables
|
||||
private FilterConfig config;
|
||||
private ServletCacheAdministrator admin = null;
|
||||
private int cacheScope = PageContext.APPLICATION_SCOPE; // filter scope - default is APPLICATION
|
||||
private int fragment = FRAGMENT_AUTODETECT; // defines if this filter handles fragments of a page - default is auto detect
|
||||
private int time = 60 * 60; // time before cache should be refreshed - default one hour (in seconds)
|
||||
private String cron = null; // A cron expression that determines when this cached content will expire - default is null
|
||||
private int nocache = NOCACHE_OFF; // defines special no cache option for the requests - default is off
|
||||
private long lastModified = LAST_MODIFIED_INITIAL; // defines if the last-modified-header will be sent - default is intial setting
|
||||
private long expires = EXPIRES_ON; // defines if the expires-header will be sent - default is on
|
||||
private int etag = ETAG_WEAK; // defines the type of the etag header - default is weak
|
||||
private long cacheControlMaxAge = -60; // defines which max-age in Cache-Control to be set - default is 60 seconds for max-age
|
||||
private ICacheKeyProvider cacheKeyProvider = this; // the provider of the cache key - default is the CacheFilter itselfs
|
||||
private ICacheGroupsProvider cacheGroupsProvider = this; // the provider of the cache groups - default is the CacheFilter itselfs
|
||||
private List disableCacheOnMethods = null; // caching can be disabled by defining the http methods - default is off
|
||||
|
||||
/**
|
||||
* Filter clean-up
|
||||
*/
|
||||
public void destroy() {
|
||||
//Not much to do...
|
||||
}
|
||||
|
||||
/**
|
||||
* The doFilter call caches the response by wrapping the <code>HttpServletResponse</code>
|
||||
* object so that the output stream can be caught. This works by splitting off the output
|
||||
* stream into two with the {@link SplitServletOutputStream} class. One stream gets written
|
||||
* out to the response as normal, the other is fed into a byte array inside a {@link ResponseContent}
|
||||
* object.
|
||||
*
|
||||
* @param request The servlet request
|
||||
* @param response The servlet response
|
||||
* @param chain The filter chain
|
||||
* @throws ServletException IOException
|
||||
*/
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("OSCache: filter in scope " + cacheScope);
|
||||
}
|
||||
|
||||
// avoid reentrance (CACHE-128) and check if request is cacheable
|
||||
if (isFilteredBefore(request) || !isCacheableInternal(request)) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
request.setAttribute(requestFiltered, Boolean.TRUE);
|
||||
|
||||
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||
|
||||
// checks if the response will be a fragment of a page
|
||||
boolean fragmentRequest = isFragment(httpRequest);
|
||||
|
||||
// avoid useless session creation for application scope pages (CACHE-129)
|
||||
Cache cache;
|
||||
if (cacheScope == PageContext.SESSION_SCOPE) {
|
||||
cache = admin.getSessionScopeCache(httpRequest.getSession(true));
|
||||
} else {
|
||||
cache = admin.getAppScopeCache(config.getServletContext());
|
||||
}
|
||||
|
||||
// generate the cache entry key
|
||||
String key = cacheKeyProvider.createCacheKey(httpRequest, admin, cache);
|
||||
|
||||
try {
|
||||
ResponseContent respContent = (ResponseContent) cache.getFromCache(key, time, cron);
|
||||
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("OSCache: Using cached entry for " + key);
|
||||
}
|
||||
|
||||
boolean acceptsGZip = false;
|
||||
if ((!fragmentRequest) && (lastModified != LAST_MODIFIED_OFF)) {
|
||||
long clientLastModified = httpRequest.getDateHeader(HEADER_IF_MODIFIED_SINCE); // will return -1 if no header...
|
||||
|
||||
// only reply with SC_NOT_MODIFIED
|
||||
// if the client has already the newest page and the response isn't a fragment in a page
|
||||
if ((clientLastModified != -1) && (clientLastModified >= respContent.getLastModified())) {
|
||||
((HttpServletResponse) response).setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
||||
return;
|
||||
}
|
||||
|
||||
acceptsGZip = respContent.isContentGZiped() && acceptsGZipEncoding(httpRequest);
|
||||
}
|
||||
|
||||
respContent.writeTo(response, fragmentRequest, acceptsGZip);
|
||||
// acceptsGZip is used for performance reasons above; use the following line for CACHE-49
|
||||
// respContent.writeTo(response, fragmentRequest, acceptsGZipEncoding(httpRequest));
|
||||
} catch (NeedsRefreshException nre) {
|
||||
boolean updateSucceeded = false;
|
||||
|
||||
try {
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("OSCache: New cache entry, cache stale or cache scope flushed for " + key);
|
||||
}
|
||||
|
||||
CacheHttpServletResponseWrapper cacheResponse = new CacheHttpServletResponseWrapper((HttpServletResponse) response, fragmentRequest, time * 1000L, lastModified, expires, cacheControlMaxAge, etag);
|
||||
chain.doFilter(request, cacheResponse);
|
||||
cacheResponse.flushBuffer();
|
||||
|
||||
// Only cache if the response is cacheable
|
||||
if (isCacheableInternal(cacheResponse)) {
|
||||
// get the cache groups of the content
|
||||
String[] groups = cacheGroupsProvider.createCacheGroups(httpRequest, admin, cache);
|
||||
// Store as the cache content the result of the response
|
||||
cache.putInCache(key, cacheResponse.getContent(), groups, expiresRefreshPolicy, null);
|
||||
updateSucceeded = true;
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("OSCache: New entry added to the cache with key " + key);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (!updateSucceeded) {
|
||||
cache.cancelUpdate(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the filter. This retrieves a {@link ServletCacheAdministrator}
|
||||
* instance and configures the filter based on any initialization parameters.<p>
|
||||
* The supported initialization parameters are:
|
||||
* <ul>
|
||||
*
|
||||
* <li><b>oscache-properties-file</b> - the properties file that contains the OSCache configuration
|
||||
* options to be used by the Cache that this Filter should use.</li>
|
||||
*
|
||||
* @param filterConfig The filter configuration
|
||||
*/
|
||||
public void init(FilterConfig filterConfig) {
|
||||
// Get whatever settings we want...
|
||||
config = filterConfig;
|
||||
|
||||
log.info("OSCache: Initializing CacheFilter with filter name " + config.getFilterName());
|
||||
|
||||
// setting the request filter to avoid reentrance with the same filter
|
||||
requestFiltered = REQUEST_FILTERED + config.getFilterName();
|
||||
log.info("Request filter attribute is " + requestFiltered);
|
||||
|
||||
// filter Properties file
|
||||
Properties props = null;
|
||||
try {
|
||||
String propertiesfile = config.getInitParameter("oscache-properties-file");
|
||||
|
||||
if (propertiesfile != null && propertiesfile.length() > 0) {
|
||||
props = Config.loadProperties(propertiesfile, "CacheFilter with filter name '" + config.getFilterName()+ "'");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.info("OSCache: Init parameter 'oscache-properties-file' not set, using default.");
|
||||
}
|
||||
admin = ServletCacheAdministrator.getInstance(config.getServletContext(), props);
|
||||
|
||||
// filter parameter time
|
||||
String timeParam = config.getInitParameter("time");
|
||||
if (timeParam != null) {
|
||||
try {
|
||||
setTime(Integer.parseInt(timeParam));
|
||||
} catch (NumberFormatException nfe) {
|
||||
log.error("OSCache: Unexpected value for the init parameter 'time', defaulting to one hour. Message=" + nfe.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// filter parameter scope
|
||||
String scopeParam = config.getInitParameter("scope");
|
||||
if (scopeParam != null) {
|
||||
if ("session".equalsIgnoreCase(scopeParam)) {
|
||||
setCacheScope(PageContext.SESSION_SCOPE);
|
||||
} else if ("application".equalsIgnoreCase(scopeParam)) {
|
||||
setCacheScope(PageContext.APPLICATION_SCOPE);
|
||||
} else {
|
||||
log.error("OSCache: Wrong value '" + scopeParam + "' for init parameter 'scope', defaulting to 'application'.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// filter parameter cron
|
||||
setCron(config.getInitParameter("cron"));
|
||||
|
||||
// filter parameter fragment
|
||||
String fragmentParam = config.getInitParameter("fragment");
|
||||
if (fragmentParam != null) {
|
||||
if ("no".equalsIgnoreCase(fragmentParam)) {
|
||||
setFragment(FRAGMENT_NO);
|
||||
} else if ("yes".equalsIgnoreCase(fragmentParam)) {
|
||||
setFragment(FRAGMENT_YES);
|
||||
} else if ("auto".equalsIgnoreCase(fragmentParam)) {
|
||||
setFragment(FRAGMENT_AUTODETECT);
|
||||
} else {
|
||||
log.error("OSCache: Wrong value '" + fragmentParam + "' for init parameter 'fragment', defaulting to 'auto detect'.");
|
||||
}
|
||||
}
|
||||
|
||||
// filter parameter nocache
|
||||
String nocacheParam = config.getInitParameter("nocache");
|
||||
if (nocacheParam != null) {
|
||||
if ("off".equalsIgnoreCase(nocacheParam)) {
|
||||
nocache = NOCACHE_OFF;
|
||||
} else if ("sessionIdInURL".equalsIgnoreCase(nocacheParam)) {
|
||||
nocache = NOCACHE_SESSION_ID_IN_URL;
|
||||
} else {
|
||||
log.error("OSCache: Wrong value '" + nocacheParam + "' for init parameter 'nocache', defaulting to 'off'.");
|
||||
}
|
||||
}
|
||||
|
||||
// filter parameter last modified
|
||||
String lastModifiedParam = config.getInitParameter("lastModified");
|
||||
if (lastModifiedParam != null) {
|
||||
if ("off".equalsIgnoreCase(lastModifiedParam)) {
|
||||
lastModified = LAST_MODIFIED_OFF;
|
||||
} else if ("on".equalsIgnoreCase(lastModifiedParam)) {
|
||||
lastModified = LAST_MODIFIED_ON;
|
||||
} else if ("initial".equalsIgnoreCase(lastModifiedParam)) {
|
||||
lastModified = LAST_MODIFIED_INITIAL;
|
||||
} else {
|
||||
log.error("OSCache: Wrong value '" + lastModifiedParam + "' for init parameter 'lastModified', defaulting to 'initial'.");
|
||||
}
|
||||
}
|
||||
|
||||
// filter parameter expires
|
||||
String expiresParam = config.getInitParameter("expires");
|
||||
if (expiresParam != null) {
|
||||
if ("off".equalsIgnoreCase(expiresParam)) {
|
||||
setExpires(EXPIRES_OFF);
|
||||
} else if ("on".equalsIgnoreCase(expiresParam)) {
|
||||
setExpires(EXPIRES_ON);
|
||||
} else if ("time".equalsIgnoreCase(expiresParam)) {
|
||||
setExpires(EXPIRES_TIME);
|
||||
} else {
|
||||
log.error("OSCache: Wrong value '" + expiresParam + "' for init parameter 'expires', defaulting to 'on'.");
|
||||
}
|
||||
}
|
||||
|
||||
// filter parameter expires
|
||||
String etagParam = config.getInitParameter("etag");
|
||||
if (etagParam != null) {
|
||||
if ("off".equalsIgnoreCase(etagParam)) {
|
||||
setETag(ETAG_OFF);
|
||||
} else if ("weak".equalsIgnoreCase(etagParam)) {
|
||||
setETag(ETAG_WEAK);
|
||||
} else {
|
||||
log.error("OSCache: Wrong value '" + etagParam + "' for init parameter 'etag', defaulting to 'weak'.");
|
||||
}
|
||||
}
|
||||
|
||||
// filter parameter Cache-Control
|
||||
String cacheControlMaxAgeParam = config.getInitParameter("max-age");
|
||||
if (cacheControlMaxAgeParam != null) {
|
||||
if (cacheControlMaxAgeParam.equalsIgnoreCase("no init")) {
|
||||
setCacheControlMaxAge(MAX_AGE_NO_INIT);
|
||||
} else if (cacheControlMaxAgeParam.equalsIgnoreCase("time")) {
|
||||
setCacheControlMaxAge(MAX_AGE_TIME);
|
||||
} else {
|
||||
try {
|
||||
setCacheControlMaxAge(Long.parseLong(cacheControlMaxAgeParam));
|
||||
} catch (NumberFormatException nfe) {
|
||||
log.error("OSCache: Unexpected value for the init parameter 'max-age', defaulting to '60'. Message=" + nfe.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// filter parameter ICacheKeyProvider
|
||||
ICacheKeyProvider cacheKeyProviderParam = (ICacheKeyProvider)instantiateFromInitParam("ICacheKeyProvider", ICacheKeyProvider.class, this.getClass().getName());
|
||||
if (cacheKeyProviderParam != null) {
|
||||
setCacheKeyProvider(cacheKeyProviderParam);
|
||||
}
|
||||
|
||||
// filter parameter ICacheGroupsProvider
|
||||
ICacheGroupsProvider cacheGroupsProviderParam = (ICacheGroupsProvider)instantiateFromInitParam("ICacheGroupsProvider", ICacheGroupsProvider.class, this.getClass().getName());
|
||||
if (cacheGroupsProviderParam != null) {
|
||||
setCacheGroupsProvider(cacheGroupsProviderParam);
|
||||
}
|
||||
|
||||
// filter parameter EntryRefreshPolicy
|
||||
EntryRefreshPolicy expiresRefreshPolicyParam = (EntryRefreshPolicy)instantiateFromInitParam("EntryRefreshPolicy", EntryRefreshPolicy.class, ExpiresRefreshPolicy.class.getName());
|
||||
if (expiresRefreshPolicyParam != null) {
|
||||
setExpiresRefreshPolicy(expiresRefreshPolicyParam);
|
||||
} else {
|
||||
// setting the refresh period for this cache filter
|
||||
setExpiresRefreshPolicy(new ExpiresRefreshPolicy(time));
|
||||
}
|
||||
|
||||
// filter parameter scope
|
||||
String disableCacheOnMethodsParam = config.getInitParameter("disableCacheOnMethods");
|
||||
if (StringUtil.hasLength(disableCacheOnMethodsParam)) {
|
||||
disableCacheOnMethods = StringUtil.split(disableCacheOnMethodsParam, ',');
|
||||
// log.error("OSCache: Wrong value '" + disableCacheOnMethodsParam + "' for init parameter 'disableCacheOnMethods', defaulting to 'null'.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Object instantiateFromInitParam(String classInitParam, Class interfaceClass, String defaultObjectName) {
|
||||
String className = config.getInitParameter(classInitParam);
|
||||
if (className != null) {
|
||||
try {
|
||||
Class clazz = ClassLoaderUtil.loadClass(className, this.getClass());
|
||||
if (!interfaceClass.isAssignableFrom(clazz)) {
|
||||
log.error("OSCache: Specified class '" + className + "' does not implement" + interfaceClass.getName() + ". Using default " + defaultObjectName + ".");
|
||||
return null;
|
||||
} else {
|
||||
return clazz.newInstance();
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
log.error("OSCache: Class '" + className + "' not found. Defaulting to " + defaultObjectName + ".", e);
|
||||
} catch (InstantiationException e) {
|
||||
log.error("OSCache: Class '" + className + "' could not be instantiated because it is not a concrete class. Using default object " + defaultObjectName + ".", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
log.error("OSCache: Class '"+ className+ "' could not be instantiated because it is not public. Using default object " + defaultObjectName + ".", e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ICacheKeyProvider}
|
||||
* @see com.opensymphony.oscache.web.filter.ICacheKeyProvider#createCacheKey(javax.servlet.http.HttpServletRequest, ServletCacheAdministrator, Cache)
|
||||
*/
|
||||
public String createCacheKey(HttpServletRequest httpRequest, ServletCacheAdministrator scAdmin, Cache cache) {
|
||||
return scAdmin.generateEntryKey(null, httpRequest, cacheScope);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ICacheGroupsProvider}
|
||||
* @see com.opensymphony.oscache.web.filter.ICacheGroupsProvider#createCacheGroups(javax.servlet.http.HttpServletRequest, ServletCacheAdministrator, Cache)
|
||||
*/
|
||||
public String[] createCacheGroups(HttpServletRequest httpRequest, ServletCacheAdministrator scAdmin, Cache cache) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the request is a fragment in a page.
|
||||
*
|
||||
* According to Java Servlet API 2.2 (8.2.1 Dispatching Requests, Included
|
||||
* Request Parameters), when a servlet is being used from within an include,
|
||||
* the attribute <code>javax.servlet.include.request_uri</code> is set.
|
||||
* According to Java Servlet API 2.3 this is excepted for servlets obtained
|
||||
* by using the getNamedDispatcher method.
|
||||
*
|
||||
* @param request the to be handled request
|
||||
* @return true if the request is a fragment in a page
|
||||
*/
|
||||
public boolean isFragment(HttpServletRequest request) {
|
||||
if (fragment == FRAGMENT_AUTODETECT) {
|
||||
return request.getAttribute("javax.servlet.include.request_uri") != null;
|
||||
} else {
|
||||
return (fragment == FRAGMENT_NO) ? false : true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the request was filtered before, so
|
||||
* guarantees to be executed once per request. You
|
||||
* can override this methods to define a more specific
|
||||
* behavior.
|
||||
*
|
||||
* @param request checks if the request was filtered before.
|
||||
* @return true if it is the first execution
|
||||
*/
|
||||
public boolean isFilteredBefore(ServletRequest request) {
|
||||
return request.getAttribute(requestFiltered) != null;
|
||||
}
|
||||
|
||||
/*
|
||||
* isCacheableInternal guarantees that the log information is correct.
|
||||
*
|
||||
* @param request The servlet request
|
||||
* @return Returns a boolean indicating if the request can be cached or not.
|
||||
*/
|
||||
private final boolean isCacheableInternal(ServletRequest request) {
|
||||
final boolean cacheable = isCacheable(request);
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("OSCache: the request " + ((cacheable) ? "is" : "is not") + " cachable.");
|
||||
}
|
||||
|
||||
return cacheable;
|
||||
}
|
||||
|
||||
/**
|
||||
* isCacheable is a method allowing a subclass to decide if a request is
|
||||
* cacheable or not.
|
||||
*
|
||||
* @param request The servlet request
|
||||
* @return Returns a boolean indicating if the request can be cached or not.
|
||||
*/
|
||||
public boolean isCacheable(ServletRequest request) {
|
||||
boolean cacheable = request instanceof HttpServletRequest;
|
||||
|
||||
if (cacheable) {
|
||||
HttpServletRequest requestHttp = (HttpServletRequest) request;
|
||||
// CACHE-272 don't cache special http request methods
|
||||
if ((disableCacheOnMethods != null) && (disableCacheOnMethods.contains(requestHttp.getMethod()))) {
|
||||
return false;
|
||||
}
|
||||
if (nocache == NOCACHE_SESSION_ID_IN_URL) { // don't cache requests if session id is in the URL
|
||||
cacheable = !requestHttp.isRequestedSessionIdFromURL();
|
||||
}
|
||||
}
|
||||
|
||||
return cacheable;
|
||||
}
|
||||
|
||||
/*
|
||||
* isCacheableInternal guarantees that the log information is correct.
|
||||
*
|
||||
* @param cacheResponse the HTTP servlet response
|
||||
* @return Returns a boolean indicating if the response can be cached or not.
|
||||
*/
|
||||
private final boolean isCacheableInternal(CacheHttpServletResponseWrapper cacheResponse) {
|
||||
final boolean cacheable = isCacheable(cacheResponse);
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("OSCache: the response " + ((cacheable) ? "is" : "is not") + " cacheable.");
|
||||
}
|
||||
|
||||
return cacheable;
|
||||
}
|
||||
|
||||
/**
|
||||
* isCacheable is a method allowing subclass to decide if a response is
|
||||
* cacheable or not.
|
||||
*
|
||||
* @param cacheResponse the HTTP servlet response
|
||||
* @return Returns a boolean indicating if the response can be cached or not.
|
||||
*/
|
||||
public boolean isCacheable(CacheHttpServletResponseWrapper cacheResponse) {
|
||||
// TODO implement CACHE-137 here
|
||||
// Only cache if the response was 200
|
||||
return cacheResponse.getStatus() == HttpServletResponse.SC_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the client browser support gzip compression.
|
||||
*
|
||||
* @param request the http request
|
||||
* @return true if client browser supports GZIP
|
||||
*/
|
||||
public boolean acceptsGZipEncoding(HttpServletRequest request) {
|
||||
String acceptEncoding = request.getHeader(HEADER_ACCEPT_ENCODING);
|
||||
return (acceptEncoding != null) && (acceptEncoding.indexOf("gzip") != -1);
|
||||
}
|
||||
|
||||
// ---------------------------------
|
||||
// --- getter and setter methods ---
|
||||
// ---------------------------------
|
||||
|
||||
/**
|
||||
* @return the max-age of the cache control
|
||||
* @since 2.4
|
||||
*/
|
||||
public long getCacheControlMaxAge() {
|
||||
if ((cacheControlMaxAge == MAX_AGE_NO_INIT) || (cacheControlMaxAge == MAX_AGE_TIME)) {
|
||||
return cacheControlMaxAge;
|
||||
}
|
||||
return - cacheControlMaxAge;
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>max-age</b> - defines the cache control response header max-age. Acceptable values are
|
||||
* <code>MAX_AGE_NO_INIT</code> for don't initializing the max-age cache control,
|
||||
* <code>MAX_AGE_TIME</code> the max-age information will be based on the time parameter and creation time of the content (expiration timestamp minus current timestamp), and
|
||||
* <code>[positive integer]</code> value constant in seconds to be set in every response, the default value is 60.
|
||||
*
|
||||
* @param cacheControlMaxAge the cacheControlMaxAge to set
|
||||
* @since 2.4
|
||||
*/
|
||||
public void setCacheControlMaxAge(long cacheControlMaxAge) {
|
||||
if ((cacheControlMaxAge == MAX_AGE_NO_INIT) || (cacheControlMaxAge == MAX_AGE_TIME)) {
|
||||
this.cacheControlMaxAge = cacheControlMaxAge;
|
||||
} else if (cacheControlMaxAge >= 0) {
|
||||
// declare the cache control as a constant
|
||||
// TODO check if this value can be stored as a positive long without changing it
|
||||
this.cacheControlMaxAge = - cacheControlMaxAge;
|
||||
} else {
|
||||
log.warn("OSCache: 'max-age' must be at least a positive integer, defaulting to '60'. ");
|
||||
this.cacheControlMaxAge = -60;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the cacheGroupsProvider
|
||||
* @since 2.4
|
||||
*/
|
||||
public ICacheGroupsProvider getCacheGroupsProvider() {
|
||||
return cacheGroupsProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>ICacheGroupsProvider</b> - Class implementing the interface <code>ICacheGroupsProvider</code>.
|
||||
* A developer can implement a method which provides cache groups based on the request,
|
||||
* the servlet cache administrator and cache. The parameter has to be not <code>null</code>.
|
||||
*
|
||||
* @param cacheGroupsProvider the cacheGroupsProvider to set
|
||||
* @since 2.4
|
||||
*/
|
||||
public void setCacheGroupsProvider(ICacheGroupsProvider cacheGroupsProvider) {
|
||||
if (cacheGroupsProvider == null) throw new IllegalArgumentException("The ICacheGroupsProvider is null.");
|
||||
this.cacheGroupsProvider = cacheGroupsProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the cacheKeyProvider
|
||||
* @since 2.4
|
||||
*/
|
||||
public ICacheKeyProvider getCacheKeyProvider() {
|
||||
return cacheKeyProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>ICacheKeyProvider</b> - Class implementing the interface <code>ICacheKeyProvider</code>.
|
||||
* A developer can implement a method which provides cache keys based on the request,
|
||||
* the servlect cache administrator and cache. The parameter has to be not <code>null</code>.
|
||||
*
|
||||
* @param cacheKeyProvider the cacheKeyProvider to set
|
||||
* @since 2.4
|
||||
*/
|
||||
public void setCacheKeyProvider(ICacheKeyProvider cacheKeyProvider) {
|
||||
if (cacheKeyProvider == null) throw new IllegalArgumentException("The ICacheKeyProvider is null.");
|
||||
this.cacheKeyProvider = cacheKeyProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns PageContext.APPLICATION_SCOPE or PageContext.SESSION_SCOPE.
|
||||
* @return the cache scope
|
||||
* @since 2.4
|
||||
*/
|
||||
public int getCacheScope() {
|
||||
return cacheScope;
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>scope</b> - the default scope to cache content. Acceptable values
|
||||
* are <code>PageContext.APPLICATION_SCOPE</code> (default) and <code>PageContext.SESSION_SCOPE</code>.
|
||||
*
|
||||
* @param cacheScope the cacheScope to set
|
||||
* @since 2.4
|
||||
*/
|
||||
public void setCacheScope(int cacheScope) {
|
||||
if ((cacheScope != PageContext.APPLICATION_SCOPE) && (cacheScope != PageContext.SESSION_SCOPE))
|
||||
throw new IllegalArgumentException("Acceptable values for cache scope are PageContext.APPLICATION_SCOPE or PageContext.SESSION_SCOPE");
|
||||
this.cacheScope = cacheScope;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the cron
|
||||
* @since 2.4
|
||||
*/
|
||||
public String getCron() {
|
||||
return cron;
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>cron</b> - defines an expression that determines when the page content will expire.
|
||||
* This allows content to be expired at particular dates and/or times, rather than once
|
||||
* a cache entry reaches a certain age.
|
||||
*
|
||||
* @param cron the cron to set
|
||||
* @since 2.4
|
||||
*/
|
||||
public void setCron(String cron) {
|
||||
this.cron = cron;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the expires header
|
||||
* @since 2.4
|
||||
*/
|
||||
public long getExpires() {
|
||||
return expires;
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>expires</b> - defines if the expires header will be sent in the response. Acceptable values are
|
||||
* <code>EXPIRES_OFF</code> for don't sending the header, even it is set in the filter chain,
|
||||
* <code>EXPIRES_ON</code> (default) for sending it if it is set in the filter chain and
|
||||
* <code>EXPIRES_TIME</code> the expires information will be intialized based on the time parameter and creation time of the content.
|
||||
*
|
||||
* @param expires the expires to set
|
||||
* @since 2.4
|
||||
*/
|
||||
public void setExpires(long expires) {
|
||||
if ((expires < EXPIRES_TIME) || (expires > EXPIRES_ON)) throw new IllegalArgumentException("Expires value out of range.");
|
||||
this.expires = expires;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the etag
|
||||
* @since 2.4.2
|
||||
*/
|
||||
public int getETag() {
|
||||
return etag;
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>etag</b> - defines if the Entity tag (ETag) HTTP header is sent in the response. Acceptable values are
|
||||
* <code>ETAG_OFF</code> for don't sending the header, even it is set in the filter chain,
|
||||
* <code>ETAG_WEAK</code> (default) for generating a Weak ETag by concatenating the content length and the last modified time in milliseconds.
|
||||
*
|
||||
* @param etag the etag to set
|
||||
* @since 2.4.2
|
||||
*/
|
||||
public void setETag(int etag) {
|
||||
if ((etag < ETAG_OFF) || (etag > ETAG_WEAK)) throw new IllegalArgumentException("ETag value out of range.");
|
||||
this.etag = etag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the expiresRefreshPolicy
|
||||
* @since 2.4
|
||||
*/
|
||||
public EntryRefreshPolicy getExpiresRefreshPolicy() {
|
||||
return expiresRefreshPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>EntryRefreshPolicy</b> - Class implementing the interface <code>EntryRefreshPolicy</code>.
|
||||
* A developer can implement a class which provides a different custom cache invalidation policy for a specific cache entry.
|
||||
* If not specified, the default policy is timed entry expiry as specified with the <b>time</b> parameter described above.
|
||||
*
|
||||
* @param expiresRefreshPolicy the expiresRefreshPolicy to set
|
||||
* @since 2.4
|
||||
*/
|
||||
public void setExpiresRefreshPolicy(EntryRefreshPolicy expiresRefreshPolicy) {
|
||||
if (expiresRefreshPolicy == null) throw new IllegalArgumentException("The EntryRefreshPolicy is null.");
|
||||
this.expiresRefreshPolicy = expiresRefreshPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the fragment
|
||||
* @since 2.4
|
||||
*/
|
||||
public int getFragment() {
|
||||
return fragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>fragment</b> - defines if this filter handles fragments of a page. Acceptable values
|
||||
* are <code>FRAGMENT_AUTODETECT</code> (default) for auto detect, <code>FRAGMENT_NO</code> and <code>FRAGMENT_YES</code>.
|
||||
*
|
||||
* @param fragment the fragment to set
|
||||
* @since 2.4
|
||||
*/
|
||||
public void setFragment(int fragment) {
|
||||
if ((fragment < FRAGMENT_AUTODETECT) || (fragment > FRAGMENT_YES)) throw new IllegalArgumentException("Fragment value out of range.");
|
||||
this.fragment = fragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the lastModified
|
||||
* @since 2.4
|
||||
*/
|
||||
public long getLastModified() {
|
||||
return lastModified;
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>lastModified</b> - defines if the last modified header will be sent in the response. Acceptable values are
|
||||
* <code>LAST_MODIFIED_OFF</code> for don't sending the header, even it is set in the filter chain,
|
||||
* <code>LAST_MODIFIED_ON</code> for sending it if it is set in the filter chain and
|
||||
* <code>LAST_MODIFIED_INITIAL</code> (default) the last modified information will be set based on the current time and changes are allowed.
|
||||
*
|
||||
* @param lastModified the lastModified to set
|
||||
* @since 2.4
|
||||
*/
|
||||
public void setLastModified(long lastModified) {
|
||||
if ((lastModified < LAST_MODIFIED_INITIAL) || (lastModified > LAST_MODIFIED_ON)) throw new IllegalArgumentException("LastModified value out of range.");
|
||||
this.lastModified = lastModified;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the nocache
|
||||
* @since 2.4
|
||||
*/
|
||||
public int getNocache() {
|
||||
return nocache;
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>nocache</b> - defines which objects shouldn't be cached. Acceptable values
|
||||
* are <code>NOCACHE_OFF</code> (default) and <code>NOCACHE_SESSION_ID_IN_URL</code> if the session id is
|
||||
* contained in the URL.
|
||||
*
|
||||
* @param nocache the nocache to set
|
||||
* @since 2.4
|
||||
*/
|
||||
public void setNocache(int nocache) {
|
||||
if ((nocache < NOCACHE_OFF) || (nocache > NOCACHE_SESSION_ID_IN_URL)) throw new IllegalArgumentException("Nocache value out of range.");
|
||||
this.nocache = nocache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the time
|
||||
* @since 2.4
|
||||
*/
|
||||
public int getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>time</b> - the default time (in seconds) to cache content for. The default
|
||||
* value is 3600 seconds (one hour). Specifying -1 (indefinite expiry) as the cache
|
||||
* time will ensure a content does not become stale until it is either explicitly
|
||||
* flushed or the expires refresh policy causes the entry to expire.
|
||||
*
|
||||
* @param time the time to set
|
||||
* @since 2.4
|
||||
*/
|
||||
public void setTime(int time) {
|
||||
this.time = time;
|
||||
// check if ExpiresRefreshPolicy has to be reset
|
||||
if (expiresRefreshPolicy instanceof ExpiresRefreshPolicy) {
|
||||
((ExpiresRefreshPolicy) expiresRefreshPolicy).setRefreshPeriod(time);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @link http://java.sun.com/j2ee/sdk_1.3/techdocs/api/javax/servlet/http/HttpServletRequest.html#getMethod()
|
||||
* @return the list of http method names for which cacheing should be disabled
|
||||
* @since 2.4
|
||||
*/
|
||||
public List getDisableCacheOnMethods() {
|
||||
return disableCacheOnMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>disableCacheOnMethods</b> - Defines the http method name for which cacheing should be disabled.
|
||||
* The default value is <code>null</code> for cacheing all requests without regarding the method name.
|
||||
* @link http://java.sun.com/j2ee/sdk_1.3/techdocs/api/javax/servlet/http/HttpServletRequest.html#getMethod()
|
||||
* @param disableCacheOnMethods the list of http method names for which cacheing should be disabled
|
||||
* @since 2.4
|
||||
*/
|
||||
public void setDisableCacheOnMethods(List disableCacheOnMethods) {
|
||||
this.disableCacheOnMethods = disableCacheOnMethods;
|
||||
}
|
||||
|
||||
// TODO: check if getter/setter for oscache-properties-file is possible
|
||||
|
||||
}
|
|
@ -0,0 +1,439 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2008 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.web.filter;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpServletResponseWrapper;
|
||||
|
||||
/**
|
||||
* CacheServletResponse is a serialized representation of a response
|
||||
*
|
||||
* @author <a href="mailto:sergek@lokitech.com">Serge Knystautas</a>
|
||||
* @version $Revision$
|
||||
*/
|
||||
public class CacheHttpServletResponseWrapper extends HttpServletResponseWrapper {
|
||||
|
||||
private final Log log = LogFactory.getLog(this.getClass());
|
||||
|
||||
/**
|
||||
* We cache the printWriter so we can maintain a single instance
|
||||
* of it no matter how many times it is requested.
|
||||
*/
|
||||
private PrintWriter cachedWriter = null;
|
||||
private ResponseContent result = null;
|
||||
private SplitServletOutputStream cacheOut = null;
|
||||
private boolean fragment = false;
|
||||
private int status = SC_OK;
|
||||
private long expires = CacheFilter.EXPIRES_ON;
|
||||
private long lastModified = CacheFilter.LAST_MODIFIED_INITIAL;
|
||||
private long cacheControl = -60;
|
||||
private int etagOption = CacheFilter.ETAG_WEAK;
|
||||
private String etag = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param response The servlet response
|
||||
*/
|
||||
public CacheHttpServletResponseWrapper(HttpServletResponse response) {
|
||||
this(response, false, Long.MAX_VALUE, CacheFilter.EXPIRES_ON, CacheFilter.LAST_MODIFIED_INITIAL, -60, CacheFilter.ETAG_WEAK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param response The servlet response
|
||||
* @param fragment true if the repsonse indicates that it is a fragement of a page
|
||||
* @param time the refresh time in millis
|
||||
* @param lastModified defines if last modified header will be send, @see CacheFilter
|
||||
* @param expires defines if expires header will be send, @see CacheFilter
|
||||
* @param cacheControl defines if cache control header will be send, @see CacheFilter
|
||||
* @param etagOption defines if the ETag header will be send, @see CacheFilter
|
||||
*/
|
||||
public CacheHttpServletResponseWrapper(HttpServletResponse response, boolean fragment, long time, long lastModified, long expires, long cacheControl, int etagOption) {
|
||||
super(response);
|
||||
result = new ResponseContent();
|
||||
this.fragment = fragment;
|
||||
this.expires = expires;
|
||||
this.lastModified = lastModified;
|
||||
this.cacheControl = cacheControl;
|
||||
this.etagOption = etagOption;
|
||||
|
||||
// only set initial values for last modified and expires, when a complete page is cached
|
||||
if (!fragment) {
|
||||
// setting a default last modified value based on object creation and remove the millis
|
||||
if (lastModified == CacheFilter.LAST_MODIFIED_INITIAL) {
|
||||
long current = System.currentTimeMillis();
|
||||
current = current - (current % 1000);
|
||||
result.setLastModified(current);
|
||||
super.setDateHeader(CacheFilter.HEADER_LAST_MODIFIED, result.getLastModified());
|
||||
}
|
||||
// setting the expires value
|
||||
if (expires == CacheFilter.EXPIRES_TIME) {
|
||||
result.setExpires(result.getLastModified() + time);
|
||||
super.setDateHeader(CacheFilter.HEADER_EXPIRES, result.getExpires());
|
||||
}
|
||||
// setting the cache control with max-age
|
||||
if (this.cacheControl == CacheFilter.MAX_AGE_TIME) {
|
||||
// set the count down
|
||||
long maxAge = System.currentTimeMillis();
|
||||
maxAge = maxAge - (maxAge % 1000) + time;
|
||||
result.setMaxAge(maxAge);
|
||||
super.addHeader(CacheFilter.HEADER_CACHE_CONTROL, "max-age=" + time / 1000);
|
||||
} else if (this.cacheControl != CacheFilter.MAX_AGE_NO_INIT) {
|
||||
result.setMaxAge(this.cacheControl);
|
||||
super.addHeader(CacheFilter.HEADER_CACHE_CONTROL, "max-age=" + (-this.cacheControl));
|
||||
} else if (this.cacheControl == CacheFilter.MAX_AGE_NO_INIT ) {
|
||||
result.setMaxAge(this.cacheControl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a response content
|
||||
*
|
||||
* @return The content
|
||||
*/
|
||||
public ResponseContent getContent() {
|
||||
// Flush the buffer
|
||||
try {
|
||||
flush();
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
|
||||
// Create the byte array
|
||||
result.commit();
|
||||
|
||||
// Return the result from this response
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the content type
|
||||
*
|
||||
* @param value The content type
|
||||
*/
|
||||
public void setContentType(String value) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("ContentType: " + value);
|
||||
}
|
||||
|
||||
super.setContentType(value);
|
||||
result.setContentType(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the date of a header
|
||||
*
|
||||
* @param name The header name
|
||||
* @param value The date
|
||||
*/
|
||||
public void setDateHeader(String name, long value) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("dateheader: " + name + ": " + value);
|
||||
}
|
||||
|
||||
// only set the last modified value, if a complete page is cached
|
||||
if ((lastModified != CacheFilter.LAST_MODIFIED_OFF) && (CacheFilter.HEADER_LAST_MODIFIED.equalsIgnoreCase(name))) {
|
||||
if (!fragment) {
|
||||
result.setLastModified(value);
|
||||
} // TODO should we return now by fragments to avoid putting the header to the response?
|
||||
}
|
||||
|
||||
// implement RFC 2616 14.21 Expires (without max-age)
|
||||
if ((expires != CacheFilter.EXPIRES_OFF) && (CacheFilter.HEADER_EXPIRES.equalsIgnoreCase(name))) {
|
||||
if (!fragment) {
|
||||
result.setExpires(value);
|
||||
} // TODO should we return now by fragments to avoid putting the header to the response?
|
||||
}
|
||||
|
||||
super.setDateHeader(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the date of a header
|
||||
*
|
||||
* @param name The header name
|
||||
* @param value The date
|
||||
*/
|
||||
public void addDateHeader(String name, long value) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("dateheader: " + name + ": " + value);
|
||||
}
|
||||
|
||||
// only set the last modified value, if a complete page is cached
|
||||
if ((lastModified != CacheFilter.LAST_MODIFIED_OFF) && (CacheFilter.HEADER_LAST_MODIFIED.equalsIgnoreCase(name))) {
|
||||
if (!fragment) {
|
||||
result.setLastModified(value);
|
||||
} // TODO should we return now by fragments to avoid putting the header to the response?
|
||||
}
|
||||
|
||||
// implement RFC 2616 14.21 Expires (without max-age)
|
||||
if ((expires != CacheFilter.EXPIRES_OFF) && (CacheFilter.HEADER_EXPIRES.equalsIgnoreCase(name))) {
|
||||
if (!fragment) {
|
||||
result.setExpires(value);
|
||||
} // TODO should we return now by fragments to avoid putting the header to the response?
|
||||
}
|
||||
|
||||
super.addDateHeader(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a header field
|
||||
*
|
||||
* @param name The header name
|
||||
* @param value The header value
|
||||
*/
|
||||
public void setHeader(String name, String value) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("header: " + name + ": " + value);
|
||||
}
|
||||
|
||||
if (CacheFilter.HEADER_CONTENT_TYPE.equalsIgnoreCase(name)) {
|
||||
result.setContentType(value);
|
||||
}
|
||||
|
||||
if (CacheFilter.HEADER_CONTENT_ENCODING.equalsIgnoreCase(name)) {
|
||||
result.setContentEncoding(value);
|
||||
}
|
||||
|
||||
if (CacheFilter.HEADER_ETAG.equalsIgnoreCase(name)) {
|
||||
result.setETag(value);
|
||||
}
|
||||
|
||||
if (CacheFilter.HEADER_CONTENT_DISPOSITION.equalsIgnoreCase(name)) {
|
||||
result.setContentDisposition(value);
|
||||
}
|
||||
|
||||
super.setHeader(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a header field
|
||||
*
|
||||
* @param name The header name
|
||||
* @param value The header value
|
||||
*/
|
||||
public void addHeader(String name, String value) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("header: " + name + ": " + value);
|
||||
}
|
||||
|
||||
if (CacheFilter.HEADER_CONTENT_TYPE.equalsIgnoreCase(name)) {
|
||||
result.setContentType(value);
|
||||
}
|
||||
|
||||
if (CacheFilter.HEADER_CONTENT_ENCODING.equalsIgnoreCase(name)) {
|
||||
result.setContentEncoding(value);
|
||||
}
|
||||
|
||||
if (CacheFilter.HEADER_ETAG.equalsIgnoreCase(name)) {
|
||||
result.setETag(value);
|
||||
}
|
||||
|
||||
if (CacheFilter.HEADER_CONTENT_DISPOSITION.equalsIgnoreCase(name)) {
|
||||
result.setContentDisposition(value);
|
||||
}
|
||||
|
||||
super.addHeader(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the int value of the header
|
||||
*
|
||||
* @param name The header name
|
||||
* @param value The int value
|
||||
*/
|
||||
public void setIntHeader(String name, int value) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("intheader: " + name + ": " + value);
|
||||
}
|
||||
|
||||
super.setIntHeader(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* We override this so we can catch the response status. Only
|
||||
* responses with a status of 200 (<code>SC_OK</code>) will
|
||||
* be cached.
|
||||
*/
|
||||
public void setStatus(int status) {
|
||||
super.setStatus(status);
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* We override this so we can catch the response status. Only
|
||||
* responses with a status of 200 (<code>SC_OK</code>) will
|
||||
* be cached.
|
||||
*/
|
||||
public void sendError(int status, String string) throws IOException {
|
||||
super.sendError(status, string);
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* We override this so we can catch the response status. Only
|
||||
* responses with a status of 200 (<code>SC_OK</code>) will
|
||||
* be cached.
|
||||
*/
|
||||
public void sendError(int status) throws IOException {
|
||||
super.sendError(status);
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* We override this so we can catch the response status. Only
|
||||
* responses with a status of 200 (<code>SC_OK</code>) will
|
||||
* be cached.
|
||||
*/
|
||||
public void setStatus(int status, String string) {
|
||||
super.setStatus(status, string);
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* We override this so we can catch the response status. Only
|
||||
* responses with a status of 200 (<code>SC_OK</code>) will
|
||||
* be cached.
|
||||
*/
|
||||
public void sendRedirect(String location) throws IOException {
|
||||
this.status = SC_MOVED_TEMPORARILY;
|
||||
super.sendRedirect(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the captured HttpResponse status.
|
||||
*/
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the locale
|
||||
*
|
||||
* @param value The locale
|
||||
*/
|
||||
public void setLocale(Locale value) {
|
||||
super.setLocale(value);
|
||||
result.setLocale(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an output stream
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public ServletOutputStream getOutputStream() throws IOException {
|
||||
// Pass this faked servlet output stream that captures what is sent
|
||||
if (cacheOut == null) {
|
||||
cacheOut = new SplitServletOutputStream(result.getOutputStream(), super.getOutputStream());
|
||||
}
|
||||
|
||||
return cacheOut;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a print writer
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public PrintWriter getWriter() throws IOException {
|
||||
if (cachedWriter == null) {
|
||||
String encoding = getCharacterEncoding();
|
||||
if (encoding != null) {
|
||||
cachedWriter = new PrintWriter(new OutputStreamWriter(getOutputStream(), encoding));
|
||||
} else { // using the default character encoding
|
||||
cachedWriter = new PrintWriter(new OutputStreamWriter(getOutputStream()));
|
||||
}
|
||||
}
|
||||
|
||||
return cachedWriter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes all streams.
|
||||
* @throws IOException
|
||||
*/
|
||||
private void flush() throws IOException {
|
||||
if (cacheOut != null) {
|
||||
cacheOut.flush();
|
||||
}
|
||||
|
||||
if (cachedWriter != null) {
|
||||
cachedWriter.flush();
|
||||
}
|
||||
}
|
||||
|
||||
public void flushBuffer() throws IOException {
|
||||
// The weak ETag is content size + lastModified
|
||||
if (etag == null) {
|
||||
if (etagOption == CacheFilter.ETAG_WEAK) {
|
||||
etag = "W/\"" + result.getSize() + "-" + result.getLastModified() + "\"";
|
||||
result.setETag(etag);
|
||||
}
|
||||
}
|
||||
super.flushBuffer();
|
||||
flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boolean indicating if the response has been committed.
|
||||
* A committed response has already had its status code and headers written.
|
||||
*
|
||||
* @see javax.servlet.ServletResponseWrapper#isCommitted()
|
||||
*/
|
||||
public boolean isCommitted() {
|
||||
return super.isCommitted(); // || (result.getOutputStream() == null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears any data that exists in the buffer as well as the status code and headers.
|
||||
* If the response has been committed, this method throws an IllegalStateException.
|
||||
* @see javax.servlet.ServletResponseWrapper#reset()
|
||||
*/
|
||||
public void reset() {
|
||||
log.info("CacheHttpServletResponseWrapper:reset()");
|
||||
super.reset();
|
||||
/*
|
||||
cachedWriter = null;
|
||||
result = new ResponseContent();
|
||||
cacheOut = null;
|
||||
fragment = false;
|
||||
status = SC_OK;
|
||||
expires = CacheFilter.EXPIRES_ON;
|
||||
lastModified = CacheFilter.LAST_MODIFIED_INITIAL;
|
||||
cacheControl = -60;
|
||||
etag = null;
|
||||
// time ?
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the content of the underlying buffer in the response without clearing headers or status code.
|
||||
* If the response has been committed, this method throws an IllegalStateException.
|
||||
* @see javax.servlet.ServletResponseWrapper#resetBuffer()
|
||||
*/
|
||||
public void resetBuffer() {
|
||||
log.info("CacheHttpServletResponseWrapper:resetBuffer()");
|
||||
super.resetBuffer();
|
||||
/*
|
||||
//cachedWriter = null;
|
||||
result = new ResponseContent();
|
||||
//cacheOut = null;
|
||||
//fragment = false;
|
||||
*/
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.web.filter;
|
||||
|
||||
import com.opensymphony.oscache.base.CacheEntry;
|
||||
import com.opensymphony.oscache.base.EntryRefreshPolicy;
|
||||
import com.opensymphony.oscache.base.NeedsRefreshException;
|
||||
|
||||
/**
|
||||
* Checks if a cache filter entry has expired.
|
||||
* This is useful when expires header are used in the response.
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:ltorunski [ AT ] t-online.de">Lars Torunski</a>
|
||||
*/
|
||||
public class ExpiresRefreshPolicy implements EntryRefreshPolicy {
|
||||
|
||||
/** the refresh period (in milliseconds) of a certain cache filter*/
|
||||
private long refreshPeriod;
|
||||
|
||||
/**
|
||||
* Constructor ExpiresRefreshPolicy.
|
||||
*
|
||||
* @param refreshPeriod the refresh period in seconds
|
||||
*/
|
||||
public ExpiresRefreshPolicy(int refreshPeriod) {
|
||||
this.refreshPeriod = refreshPeriod * 1000L;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the supplied <code>CacheEntry</code> needs to be refreshed.
|
||||
* This will be called when retrieving an entry from the cache - if this method
|
||||
* returns <code>true</code> then a <code>NeedsRefreshException</code> will be
|
||||
* thrown.
|
||||
*
|
||||
* @param entry The cache entry which is ignored.
|
||||
* @return <code>true</code> if the content needs refreshing, <code>false</code> otherwise.
|
||||
*
|
||||
* @see NeedsRefreshException
|
||||
* @see CacheEntry
|
||||
*/
|
||||
public boolean needsRefresh(CacheEntry entry) {
|
||||
|
||||
long currentTimeMillis = System.currentTimeMillis();
|
||||
|
||||
if ((refreshPeriod >= 0) && (currentTimeMillis >= (entry.getLastUpdate() + refreshPeriod))) {
|
||||
return true;
|
||||
} else if (entry.getContent() instanceof ResponseContent) {
|
||||
ResponseContent responseContent = (ResponseContent) entry.getContent();
|
||||
return currentTimeMillis >= responseContent.getExpires();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the refreshPeriod in seconds
|
||||
* @since 2.4
|
||||
*/
|
||||
public long getRefreshPeriod() {
|
||||
return refreshPeriod / 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param refreshPeriod the refresh period in seconds
|
||||
* @since 2.4
|
||||
*/
|
||||
public void setRefreshPeriod(long refreshPeriod) {
|
||||
this.refreshPeriod = refreshPeriod * 1000L;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.web.filter;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import com.opensymphony.oscache.base.Cache;
|
||||
import com.opensymphony.oscache.web.ServletCacheAdministrator;
|
||||
|
||||
/**
|
||||
* Provider interface for cache groups creation in CacheFilter. A developer can implement a method which provides
|
||||
* cache groups based on the request, the servlet cache administrator and cache.
|
||||
*
|
||||
* JIRA issue: http://jira.opensymphony.com/browse/CACHE-195
|
||||
*
|
||||
* @author <a href="mailto:ltorunski@t-online.de">Lars Torunski</a>
|
||||
* @version $Revision$
|
||||
*/
|
||||
public interface ICacheGroupsProvider {
|
||||
|
||||
/**
|
||||
* Creates the cache groups for the CacheFilter.
|
||||
*
|
||||
* @param httpRequest the http request.
|
||||
* @param scAdmin the ServletCacheAdministrator of the cache
|
||||
* @param cache the cache of the ServletCacheAdministrator
|
||||
* @return the cache key
|
||||
*/
|
||||
public String[] createCacheGroups(HttpServletRequest httpRequest, ServletCacheAdministrator scAdmin, Cache cache);
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.web.filter;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import com.opensymphony.oscache.base.Cache;
|
||||
import com.opensymphony.oscache.web.ServletCacheAdministrator;
|
||||
|
||||
/**
|
||||
* Provider interface for cache key creation. A developer can implement a method which provides
|
||||
* cache keys based on the request, the servlet cache administrator and cache.
|
||||
*
|
||||
* JIRA issue: http://jira.opensymphony.com/browse/CACHE-179
|
||||
*
|
||||
* @author <a href="mailto:ltorunski@t-online.de">Lars Torunski</a>
|
||||
* @version $Revision$
|
||||
*/
|
||||
public interface ICacheKeyProvider {
|
||||
|
||||
/**
|
||||
* Creates the cache key for the CacheFilter.
|
||||
*
|
||||
* @param httpRequest the http request.
|
||||
* @param scAdmin the ServletCacheAdministrator of the cache
|
||||
* @param cache the cache of the ServletCacheAdministrator
|
||||
* @return the cache key
|
||||
*/
|
||||
public String createCacheKey(HttpServletRequest httpRequest, ServletCacheAdministrator scAdmin, Cache cache);
|
||||
|
||||
}
|
|
@ -0,0 +1,265 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2009 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.web.filter;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Holds the servlet response in a byte array so that it can be held
|
||||
* in the cache (and, since this class is serializable, optionally
|
||||
* persisted to disk).
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:sergek@lokitech.com">Serge Knystautas</a>
|
||||
*/
|
||||
public class ResponseContent implements Serializable {
|
||||
private transient ByteArrayOutputStream bout = new ByteArrayOutputStream(1000);
|
||||
private Locale locale = null;
|
||||
private String contentEncoding = null;
|
||||
private String contentType = null;
|
||||
private byte[] content = null;
|
||||
private long expires = Long.MAX_VALUE;
|
||||
private long lastModified = -1;
|
||||
private long maxAge = -60;
|
||||
private String etag = null;
|
||||
private String contentDisposition = null;
|
||||
|
||||
public String getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the content type. We capture this so that when we serve this
|
||||
* data from cache, we can set the correct content type on the response.
|
||||
*/
|
||||
public void setContentType(String value) {
|
||||
contentType = value;
|
||||
}
|
||||
|
||||
public long getLastModified() {
|
||||
return lastModified;
|
||||
}
|
||||
|
||||
public void setLastModified(long value) {
|
||||
lastModified = value;
|
||||
}
|
||||
|
||||
public String getContentEncoding() {
|
||||
return contentEncoding;
|
||||
}
|
||||
|
||||
public void setContentEncoding(String contentEncoding) {
|
||||
this.contentEncoding = contentEncoding;
|
||||
}
|
||||
|
||||
public String getETag() {
|
||||
return etag;
|
||||
}
|
||||
|
||||
public void setETag(String etag) {
|
||||
this.etag = etag;
|
||||
}
|
||||
|
||||
public String getContentDisposition() {
|
||||
return contentDisposition;
|
||||
}
|
||||
|
||||
public void setContentDisposition(String contentDisposition) {
|
||||
this.contentDisposition = contentDisposition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Locale. We capture this so that when we serve this data from
|
||||
* cache, we can set the correct locale on the response.
|
||||
*/
|
||||
public void setLocale(Locale value) {
|
||||
locale = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the expires date and time in milliseconds when the content will be stale
|
||||
*/
|
||||
public long getExpires() {
|
||||
return expires;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the expires date and time in milliseconds.
|
||||
* @param value time in milliseconds when the content will expire
|
||||
*/
|
||||
public void setExpires(long value) {
|
||||
expires = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the max age of the content in milliseconds. If expires header and cache control are
|
||||
* enabled both, both will be equal.
|
||||
* @return the max age of the content in milliseconds, if -1 max-age is disabled
|
||||
*/
|
||||
public long getMaxAge() {
|
||||
return maxAge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the max age date and time in milliseconds. If the parameter is -1, the max-age parameter
|
||||
* won't be set by default in the Cache-Control header.
|
||||
* @param value sets the intial
|
||||
*/
|
||||
public void setMaxAge(long value) {
|
||||
maxAge = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an output stream. This is used by the {@link SplitServletOutputStream}
|
||||
* to capture the original (uncached) response into a byte array.
|
||||
* @return the original (uncached) response, returns null if response is already committed.
|
||||
*/
|
||||
public OutputStream getOutputStream() {
|
||||
return bout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size of this cached content.
|
||||
*
|
||||
* @return The size of the content, in bytes. If no content
|
||||
* exists, this method returns <code>-1</code>.
|
||||
*/
|
||||
public int getSize() {
|
||||
return (content != null) ? content.length : (-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once the response has been written in its entirety. This
|
||||
* method commits the response output stream by converting the output
|
||||
* stream into a byte array.
|
||||
*/
|
||||
public void commit() {
|
||||
if (bout != null) {
|
||||
content = bout.toByteArray();
|
||||
bout = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes this cached data out to the supplied <code>ServletResponse</code>.
|
||||
*
|
||||
* @param response The servlet response to output the cached content to.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void writeTo(ServletResponse response) throws IOException {
|
||||
writeTo(response, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes this cached data out to the supplied <code>ServletResponse</code>.
|
||||
*
|
||||
* @param response The servlet response to output the cached content to.
|
||||
* @param fragment is true if this content a fragment or part of a page
|
||||
* @param acceptsGZip is true if client browser supports gzip compression
|
||||
* @throws IOException
|
||||
*/
|
||||
public void writeTo(ServletResponse response, boolean fragment, boolean acceptsGZip) throws IOException {
|
||||
//Send the content type and data to this response
|
||||
if (contentType != null) {
|
||||
response.setContentType(contentType);
|
||||
}
|
||||
|
||||
if (fragment) {
|
||||
// Don't support gzip compression if the content is a fragment of a page
|
||||
acceptsGZip = false;
|
||||
} else {
|
||||
// add special headers for a complete page
|
||||
if (response instanceof HttpServletResponse) {
|
||||
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
||||
|
||||
// add the last modified header
|
||||
if (lastModified != -1) {
|
||||
httpResponse.setDateHeader(CacheFilter.HEADER_LAST_MODIFIED, lastModified);
|
||||
}
|
||||
|
||||
// add the etag header
|
||||
if (etag != null) {
|
||||
httpResponse.addHeader(CacheFilter.HEADER_ETAG, etag);
|
||||
}
|
||||
|
||||
// add the content disposition header
|
||||
if(contentDisposition != null) {
|
||||
httpResponse.addHeader(CacheFilter.HEADER_CONTENT_DISPOSITION, contentDisposition);
|
||||
}
|
||||
|
||||
// add the expires header
|
||||
if (expires != Long.MAX_VALUE) {
|
||||
httpResponse.setDateHeader(CacheFilter.HEADER_EXPIRES, expires);
|
||||
}
|
||||
|
||||
// add the cache-control header for max-age
|
||||
if (maxAge == CacheFilter.MAX_AGE_NO_INIT || maxAge == CacheFilter.MAX_AGE_TIME) {
|
||||
// do nothing
|
||||
} else if (maxAge > 0) { // set max-age based on life time
|
||||
long currentMaxAge = maxAge / 1000 - System.currentTimeMillis() / 1000;
|
||||
if (currentMaxAge < 0) {
|
||||
currentMaxAge = 0;
|
||||
}
|
||||
httpResponse.addHeader(CacheFilter.HEADER_CACHE_CONTROL, "max-age=" + currentMaxAge);
|
||||
} else {
|
||||
httpResponse.addHeader(CacheFilter.HEADER_CACHE_CONTROL, "max-age=" + (-maxAge));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (locale != null) {
|
||||
response.setLocale(locale);
|
||||
}
|
||||
|
||||
OutputStream out = new BufferedOutputStream(response.getOutputStream());
|
||||
|
||||
if (isContentGZiped()) {
|
||||
if (acceptsGZip) {
|
||||
((HttpServletResponse) response).addHeader(CacheFilter.HEADER_CONTENT_ENCODING, "gzip");
|
||||
response.setContentLength(content.length);
|
||||
out.write(content);
|
||||
} else {
|
||||
// client doesn't support, so we have to uncompress it
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(content);
|
||||
GZIPInputStream zis = new GZIPInputStream(bais);
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
int numBytesRead = 0;
|
||||
byte[] tempBytes = new byte[4196];
|
||||
|
||||
while ((numBytesRead = zis.read(tempBytes, 0, tempBytes.length)) != -1) {
|
||||
baos.write(tempBytes, 0, numBytesRead);
|
||||
}
|
||||
|
||||
byte[] result = baos.toByteArray();
|
||||
|
||||
response.setContentLength(result.length);
|
||||
out.write(result);
|
||||
}
|
||||
} else {
|
||||
// the content isn't compressed
|
||||
// regardless if the client browser supports gzip we will just return the content
|
||||
response.setContentLength(content.length);
|
||||
out.write(content);
|
||||
}
|
||||
out.flush();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return true if the content is GZIP compressed
|
||||
*/
|
||||
public boolean isContentGZiped() {
|
||||
return "gzip".equals(contentEncoding);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.web.filter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javax.servlet.ServletOutputStream;
|
||||
|
||||
/**
|
||||
* Extends the base <code>ServletOutputStream</code> class so that
|
||||
* the stream can be captured as it gets written. This is achieved
|
||||
* by overriding the <code>write()</code> methods and outputting
|
||||
* the data to two streams - the original stream and a secondary stream
|
||||
* that is designed to capture the written data.
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author <a href="mailto:sergek@lokitech.com">Serge Knystautas</a>
|
||||
*/
|
||||
public class SplitServletOutputStream extends ServletOutputStream {
|
||||
OutputStream captureStream = null;
|
||||
OutputStream passThroughStream = null;
|
||||
|
||||
/**
|
||||
* Constructs a split output stream that both captures and passes through
|
||||
* the servlet response.
|
||||
*
|
||||
* @param captureStream The stream that will be used to capture the data.
|
||||
* @param passThroughStream The pass-through <code>ServletOutputStream</code>
|
||||
* that will write the response to the client as originally intended.
|
||||
*/
|
||||
public SplitServletOutputStream(OutputStream captureStream, OutputStream passThroughStream) {
|
||||
this.captureStream = captureStream;
|
||||
this.passThroughStream = passThroughStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the incoming data to both the output streams.
|
||||
*
|
||||
* @param value The int data to write.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void write(int value) throws IOException {
|
||||
captureStream.write(value);
|
||||
passThroughStream.write(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the incoming data to both the output streams.
|
||||
*
|
||||
* @param value The bytes to write to the streams.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void write(byte[] value) throws IOException {
|
||||
captureStream.write(value);
|
||||
passThroughStream.write(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the incoming data to both the output streams.
|
||||
*
|
||||
* @param b The bytes to write out to the streams.
|
||||
* @param off The offset into the byte data where writing should begin.
|
||||
* @param len The number of bytes to write.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
captureStream.write(b, off, len);
|
||||
passThroughStream.write(b, off, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes both the output streams.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void flush() throws IOException {
|
||||
super.flush();
|
||||
captureStream.flush(); //why not?
|
||||
passThroughStream.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes both the output streams.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
captureStream.close();
|
||||
passThroughStream.close();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
@(#)package.html
|
||||
|
||||
Copyright (c) 2002-2003 by OpenSymphony
|
||||
All rights reserved.
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Provides the caching filter (and its support classes) that allows HTTP responses
|
||||
to be cached by OSCache.
|
||||
|
||||
|
||||
<h2>Package Specification</h2>
|
||||
|
||||
<h2>Related Documentation</h2>
|
||||
|
||||
|
||||
For overviews, tutorials, examples, guides, and tool documentation, please see:
|
||||
<ul>
|
||||
<li><a href="http://www.opensymphony.com/oscache">The OSCache Homepage</a>
|
||||
<li><a href="http://www.opensymphony.com/oscache/filter.jsp">Caching Filter Documentation</a>
|
||||
</ul>
|
||||
|
||||
<!-- Put @see and @since tags down here. -->
|
||||
|
||||
</body>
|
||||
</html>
|
31
src/main/java/com/opensymphony/oscache/web/package.html
Normal file
31
src/main/java/com/opensymphony/oscache/web/package.html
Normal file
|
@ -0,0 +1,31 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
@(#)package.html
|
||||
|
||||
Copyright (c) 2002-2003 by OpenSymphony
|
||||
All rights reserved.
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Provides classes and interfaces that make up the base of OSCache's web application support.
|
||||
|
||||
|
||||
<h2>Package Specification</h2>
|
||||
|
||||
<h2>Related Documentation</h2>
|
||||
|
||||
|
||||
For overviews, tutorials, examples, guides, and tool documentation, please see:
|
||||
<ul>
|
||||
<li><a href="http://www.opensymphony.com/oscache">The OSCache Homepage</a>
|
||||
</ul>
|
||||
|
||||
<!-- Put @see and @since tags down here. -->
|
||||
|
||||
</body>
|
||||
</html>
|
824
src/main/java/com/opensymphony/oscache/web/tag/CacheTag.java
Normal file
824
src/main/java/com/opensymphony/oscache/web/tag/CacheTag.java
Normal file
|
@ -0,0 +1,824 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.web.tag;
|
||||
|
||||
import com.opensymphony.oscache.base.Cache;
|
||||
import com.opensymphony.oscache.base.NeedsRefreshException;
|
||||
import com.opensymphony.oscache.util.StringUtil;
|
||||
import com.opensymphony.oscache.web.ServletCacheAdministrator;
|
||||
import com.opensymphony.oscache.web.WebEntryRefreshPolicy;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.jsp.JspTagException;
|
||||
import javax.servlet.jsp.PageContext;
|
||||
import javax.servlet.jsp.tagext.BodyTagSupport;
|
||||
import javax.servlet.jsp.tagext.TryCatchFinally;
|
||||
|
||||
/**
|
||||
* CacheTag is a tag that allows for server-side caching of post-processed JSP content.<p>
|
||||
*
|
||||
* It also gives great programatic control over refreshing, flushing and updating the cache.<p>
|
||||
*
|
||||
* Usage Example:
|
||||
* <pre><code>
|
||||
* <%@ taglib uri="oscache" prefix="cache" %>
|
||||
* <cache:cache key="mycache"
|
||||
* scope="application"
|
||||
* refresh="false"
|
||||
* time="30">
|
||||
* jsp content here... refreshed every 30 seconds
|
||||
* </cache:cache>
|
||||
* </code></pre>
|
||||
*
|
||||
* @author <a href="mailto:mike@atlassian.com">Mike Cannon-Brookes</a>
|
||||
* @author <a href="mailto:tgochenour@peregrine.com">Todd Gochenour</a>
|
||||
* @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
|
||||
* @author <a href="mailto:abergevin@pyxis-tech.com">Alain Bergevin</a>
|
||||
* @version $Revision$
|
||||
*/
|
||||
public class CacheTag extends BodyTagSupport implements TryCatchFinally {
|
||||
/**
|
||||
* Constants for time computation
|
||||
*/
|
||||
private final static int SECOND = 1;
|
||||
private final static int MINUTE = 60 * SECOND;
|
||||
private final static int HOUR = 60 * MINUTE;
|
||||
private final static int DAY = 24 * HOUR;
|
||||
private final static int WEEK = 7 * DAY;
|
||||
private final static int MONTH = 30 * DAY;
|
||||
private final static int YEAR = 365 * DAY;
|
||||
|
||||
/**
|
||||
* The key under which the tag counter will be stored in the request
|
||||
*/
|
||||
private final static String CACHE_TAG_COUNTER_KEY = "__oscache_tag_counter";
|
||||
|
||||
/**
|
||||
* Constants for refresh time
|
||||
*/
|
||||
final static private int ONE_MINUTE = 60;
|
||||
final static private int ONE_HOUR = 60 * ONE_MINUTE;
|
||||
final static private int DEFAULT_TIMEOUT = ONE_HOUR;
|
||||
private static transient Log log = LogFactory.getLog(CacheTag.class);
|
||||
|
||||
/**
|
||||
* Cache modes
|
||||
*/
|
||||
final static private int SILENT_MODE = 1;
|
||||
|
||||
/**
|
||||
* A flag to indicate whether a NeedsRefreshException was thrown and
|
||||
* the update needs to be cancelled
|
||||
*/
|
||||
boolean cancelUpdateRequired = false;
|
||||
private Cache cache = null;
|
||||
|
||||
/**
|
||||
* If no groups are specified, the cached content does not get put into any groups
|
||||
*/
|
||||
private List groups = null;
|
||||
private ServletCacheAdministrator admin = null;
|
||||
|
||||
/**
|
||||
* The actual key to use. This is generated based on the supplied key, scope etc.
|
||||
*/
|
||||
private String actualKey = null;
|
||||
|
||||
/**
|
||||
* The content that was retrieved from cache
|
||||
*/
|
||||
private String content = null;
|
||||
|
||||
/**
|
||||
* The cron expression that is used to expire cache entries at specific dates and/or times.
|
||||
*/
|
||||
private String cron = null;
|
||||
|
||||
/**
|
||||
* if cache key is null, the request URI is used
|
||||
*/
|
||||
private String key = null;
|
||||
|
||||
/**
|
||||
* The ISO-639 language code to distinguish different pages in application scope
|
||||
*/
|
||||
private String language = null;
|
||||
|
||||
/**
|
||||
* Class used to handle the refresh policy logic
|
||||
*/
|
||||
private String refreshPolicyClass = null;
|
||||
|
||||
/**
|
||||
* Parameters that will be passed to the init method of the
|
||||
* refresh policy instance.
|
||||
*/
|
||||
private String refreshPolicyParam = null;
|
||||
|
||||
/**
|
||||
* Whether the cache should be refreshed instantly
|
||||
*/
|
||||
private boolean refresh = false;
|
||||
|
||||
/**
|
||||
* used for subtags to tell this tag that we should use the cached version
|
||||
*/
|
||||
private boolean useBody = true;
|
||||
|
||||
/**
|
||||
* The cache mode. Valid values are SILENT_MODE
|
||||
*/
|
||||
private int mode = 0;
|
||||
|
||||
/**
|
||||
* The cache scope to use
|
||||
*/
|
||||
private int scope = PageContext.APPLICATION_SCOPE;
|
||||
|
||||
/**
|
||||
* time (in seconds) before cache should be refreshed
|
||||
*/
|
||||
private int time = DEFAULT_TIMEOUT;
|
||||
|
||||
/**
|
||||
* Set the time this cache entry will be cached for. A date and/or time in
|
||||
* either ISO-8601 format or a simple format can be specified. The acceptable
|
||||
* syntax for the simple format can be any one of the following:
|
||||
*
|
||||
* <ul>
|
||||
* <li>0 (seconds)
|
||||
* <li>0s (seconds)
|
||||
* <li>0m (minutes)
|
||||
* <li>0h (hours)
|
||||
* <li>0d (days)
|
||||
* <li>0w (weeks)
|
||||
* </ul>
|
||||
*
|
||||
* @param duration The duration to cache this content (using either the simple
|
||||
* or the ISO-8601 format). Passing in a duration of zero will turn off the
|
||||
* caching, while a negative value will result in the cached content never
|
||||
* expiring (ie, the cached content will always be served as long as it is
|
||||
* present).
|
||||
*/
|
||||
public void setDuration(String duration) {
|
||||
try {
|
||||
// Try Simple Date Format Duration first because it's faster
|
||||
this.time = parseDuration(duration);
|
||||
} catch (Exception ex) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Failed parsing simple duration format '" + duration + "' (" + ex.getMessage() + "). Trying ISO-8601 format...");
|
||||
}
|
||||
|
||||
try {
|
||||
// Try ISO-8601 Duration
|
||||
this.time = parseISO_8601_Duration(duration);
|
||||
} catch (Exception ex1) {
|
||||
// An invalid duration entered, not much impact.
|
||||
// The default timeout will be used
|
||||
log.warn("The requested cache duration '" + duration + "' is invalid (" + ex1.getMessage() + "). Reverting to the default timeout");
|
||||
this.time = DEFAULT_TIMEOUT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cron expression that should be used to expire content at specific
|
||||
* dates and/or times.
|
||||
*/
|
||||
public void setCron(String cron) {
|
||||
this.cron = cron;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the groups for this cache entry. Any existing groups will
|
||||
* be replaced.
|
||||
*
|
||||
* @param groups A comma-delimited list of groups that the cache entry belongs to.
|
||||
*/
|
||||
public void setGroups(String groups) {
|
||||
// FIXME: ArrayList doesn't avoid duplicates
|
||||
this.groups = StringUtil.split(groups, ',');
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds to the groups for this cache entry.
|
||||
*
|
||||
* @param group A group to which the cache entry should belong.
|
||||
*/
|
||||
void addGroup(String group) {
|
||||
if (groups == null) {
|
||||
// FIXME: ArrayList doesn't avoid duplicates
|
||||
groups = new ArrayList();
|
||||
}
|
||||
|
||||
groups.add(group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds comma-delimited list of groups that the cache entry belongs to.
|
||||
*
|
||||
* @param groups A comma-delimited list of groups that the cache entry belongs to also.
|
||||
*/
|
||||
void addGroups(String groupsString) {
|
||||
if (groups == null) {
|
||||
// FIXME: ArrayList doesn't avoid duplicates
|
||||
groups = new ArrayList();
|
||||
}
|
||||
|
||||
groups.addAll(StringUtil.split(groupsString, ','));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the key for this cache entry.
|
||||
*
|
||||
* @param key The key for this cache entry.
|
||||
*/
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ISO-639 language code to distinguish different pages in application scope
|
||||
*
|
||||
* @param language The language code for this cache entry.
|
||||
*/
|
||||
public void setLanguage(String language) {
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method allows the user to programatically decide whether the cached
|
||||
* content should be refreshed immediately.
|
||||
*
|
||||
* @param refresh Whether or not to refresh this cache entry immediately.
|
||||
*/
|
||||
public void setRefresh(boolean refresh) {
|
||||
this.refresh = refresh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting this to <code>true</code> prevents the cache from writing any output
|
||||
* to the response, however the JSP content is still cached as normal.
|
||||
* @param mode The cache mode to use.
|
||||
*/
|
||||
public void setMode(String mode) {
|
||||
if ("silent".equalsIgnoreCase(mode)) {
|
||||
this.mode = SILENT_MODE;
|
||||
} else {
|
||||
this.mode = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class used to handle the refresh policy logic
|
||||
*/
|
||||
public void setRefreshpolicyclass(String refreshPolicyClass) {
|
||||
this.refreshPolicyClass = refreshPolicyClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters that will be passed to the init method of the
|
||||
* refresh policy instance.
|
||||
*/
|
||||
public void setRefreshpolicyparam(String refreshPolicyParam) {
|
||||
this.refreshPolicyParam = refreshPolicyParam;
|
||||
}
|
||||
|
||||
// ----------- setMethods ------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Set the scope of this cache.
|
||||
* <p>
|
||||
* @param scope The scope of this cache. Either "application" (default) or "session".
|
||||
*/
|
||||
public void setScope(String scope) {
|
||||
if (scope.equalsIgnoreCase(ServletCacheAdministrator.SESSION_SCOPE_NAME)) {
|
||||
this.scope = PageContext.SESSION_SCOPE;
|
||||
} else {
|
||||
this.scope = PageContext.APPLICATION_SCOPE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time this cache entry will be cached for (in seconds)
|
||||
*
|
||||
* @param time The time to cache this content (in seconds). Passing in
|
||||
* a time of zero will turn off the caching. A negative value for the
|
||||
* time will result in the cached content never expiring (ie, the cached
|
||||
* content will always be served if it is present)
|
||||
*/
|
||||
public void setTime(int time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
/**
|
||||
* This controls whether or not the body of the tag is evaluated or used.<p>
|
||||
*
|
||||
* It is most often called by the <UseCached /> tag to tell this tag to
|
||||
* use the cached content.
|
||||
*
|
||||
* @see UseCachedTag
|
||||
* @param useBody Whether or not to use the cached content.
|
||||
*/
|
||||
public void setUseBody(boolean useBody) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("<cache>: Set useBody to " + useBody);
|
||||
}
|
||||
|
||||
this.useBody = useBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* After the cache body, either update the cache, serve new cached content or
|
||||
* indicate an error.
|
||||
*
|
||||
* @throws JspTagException The standard exception thrown.
|
||||
* @return The standard BodyTag return.
|
||||
*/
|
||||
public int doAfterBody() throws JspTagException {
|
||||
String body = null;
|
||||
|
||||
try {
|
||||
// if we have a body, and we have not been told to use the cached version
|
||||
if ((bodyContent != null) && (useBody || (time == 0)) && ((body = bodyContent.getString()) != null)) {
|
||||
if ((time != 0) || (refreshPolicyClass != null)) {
|
||||
// Instantiate custom refresh policy if needed
|
||||
WebEntryRefreshPolicy policy = null;
|
||||
|
||||
if (refreshPolicyClass != null) {
|
||||
try {
|
||||
policy = (WebEntryRefreshPolicy) Class.forName(refreshPolicyClass).newInstance();
|
||||
policy.init(actualKey, refreshPolicyParam);
|
||||
} catch (Exception e) {
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("<cache>: Problem instantiating or initializing refresh policy : " + refreshPolicyClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("<cache>: Updating cache entry with new content : " + actualKey);
|
||||
}
|
||||
|
||||
cancelUpdateRequired = false;
|
||||
|
||||
if ((groups == null) || groups.isEmpty()) {
|
||||
cache.putInCache(actualKey, body, policy);
|
||||
} else {
|
||||
String[] groupArray = new String[groups.size()];
|
||||
groups.toArray(groupArray);
|
||||
cache.putInCache(actualKey, body, groupArray, policy, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
// otherwise if we have been told to use the cached content and we have cached content
|
||||
else {
|
||||
if (!useBody && (content != null)) {
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("<cache>: Using cached version as instructed, useBody = false : " + actualKey);
|
||||
}
|
||||
|
||||
body = content;
|
||||
}
|
||||
// either the cached entry is blank and a subtag has said don't useBody, or body is null
|
||||
else {
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("<cache>: Missing cached content : " + actualKey);
|
||||
}
|
||||
|
||||
body = "Missing cached content";
|
||||
}
|
||||
}
|
||||
|
||||
// Only display anything if we're not running in silent mode
|
||||
if (mode != SILENT_MODE) {
|
||||
bodyContent.clearBody();
|
||||
bodyContent.write(body);
|
||||
bodyContent.writeOut(bodyContent.getEnclosingWriter());
|
||||
}
|
||||
} catch (java.io.IOException e) {
|
||||
throw new JspTagException("IO Error: " + e.getMessage());
|
||||
}
|
||||
|
||||
return SKIP_BODY;
|
||||
}
|
||||
|
||||
public void doCatch(Throwable throwable) throws Throwable {
|
||||
throw throwable;
|
||||
}
|
||||
|
||||
/**
|
||||
* The end tag - clean up variables used.
|
||||
*
|
||||
* @throws JspTagException The standard exception thrown.
|
||||
* @return The standard BodyTag return.
|
||||
*/
|
||||
public int doEndTag() throws JspTagException {
|
||||
return EVAL_PAGE;
|
||||
}
|
||||
|
||||
public void doFinally() {
|
||||
if (cancelUpdateRequired && (actualKey != null)) {
|
||||
cache.cancelUpdate(actualKey);
|
||||
}
|
||||
|
||||
// reset all states, CACHE-144
|
||||
groups = null;
|
||||
scope = PageContext.APPLICATION_SCOPE;
|
||||
cron = null;
|
||||
key = null;
|
||||
language = null;
|
||||
refreshPolicyClass = null;
|
||||
refreshPolicyParam = null;
|
||||
time = DEFAULT_TIMEOUT;
|
||||
refresh = false;
|
||||
mode = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The start of the tag.
|
||||
* <p>
|
||||
* Grabs the administrator, the cache, the specific cache entry, then decides
|
||||
* whether to refresh.
|
||||
* <p>
|
||||
* If no refresh is needed, this serves the cached content directly.
|
||||
*
|
||||
* @throws JspTagException The standard exception thrown.
|
||||
* @return The standard doStartTag() return.
|
||||
*/
|
||||
public int doStartTag() throws JspTagException {
|
||||
cancelUpdateRequired = false;
|
||||
useBody = true;
|
||||
content = null;
|
||||
|
||||
// We can only skip the body if the cache has the data
|
||||
int returnCode = EVAL_BODY_BUFFERED;
|
||||
|
||||
if (admin == null) {
|
||||
admin = ServletCacheAdministrator.getInstance(pageContext.getServletContext());
|
||||
}
|
||||
|
||||
// Retrieve the cache
|
||||
if (scope == PageContext.SESSION_SCOPE) {
|
||||
cache = admin.getSessionScopeCache(((HttpServletRequest) pageContext.getRequest()).getSession(true));
|
||||
} else {
|
||||
cache = admin.getAppScopeCache(pageContext.getServletContext());
|
||||
}
|
||||
|
||||
// This allows to have multiple cache tags on a single page without
|
||||
// having to specify keys. However, nested cache tags are not supported.
|
||||
// In that case you would have to supply a key.
|
||||
String suffix = null;
|
||||
|
||||
if (key == null) {
|
||||
synchronized (pageContext.getRequest()) {
|
||||
Object o = pageContext.getRequest().getAttribute(CACHE_TAG_COUNTER_KEY);
|
||||
|
||||
if (o == null) {
|
||||
suffix = "1";
|
||||
} else {
|
||||
suffix = Integer.toString(Integer.parseInt((String) o) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
pageContext.getRequest().setAttribute(CACHE_TAG_COUNTER_KEY, suffix);
|
||||
}
|
||||
|
||||
// Generate the actual cache key
|
||||
actualKey = admin.generateEntryKey(key, (HttpServletRequest) pageContext.getRequest(), scope, language, suffix);
|
||||
|
||||
/*
|
||||
if
|
||||
- refresh is not set,
|
||||
- the cacheEntry itself does not need to be refreshed before 'time' and
|
||||
- the administrator has not had the cache entry's scope flushed
|
||||
|
||||
send out the cached version!
|
||||
*/
|
||||
try {
|
||||
if (refresh) {
|
||||
// Force a refresh
|
||||
content = (String) cache.getFromCache(actualKey, 0, cron);
|
||||
} else {
|
||||
// Use the specified refresh period
|
||||
content = (String) cache.getFromCache(actualKey, time, cron);
|
||||
}
|
||||
|
||||
try {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("<cache>: Using cached entry : " + actualKey);
|
||||
}
|
||||
|
||||
// Ensure that the cache returns the data correctly. Else re-evaluate the body
|
||||
if ((content != null)) {
|
||||
if (mode != SILENT_MODE) {
|
||||
pageContext.getOut().write(content);
|
||||
}
|
||||
|
||||
returnCode = SKIP_BODY;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new JspTagException("IO Exception: " + e.getMessage());
|
||||
}
|
||||
} catch (NeedsRefreshException nre) {
|
||||
cancelUpdateRequired = true;
|
||||
content = (String) nre.getCacheContent();
|
||||
}
|
||||
|
||||
if (returnCode == EVAL_BODY_BUFFERED) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("<cache>: Cached content not used: New cache entry, cache stale or scope flushed : " + actualKey);
|
||||
}
|
||||
}
|
||||
|
||||
return returnCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a SimpleDateFormat string to seconds
|
||||
* Acceptable format are :
|
||||
* <ul>
|
||||
* <li>0s (seconds)
|
||||
* <li>0m (minute)
|
||||
* <li>0h (hour)
|
||||
* <li>0d (day)
|
||||
* <li>0w (week)
|
||||
* </ul>
|
||||
* @param duration The simple date time to parse
|
||||
* @return The value in seconds
|
||||
*/
|
||||
private int parseDuration(String duration) {
|
||||
int time = 0;
|
||||
|
||||
//Detect if the factor is specified
|
||||
try {
|
||||
time = Integer.parseInt(duration);
|
||||
} catch (Exception ex) {
|
||||
//Extract number and ajust this number with the time factor
|
||||
for (int i = 0; i < duration.length(); i++) {
|
||||
if (!Character.isDigit(duration.charAt(i))) {
|
||||
time = Integer.parseInt(duration.substring(0, i));
|
||||
|
||||
switch ((int) duration.charAt(i)) {
|
||||
case (int) 's':
|
||||
time *= SECOND;
|
||||
break;
|
||||
case (int) 'm':
|
||||
time *= MINUTE;
|
||||
break;
|
||||
case (int) 'h':
|
||||
time *= HOUR;
|
||||
break;
|
||||
case (int) 'd':
|
||||
time *= DAY;
|
||||
break;
|
||||
case (int) 'w':
|
||||
time *= WEEK;
|
||||
break;
|
||||
default:
|
||||
//no defined use as is
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// if
|
||||
}
|
||||
|
||||
// for
|
||||
}
|
||||
|
||||
// catch
|
||||
return time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an ISO-8601 format date and return it's value in seconds
|
||||
*
|
||||
* @param duration The ISO-8601 date
|
||||
* @return The equivalent number of seconds
|
||||
* @throws Exception
|
||||
*/
|
||||
private int parseISO_8601_Duration(String duration) throws Exception {
|
||||
int years = 0;
|
||||
int months = 0;
|
||||
int days = 0;
|
||||
int hours = 0;
|
||||
int mins = 0;
|
||||
int secs = 0;
|
||||
|
||||
// If there is a negative sign, it must be first
|
||||
// If it is present, we will ignore it
|
||||
int index = duration.indexOf("-");
|
||||
|
||||
if (index > 0) {
|
||||
throw new Exception("Invalid duration (- must be at the beginning)");
|
||||
}
|
||||
|
||||
// First caracter must be P
|
||||
String workValue = duration.substring(index + 1);
|
||||
|
||||
if (workValue.charAt(0) != 'P') {
|
||||
throw new Exception("Invalid duration (P must be at the beginning)");
|
||||
}
|
||||
|
||||
// Must contain a value
|
||||
workValue = workValue.substring(1);
|
||||
|
||||
if (workValue.length() == 0) {
|
||||
throw new Exception("Invalid duration (nothing specified)");
|
||||
}
|
||||
|
||||
// Check if there is a T
|
||||
index = workValue.indexOf('T');
|
||||
|
||||
String timeString = "";
|
||||
|
||||
if (index > 0) {
|
||||
timeString = workValue.substring(index + 1);
|
||||
|
||||
// Time cannot be empty
|
||||
if (timeString.equals("")) {
|
||||
throw new Exception("Invalid duration (T with no time)");
|
||||
}
|
||||
|
||||
workValue = workValue.substring(0, index);
|
||||
} else if (index == 0) {
|
||||
timeString = workValue.substring(1);
|
||||
workValue = "";
|
||||
}
|
||||
|
||||
if (!workValue.equals("")) {
|
||||
validateDateFormat(workValue);
|
||||
|
||||
int yearIndex = workValue.indexOf('Y');
|
||||
int monthIndex = workValue.indexOf('M');
|
||||
int dayIndex = workValue.indexOf('D');
|
||||
|
||||
if ((yearIndex != -1) && (monthIndex != -1) && (yearIndex > monthIndex)) {
|
||||
throw new Exception("Invalid duration (Date part not properly specified)");
|
||||
}
|
||||
|
||||
if ((yearIndex != -1) && (dayIndex != -1) && (yearIndex > dayIndex)) {
|
||||
throw new Exception("Invalid duration (Date part not properly specified)");
|
||||
}
|
||||
|
||||
if ((dayIndex != -1) && (monthIndex != -1) && (monthIndex > dayIndex)) {
|
||||
throw new Exception("Invalid duration (Date part not properly specified)");
|
||||
}
|
||||
|
||||
if (yearIndex >= 0) {
|
||||
years = (new Integer(workValue.substring(0, yearIndex))).intValue();
|
||||
}
|
||||
|
||||
if (monthIndex >= 0) {
|
||||
months = (new Integer(workValue.substring(yearIndex + 1, monthIndex))).intValue();
|
||||
}
|
||||
|
||||
if (dayIndex >= 0) {
|
||||
if (monthIndex >= 0) {
|
||||
days = (new Integer(workValue.substring(monthIndex + 1, dayIndex))).intValue();
|
||||
} else {
|
||||
if (yearIndex >= 0) {
|
||||
days = (new Integer(workValue.substring(yearIndex + 1, dayIndex))).intValue();
|
||||
} else {
|
||||
days = (new Integer(workValue.substring(0, dayIndex))).intValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!timeString.equals("")) {
|
||||
validateHourFormat(timeString);
|
||||
|
||||
int hourIndex = timeString.indexOf('H');
|
||||
int minuteIndex = timeString.indexOf('M');
|
||||
int secondIndex = timeString.indexOf('S');
|
||||
|
||||
if ((hourIndex != -1) && (minuteIndex != -1) && (hourIndex > minuteIndex)) {
|
||||
throw new Exception("Invalid duration (Time part not properly specified)");
|
||||
}
|
||||
|
||||
if ((hourIndex != -1) && (secondIndex != -1) && (hourIndex > secondIndex)) {
|
||||
throw new Exception("Invalid duration (Time part not properly specified)");
|
||||
}
|
||||
|
||||
if ((secondIndex != -1) && (minuteIndex != -1) && (minuteIndex > secondIndex)) {
|
||||
throw new Exception("Invalid duration (Time part not properly specified)");
|
||||
}
|
||||
|
||||
if (hourIndex >= 0) {
|
||||
hours = (new Integer(timeString.substring(0, hourIndex))).intValue();
|
||||
}
|
||||
|
||||
if (minuteIndex >= 0) {
|
||||
mins = (new Integer(timeString.substring(hourIndex + 1, minuteIndex))).intValue();
|
||||
}
|
||||
|
||||
if (secondIndex >= 0) {
|
||||
if (timeString.length() != (secondIndex + 1)) {
|
||||
throw new Exception("Invalid duration (Time part not properly specified)");
|
||||
}
|
||||
|
||||
if (minuteIndex >= 0) {
|
||||
timeString = timeString.substring(minuteIndex + 1, timeString.length() - 1);
|
||||
} else {
|
||||
if (hourIndex >= 0) {
|
||||
timeString = timeString.substring(hourIndex + 1, timeString.length() - 1);
|
||||
} else {
|
||||
timeString = timeString.substring(0, timeString.length() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (timeString.indexOf('.') == (timeString.length() - 1)) {
|
||||
throw new Exception("Invalid duration (Time part not properly specified)");
|
||||
}
|
||||
|
||||
secs = (new Double(timeString)).intValue();
|
||||
}
|
||||
}
|
||||
|
||||
// Compute Value
|
||||
return secs + (mins * MINUTE) + (hours * HOUR) + (days * DAY) + (months * MONTH) + (years * YEAR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the basic date format
|
||||
*
|
||||
* @param basicDate The string to validate
|
||||
* @throws Exception
|
||||
*/
|
||||
private void validateDateFormat(String basicDate) throws Exception {
|
||||
int yearCounter = 0;
|
||||
int monthCounter = 0;
|
||||
int dayCounter = 0;
|
||||
|
||||
for (int counter = 0; counter < basicDate.length(); counter++) {
|
||||
// Check if there's any other caracters than Y, M, D and numbers
|
||||
if (!Character.isDigit(basicDate.charAt(counter)) && (basicDate.charAt(counter) != 'Y') && (basicDate.charAt(counter) != 'M') && (basicDate.charAt(counter) != 'D')) {
|
||||
throw new Exception("Invalid duration (Date part not properly specified)");
|
||||
}
|
||||
|
||||
// Check if the allowed caracters are present more than 1 time
|
||||
if (basicDate.charAt(counter) == 'Y') {
|
||||
yearCounter++;
|
||||
}
|
||||
|
||||
if (basicDate.charAt(counter) == 'M') {
|
||||
monthCounter++;
|
||||
}
|
||||
|
||||
if (basicDate.charAt(counter) == 'D') {
|
||||
dayCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
if ((yearCounter > 1) || (monthCounter > 1) || (dayCounter > 1)) {
|
||||
throw new Exception("Invalid duration (Date part not properly specified)");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the basic hour format
|
||||
*
|
||||
* @param basicHour The string to validate
|
||||
* @throws Exception
|
||||
*/
|
||||
private void validateHourFormat(String basicHour) throws Exception {
|
||||
int minuteCounter = 0;
|
||||
int secondCounter = 0;
|
||||
int hourCounter = 0;
|
||||
|
||||
for (int counter = 0; counter < basicHour.length(); counter++) {
|
||||
if (!Character.isDigit(basicHour.charAt(counter)) && (basicHour.charAt(counter) != 'H') && (basicHour.charAt(counter) != 'M') && (basicHour.charAt(counter) != 'S') && (basicHour.charAt(counter) != '.')) {
|
||||
throw new Exception("Invalid duration (Time part not properly specified)");
|
||||
}
|
||||
|
||||
if (basicHour.charAt(counter) == 'H') {
|
||||
hourCounter++;
|
||||
}
|
||||
|
||||
if (basicHour.charAt(counter) == 'M') {
|
||||
minuteCounter++;
|
||||
}
|
||||
|
||||
if (basicHour.charAt(counter) == 'S') {
|
||||
secondCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
if ((hourCounter > 1) || (minuteCounter > 1) || (secondCounter > 1)) {
|
||||
throw new Exception("Invalid duration (Time part not properly specified)");
|
||||
}
|
||||
}
|
||||
}
|
171
src/main/java/com/opensymphony/oscache/web/tag/FlushTag.java
Normal file
171
src/main/java/com/opensymphony/oscache/web/tag/FlushTag.java
Normal file
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.web.tag;
|
||||
|
||||
import com.opensymphony.oscache.base.Cache;
|
||||
import com.opensymphony.oscache.web.ServletCacheAdministrator;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.jsp.JspTagException;
|
||||
import javax.servlet.jsp.PageContext;
|
||||
import javax.servlet.jsp.tagext.TagSupport;
|
||||
|
||||
/**
|
||||
* FlushTag flushes caches created with <cache>.
|
||||
*
|
||||
* This tag provides programmatic control over when caches are flushed,
|
||||
* and can flush all caches at once.<p>
|
||||
*
|
||||
* Usage Examples:
|
||||
* <pre><code>
|
||||
* <%@ taglib uri="oscache" prefix="cache" %>
|
||||
* <cache:flush scope="application" />
|
||||
* <cache:flush scope="session" key="foobar" />
|
||||
* </code></pre>
|
||||
*
|
||||
* Note: If no scope is provided (or scope is null), it will flush
|
||||
* all caches globally - use with care!<p>
|
||||
* <p>
|
||||
* Flushing is done by setting an appropriate application level time,
|
||||
* which <cache> always looks at before retrieving the cache.
|
||||
* If this 'flush time' is > that cache's last update, it will refresh
|
||||
* the cache.
|
||||
* <p>
|
||||
* As such caches are not all 'flushed', they are all marked
|
||||
* to be refreshed at their next access. That is the only way that
|
||||
* the content can still be available if the refresh fails.
|
||||
*
|
||||
* @author <a href="mailto:mike@atlassian.com">Mike Cannon-Brookes</a>
|
||||
* @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
|
||||
* @version $Revision$
|
||||
*/
|
||||
public class FlushTag extends TagSupport {
|
||||
ServletCacheAdministrator admin = null;
|
||||
|
||||
/**
|
||||
* A cache group.
|
||||
* If specified, all content in that group will be flushed
|
||||
*/
|
||||
String group = null;
|
||||
|
||||
/**
|
||||
* Tag key.
|
||||
*/
|
||||
String key = null;
|
||||
|
||||
/**
|
||||
* if pattern value is specified, all keys that contain the pattern are flushed.
|
||||
*/
|
||||
String pattern = null;
|
||||
String scope = null;
|
||||
int cacheScope = -1;
|
||||
|
||||
/**
|
||||
* The ISO-639 language code to distinguish different pages in application scope.
|
||||
*/
|
||||
private String language = null;
|
||||
|
||||
/**
|
||||
* The group to be flushed.
|
||||
* If specified, all cached content in the group will be flushed.
|
||||
*
|
||||
* @param group The name of the group to flush.
|
||||
*/
|
||||
public void setGroup(String group) {
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
/**
|
||||
* The key to be flushed.
|
||||
* If specified, only one cache entry will be flushed.
|
||||
*
|
||||
* @param value The key of the specific entry to flush.
|
||||
*/
|
||||
public void setKey(String value) {
|
||||
this.key = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ISO-639 language code to distinguish different pages in application scope.
|
||||
*
|
||||
* @param value The language code for this cache entry.
|
||||
*/
|
||||
public void setLanguage(String value) {
|
||||
this.language = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The key pattern to be flushed.
|
||||
* If specified, all entries that contain the pattern will be flushed.
|
||||
* @param value The key of the specific entry to flush.
|
||||
*/
|
||||
public void setPattern(String value) {
|
||||
this.pattern = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the scope of this flush.
|
||||
*
|
||||
* @param value The scope - either "application" (default) or "session".
|
||||
*/
|
||||
public void setScope(String value) {
|
||||
if (value != null) {
|
||||
if (value.equalsIgnoreCase(ServletCacheAdministrator.SESSION_SCOPE_NAME)) {
|
||||
cacheScope = PageContext.SESSION_SCOPE;
|
||||
} else if (value.equalsIgnoreCase(ServletCacheAdministrator.APPLICATION_SCOPE_NAME)) {
|
||||
cacheScope = PageContext.APPLICATION_SCOPE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the start of the tag.
|
||||
*
|
||||
* @throws JspTagException The standard tag exception thrown.
|
||||
* @return The standard Tag return.
|
||||
*/
|
||||
public int doStartTag() throws JspTagException {
|
||||
if (admin == null) {
|
||||
admin = ServletCacheAdministrator.getInstance(pageContext.getServletContext());
|
||||
}
|
||||
|
||||
if (group != null) // We're flushing a group
|
||||
{
|
||||
if (cacheScope >= 0) {
|
||||
Cache cache = admin.getCache((HttpServletRequest) pageContext.getRequest(), cacheScope);
|
||||
cache.flushGroup(group);
|
||||
} else {
|
||||
throw new JspTagException("A cache group was specified for flushing, but the scope wasn't supplied or was invalid");
|
||||
}
|
||||
} else if (pattern != null) // We're flushing keys which contain the pattern
|
||||
{
|
||||
if (cacheScope >= 0) {
|
||||
Cache cache = admin.getCache((HttpServletRequest) pageContext.getRequest(), cacheScope);
|
||||
cache.flushPattern(pattern);
|
||||
} else {
|
||||
throw new JspTagException("A pattern was specified for flushing, but the scope wasn't supplied or was invalid");
|
||||
}
|
||||
} else if (key == null) // we're flushing a whole scope
|
||||
{
|
||||
if (cacheScope >= 0) {
|
||||
admin.setFlushTime(cacheScope);
|
||||
} else {
|
||||
admin.flushAll();
|
||||
}
|
||||
} else // we're flushing just one key
|
||||
{
|
||||
if (cacheScope >= 0) {
|
||||
String actualKey = admin.generateEntryKey(key, (HttpServletRequest) pageContext.getRequest(), cacheScope, language);
|
||||
|
||||
Cache cache = admin.getCache((HttpServletRequest) pageContext.getRequest(), cacheScope);
|
||||
cache.flushEntry(actualKey);
|
||||
} else {
|
||||
throw new JspTagException("A cache key was specified for flushing, but the scope wasn't supplied or was invalid");
|
||||
}
|
||||
}
|
||||
|
||||
return SKIP_BODY;
|
||||
}
|
||||
}
|
32
src/main/java/com/opensymphony/oscache/web/tag/GroupTag.java
Normal file
32
src/main/java/com/opensymphony/oscache/web/tag/GroupTag.java
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.web.tag;
|
||||
|
||||
import javax.servlet.jsp.JspTagException;
|
||||
import javax.servlet.jsp.tagext.TagSupport;
|
||||
|
||||
/**
|
||||
* GroupTag is a tag that adds a group to an ancestor CacheTag's groups.<p>
|
||||
*
|
||||
* @author <a href="mailto:robvdv@yahoo.com">Robert van der Vliet</a>
|
||||
*/
|
||||
public class GroupTag extends TagSupport {
|
||||
private Object group = null;
|
||||
|
||||
public int doStartTag() throws JspTagException {
|
||||
CacheTag ancestorCacheTag = (CacheTag) TagSupport.findAncestorWithClass(this, CacheTag.class);
|
||||
|
||||
if (ancestorCacheTag == null) {
|
||||
throw new JspTagException("GroupTag cannot be used from outside a CacheTag");
|
||||
}
|
||||
|
||||
ancestorCacheTag.addGroup(String.valueOf(group));
|
||||
return EVAL_BODY_INCLUDE;
|
||||
}
|
||||
|
||||
public void setGroup(Object group) {
|
||||
this.group = group;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.web.tag;
|
||||
|
||||
import javax.servlet.jsp.JspTagException;
|
||||
import javax.servlet.jsp.tagext.TagSupport;
|
||||
|
||||
/**
|
||||
* GroupsTag is a tag that add a comma-delimited list of groups to an ancestor CacheTag's groups.<p>
|
||||
*
|
||||
* @author <a href="mailto:ltorunski@t-online.de">Lars Torunski</a>
|
||||
*/
|
||||
public class GroupsTag extends TagSupport {
|
||||
private Object groups = null;
|
||||
|
||||
public int doStartTag() throws JspTagException {
|
||||
CacheTag ancestorCacheTag = (CacheTag) TagSupport.findAncestorWithClass(this, CacheTag.class);
|
||||
|
||||
if (ancestorCacheTag == null) {
|
||||
throw new JspTagException("GroupsTag cannot be used from outside a CacheTag");
|
||||
}
|
||||
|
||||
ancestorCacheTag.addGroups(String.valueOf(groups));
|
||||
|
||||
return EVAL_BODY_INCLUDE;
|
||||
}
|
||||
|
||||
public void setGroups(Object groups) {
|
||||
this.groups = groups;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2003 by OpenSymphony
|
||||
* All rights reserved.
|
||||
*/
|
||||
package com.opensymphony.oscache.web.tag;
|
||||
|
||||
import javax.servlet.jsp.JspTagException;
|
||||
import javax.servlet.jsp.tagext.TagSupport;
|
||||
|
||||
/**
|
||||
* UseCachedTag is a tag that tells a <cache> tag to reuse the cached body.<p>
|
||||
*
|
||||
* Usage Example:
|
||||
* <pre><code>
|
||||
* <%@ taglib uri="oscache" prefix="cache" %>
|
||||
* <cache:cache key="mycache" scope="application">
|
||||
* if (reuse cached)
|
||||
* <cache:usecached />
|
||||
* else
|
||||
* some other logic
|
||||
* </cache:cache>
|
||||
* </code></pre>
|
||||
*
|
||||
* Note this is very useful with try / catch blocks
|
||||
* so that you can still produce old cached data if an
|
||||
* exception occurs, eg your database goes down.<p>
|
||||
*
|
||||
* @author <a href="mailto:mike@atlassian.com">Mike Cannon-Brookes</a>
|
||||
* @version $Revision$
|
||||
*/
|
||||
public class UseCachedTag extends TagSupport {
|
||||
boolean use = true;
|
||||
|
||||
/**
|
||||
* Set the decision to use the body content of the ancestor <cache> or not.
|
||||
*
|
||||
* @param value Whether or not to use the body content. Default is true.
|
||||
*/
|
||||
public void setUse(boolean value) {
|
||||
this.use = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The start tag.
|
||||
*
|
||||
* @throws JspTagException The standard tag exception thrown.
|
||||
* @return The standard Tag return.
|
||||
*/
|
||||
public int doStartTag() throws JspTagException {
|
||||
CacheTag cacheTag = (CacheTag) TagSupport.findAncestorWithClass(this, CacheTag.class);
|
||||
|
||||
if (cacheTag == null) {
|
||||
throw new JspTagException("A UseCached tag must be nested within a Cache tag");
|
||||
}
|
||||
|
||||
cacheTag.setUseBody(!use);
|
||||
|
||||
return SKIP_BODY;
|
||||
}
|
||||
}
|
33
src/main/java/com/opensymphony/oscache/web/tag/package.html
Normal file
33
src/main/java/com/opensymphony/oscache/web/tag/package.html
Normal file
|
@ -0,0 +1,33 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
@(#)package.html
|
||||
|
||||
Copyright (c) 2002-2003 by OpenSymphony
|
||||
All rights reserved.
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Provides the tag libraries that allow OSCache to be accessed via JSP custom tags for
|
||||
caching portions of JSP pages.
|
||||
|
||||
|
||||
<h2>Package Specification</h2>
|
||||
|
||||
<h2>Related Documentation</h2>
|
||||
|
||||
|
||||
For overviews, tutorials, examples, guides, and tool documentation, please see:
|
||||
<ul>
|
||||
<li><a href="http://www.opensymphony.com/oscache">The OSCache Homepage</a>
|
||||
<li><a href="http://www.opensymphony.com/oscache/tags.jsp">Tag Reference</a>
|
||||
</ul>
|
||||
|
||||
<!-- Put @see and @since tags down here. -->
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Add a link
Reference in a new issue