/*
* FreeMarker: a tool that allows Java programs to generate HTML
* output using templates.
* Copyright (C) 1998-2004 Benjamin Geer
* Email: beroul@users.sourceforge.net
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
package freemarker.template.cache;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import freemarker.template.TemplateException;
/**
* Implements a load-on-demand caching strategy.
*
* @author Nicholas Cull
* @version $Id: LoadOnDemandCachingStrategy.java 995 2004-10-15 04:05:41Z
* run2000 $
*/
public final class LoadOnDemandCachingStrategy extends BaseCachingStrategy {
private HashMap cache = new HashMap();
private UpdateTimer timer;
private long delay = 5000; // five seconds
private int maximumAge = 0; // Default to no expiry
private String defaultTemplate;
/** Creates new LoadOnDemandCachingStrategy. */
public LoadOnDemandCachingStrategy() {
}
/**
* Sets the interval between two cache updates. This is meaningful only if
* the cache policy is a load-on-demand or preload type.
*
* @param delay
* the number of seconds between cache updates
*/
public synchronized void setDelay(long delay) {
this.delay = delay * 1000;
}
/**
* Returns the interval between two cache updates. This is meaningful only
* if the cache policy is a load-on-demand or preload type.
*
* @return the number of seconds between cache updates
*/
public synchronized long getDelay() {
return delay / 1000;
}
/**
* Sets the maximum age a cache item can be before it is evicted from the
* cache. The age is determined as the number of cache updates since the
* item was last accessed. This is meaningful only if the cache policy is a
* load-on-demand type.
*
* @param age
* the maximum age before an item is evicted from the cache
*/
public synchronized void setMaximumAge(int age) {
maximumAge = age;
}
/**
* Retrieves the maximum age a cache item can be before it is evicted from
* the cache. The age is determined as the number of cache updates since the
* item was last accessed. This is meaningful only if the cache policy is a
* load-on-demand type.
*
* @return the maximum age before an item is evicted from the cache
*/
public synchronized int getMaximumAge() {
return maximumAge;
}
/**
* Retrieves an item from the cache, according to the loading policy
* implemented.
*
* @param name
* the name of the item to retrieve
* @return the corresponding <code>Cacheable</code> object, or
* <code>null</code> if not found or an error has occurred
*/
public Cacheable getItem(String name) {
return getItem(name, defaultTemplate);
}
/**
* Retrieves an item from the cache, according to the loading policy
* implemented.
*
* @param name
* the name of the item to retrieve
* @param type
* the type of item to be retrieved
* @return the corresponding <code>Cacheable</code> object, or
* <code>null</code> if not found or an error has occurred
*/
public Cacheable getItem(String name, String type) {
CacheElement element;
Cacheable item;
long lastModified;
if (!connectionOk()) {
return null;
}
element = (CacheElement) cache.get(name);
try {
lastModified = retriever.lastModified(name);
if (element == null) {
HashMap newCache = (HashMap) cache.clone();
item = retriever.loadData(name, type);
if (item != null) {
item.setCache(this);
}
newCache.put(name, new CacheElement(name, type, item, lastModified));
eventHandler.fireElementUpdated(this, name, lastModified);
synchronized (this) {
cache = newCache;
}
} else if (lastModified > element.lastModified()) {
item = retriever.loadData(name, type);
if (item != null) {
item.setCache(this);
}
synchronized (this) {
cache.put(name, new CacheElement(name, type, item, lastModified));
}
eventHandler.fireElementUpdated(this, name, lastModified);
} else if ((type != null) && (!type.equals(element.getType()))) {
item = retriever.loadData(name, type);
if (item != null) {
item.setCache(this);
}
synchronized (this) {
cache.put(name, new CacheElement(name, type, item, lastModified));
}
eventHandler.fireElementUpdated(this, name, lastModified);
} else {
// Return the item we already have.
item = element.getObject();
}
} catch (TemplateException e) {
eventHandler.fireElementUpdateFailed(this, name, e);
return null;
}
return item;
}
/**
* Retrieves a list of objects currently in the cache.
*
* @return an iterator that can recurse over the cached objects. May return
* <code>null</code> if there are no items in the cache, or the
* strategy does not implement a cache.
*/
public Iterator listCachedFiles() {
return Collections.unmodifiableCollection(cache.values()).iterator();
}
/**
* Asks for a "blank" update. It is up to the implementation to determine
* what has to be updated.
*/
public void update() throws InterruptedException {
if (connectionOk()) {
removeDeletedItems();
}
}
/**
* Asks for the named object to be updated.
*
* @param name
* the name of the object to update
*/
public void update(String name) {
// Do nothing
}
/**
* Asks for the named object to be updated.
*
* @param name
* the name of the object to update
* @param type
* the type of the object to update
*/
public void update(String name, String type) {
// Do nothing
}
/**
* Stops automatically updating the cache.
*/
public synchronized void stopAutoUpdate() {
if (timer != null) {
timer.stopTiming();
timer = null;
}
}
/**
* Begins automatic updates of the cache.
*/
public synchronized void startAutoUpdate() {
stopAutoUpdate();
try {
update();
timer = new UpdateTimer(this, delay);
timer.startTiming(1);
} catch (InterruptedException e) {
// Propagate the interrupted flag to the caller
Thread.currentThread().interrupt();
}
}
/**
* Removes from the cache objects that correspond to deleted items.
*/
private void removeDeletedItems() throws InterruptedException {
HashMap newCache = (HashMap) cache.clone();
Set keySet = newCache.keySet();
Iterator keyIterator = keySet.iterator();
// Iterate through existing items, and check whether they're
// still in the main data store
while (keyIterator.hasNext()) {
String name = (String) keyIterator.next();
CacheElement element = (CacheElement) newCache.get(name);
element.age();
if ((!retriever.exists(name)) || (element.isExpired(maximumAge))) {
keyIterator.remove();
eventHandler.fireElementRemoved(this, name);
}
// Check whether the thread has been interrupted in the mean time.
// If so, exit the loop immediately.
if (Thread.interrupted()) {
throw new InterruptedException();
}
}
// Finally, update the cache with the new contents
synchronized (this) {
cache = newCache;
}
}
/**
* Clears all the elements in the cache.
*/
public void clearCache() {
cache = new HashMap();
}
/**
* Sets the default template to use when retrieving.
*/
public void setDefaultTemplate(String aTemplate) {
defaultTemplate = aTemplate;
}
/**
* Returns a string representation of the object.
*
* @return a <code>String</code> representation of the object
*/
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("LoadOnDemandCachingStrategy, ");
buffer.append(cache.size());
buffer.append(" cached items. ");
if (retriever != null) {
buffer.append(retriever.toString());
}
return buffer.toString();
}
}