/*
* 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.List;
import java.util.Map;
import java.util.Set;
import freemarker.template.TemplateException;
/**
* Implements a preload caching strategy.
*
* @author Nicholas Cull
* @version $Id: PreloadCachingStrategy.java 1051 2004-10-24 09:14:44Z run2000 $
*/
public final class PreloadCachingStrategy extends BaseCachingStrategy {
private Map cache = new HashMap();
private UpdateTimer timer;
private long delay = 5000; // five seconds
private String defaultTemplate;
/** Creates new PreloadCachingStrategy. */
public PreloadCachingStrategy() {
}
/**
* 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 void setMaximumAge(int age) {
// Do nothing -- preload doesn't perform cache expiry
}
/**
* 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 int getMaximumAge() {
return 0;
}
/**
* Retrieves an item from the cache, according to the loading policy
* implemented. We're preloading, so we can just return the template from
* the cache if we have it -- it will be updated the next time our
* <code>update()</code> method is called.
*
* @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. We're preloading, so we can just return the template from
* the cache if we have it -- it will be updated the next time our
* <code>update()</code> method is called.
*
* @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, String type) {
CacheElement element = (CacheElement) cache.get(name);
if (element == null) {
return null;
}
// If we have an element, but of the wrong type, reget the element.
if ((type != null) && (!type.equals(element.getType()))) {
Cacheable item;
long lastModified;
try {
// Create a new element of the correct type
lastModified = retriever.lastModified(name);
item = retriever.loadData(name, type);
if (item != null) {
item.setCache(this);
}
element = new CacheElement(name, type, item, lastModified);
// Replace the existing element -- should be atomic operation
synchronized (this) {
cache.put(name, element);
}
eventHandler.fireElementUpdated(this, name, lastModified);
} catch (TemplateException e) {
eventHandler.fireElementUpdateFailed(this, name, e);
return null;
}
}
return element.getObject();
}
/**
* Returns an iterator over a list of CacheElement instances.
*
* @return the iterator over a list of CacheElement instances that
* correspond to templates in the cache
*/
public Iterator listCachedFiles() {
return Collections.unmodifiableCollection(cache.values()).iterator();
}
/**
* 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 status up to the caller
Thread.currentThread().interrupt();
}
}
/**
* Stops automatically updating the cache.
*/
public synchronized void stopAutoUpdate() {
if (timer != null) {
timer.stopTiming();
timer = null;
}
}
/**
* 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()) {
loadItems();
}
}
/**
* 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
}
/**
* Load all items in the data store and remove the ones in the cache that we
* don't know about.
*/
private void loadItems() throws InterruptedException {
try {
List visitedFiles = retriever.getPreloadData();
if (Thread.interrupted()) {
throw new InterruptedException();
}
loadItems(visitedFiles);
} catch (TemplateException e) {
stopAutoUpdate();
eventHandler.fireCacheUnavailable(this, e);
return;
}
}
/**
* Load all items in a given list into the cache. If an item already exists,
* it is checked for currency, and updated if necessary.
*
* @param items
* a list of strings representing an item to load in the cache.
*/
private void loadItems(List items) throws InterruptedException {
Map newCache = new HashMap((int) (items.size() * 1.4) + 1, (float) 0.75);
Iterator iName = items.iterator();
String name;
Cacheable item;
long lastModified;
CacheElement element;
while (iName.hasNext()) {
name = (String) iName.next();
element = (CacheElement) cache.get(name);
try {
lastModified = retriever.lastModified(name);
} catch (TemplateException e) {
eventHandler.fireElementUpdateFailed(this, name, e);
continue;
}
if (element == null) {
try {
item = retriever.loadData(name, defaultTemplate);
} catch (TemplateException e) {
eventHandler.fireElementUpdateFailed(this, name, e);
item = null;
}
if (item != null) {
item.setCache(this);
newCache.put(name, new CacheElement(name, defaultTemplate, item, lastModified));
eventHandler.fireElementUpdated(this, name, lastModified);
}
} else if (lastModified > element.lastModified()) {
String type = element.getType();
try {
item = retriever.loadData(name, type);
} catch (TemplateException e) {
eventHandler.fireElementUpdateFailed(this, name, e);
item = null;
}
if (item != null) {
item.setCache(this);
newCache.put(name, new CacheElement(name, type, item, lastModified));
eventHandler.fireElementUpdated(this, name, lastModified);
}
} else {
newCache.put(name, element);
}
// Check whether the thread has been interrupted in the mean time.
// If so, exit the loop immediately.
if (Thread.interrupted()) {
throw new InterruptedException();
}
}
removeUnvisitedItems(newCache);
synchronized (this) {
cache = newCache;
}
}
/**
* Fire the eventRemoved event for any items corresponding to those we
* didn't just visit.
*
* @param newFiles
* list of visited items
*/
private void removeUnvisitedItems(final Map newFiles) {
Set keySet = cache.keySet();
Iterator keyIterator = keySet.iterator();
while (keyIterator.hasNext()) {
String elementName = (String) keyIterator.next();
if (!newFiles.containsKey(elementName)) {
eventHandler.fireElementRemoved(this, elementName);
}
}
}
/**
* Clears all the elements in the cache.
*/
public void clearCache() {
cache = new HashMap();
}
/**
* Sets the default template to use when retrieving.
*
* @param template
* the type of template to be used by default when retrieving
* objects from the repository
*/
public void setDefaultTemplate(String template) {
defaultTemplate = template;
}
/**
* 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("PreloadCachingStrategy, ");
buffer.append(cache.size());
buffer.append(" cached items. ");
if (retriever != null) {
buffer.append(retriever.toString());
}
return buffer.toString();
}
}