/*
* 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;
import java.io.File;
import java.util.Iterator;
import freemarker.template.cache.CacheEventAdapter;
import freemarker.template.cache.CacheRetriever;
import freemarker.template.cache.Cacheable;
import freemarker.template.cache.CachingStrategy;
import freemarker.template.cache.FileRetriever;
import freemarker.template.cache.LoDWithRefreshCachingStrategy;
import freemarker.template.cache.LoadAdHocCachingStrategy;
import freemarker.template.cache.LoadOnDemandCachingStrategy;
import freemarker.template.cache.NullCachingStrategy;
import freemarker.template.cache.PreloadCachingStrategy;
import freemarker.template.cache.RegistryAccepter;
import freemarker.template.cache.TemplateRegistry;
import freemarker.template.cache.Updateable;
/**
* <p>
* A <code>TemplateCache</code> that loads templates from a filesystem. Given a
* directory path, the cache assumes by default that all files in the directory
* are templates. It can optionally be given a filename suffix for templates.
* </p>
*
* <p>
* The default loading policy is {@link #LOAD_ON_DEMAND}: templates are loaded
* into the cache only when requested, each template's modification date is
* checked each time it is requested, and the periodic updates are used only to
* remove deleted templates from the cache. If the loading policy is set to
* {@link #PRELOAD}, all templates are loaded when the loading policy is set,
* and all files are checked during each periodic update. If template files will
* not be changed frequently, use {@link #PRELOAD} with a long delay value for
* maximum performance.
* </p>
*
* <p>
* A combination of the two above is {@link #LOAD_ON_DEMAND_WITH_REFRESH_CACHE}.
* This loads a template on demand the first time it is requested. Subsequently,
* it will do periodic refreshes on any templates that have been requested from
* its cache. This saves memory if some templates are unlikely to be needed,
* since they won't occupy memory until they are requested, while improving
* performance over {@link #LOAD_ON_DEMAND} since it doesn't have to check the
* file system every time a template is requested.
* </p>
*
* <p>
* For maximum flexibility {@link #LOAD_AD_HOC} mode exists so that all
* templates are loaded when the loading policy is set but files are not
* refreshed periodically. Instead, one can write a client that will ask the
* <code>FileTemplateCache</code> to update a single template via the
* {@link freemarker.template.cache.Updateable#update(String)} method.
* Applications with a large number of templates many of which are not
* frequently updated will work well with {@link #LOAD_AD_HOC} mode.
* </p>
*
* <p>
* The first argument to the <code>getItem()</code> method is interpreted as the
* template's path relative to the cache's root directory, using a forward slash
* (<code>/</code>) as a separator (this is to facilitate using URL path info to
* request templates). For example, if a <code>TemplateCache</code> object was
* made for the directory <code>templates</code>, which contains a subdirectory
* <code>foo</code>, in which there is a template file called
* <code>index.html</code>, you would call
* <code>getItem("foo/index.html")</code> to retrieve that template.
* </p>
*
* <p>
* If a second argument is specified in a call to <code>getItem()</code>, this
* will determine the type of object to be retrieved. The types that can be
* returned depend on the <code>TemplateRegistry</code>. Three types of object
* are registered with the <code>TemplateRegistry</code> by default:
* </p>
* <ul>
* <li><code>template</code> -- returns a {@link Template} object. This is the
* default type to be retrieved by the cache.</li>
* <li><code>unparsed</code> -- returns an {@link UnparsedTemplate} object. This
* behaves like other <code>Template</code> objects, but its contents wont be
* parsed by FM-Classic.</li>
* <li><code>binary</code> -- returns a {@link BinaryData} object. This is
* useful for caching other file types, such as images.</li>
* </ul>
*
* <p>
* The owner of the cache should implement the <code>CacheListener</code>
* interface and register itself using <code>addCacheListener()</code>.
* </p>
*
* <p>
* If the template cannot read its cache directory, the periodic updates will be
* cancelled until the next time a loading policy is set.
* </p>
*
* @see TemplateCache
* @see CacheEvent
* @see CacheListener
* @see Updateable#update(String)
* @version $Id: FileTemplateCache.java 987 2004-10-05 10:13:24Z run2000 $
*/
public class FileTemplateCache implements TemplateCache, TextEncoding, RegistryAccepter {
private CachingStrategy cStrategy;
private CacheRetriever cRetriever;
private TemplateRegistry cTemplates;
private int loadingPolicy;
private CacheEventAdapter cEventHandler;
private String aDefaultTemplateType;
private String aTextEncoding;
private long delay;
private int maximumAge;
/**
* Used with {@link #setLoadingPolicy} to indicate that templates should be
* loaded as they are requested.
*/
public static final int LOAD_ON_DEMAND = 0;
/**
* Used with {@link #setLoadingPolicy} to indicate that templates should be
* preloaded.
*/
public static final int PRELOAD = 1;
/**
* Used with {@link #setLoadingPolicy} to indicate that templates are
* preloaded but there is no automatic updating of them. Instead, only named
* templates are updated when the cache is requested to do so.
*/
public static final int LOAD_AD_HOC = 2;
/**
* Used with {@link #setLoadingPolicy} to indicate that no files are cached.
*/
public static final int NULL_CACHE = 3;
/**
* Used with {@link #setLoadingPolicy} to indicate that templates should be
* loaded as they are requested. Once loaded, they are periodically
* refreshed as per the {@link #PRELOAD} policy, rather than checked at each
* request.
*/
public static final int LOAD_ON_DEMAND_WITH_REFRESH_CACHE = 4;
/**
* Constructs an empty <code>FileTemplateCache</code>.
*/
public FileTemplateCache() {
cStrategy = new LoadOnDemandCachingStrategy();
cRetriever = new FileRetriever();
cEventHandler = new CacheEventAdapter();
cTemplates = new TemplateRegistry();
aDefaultTemplateType = "template";
cStrategy.setCacheRetriever(cRetriever);
cStrategy.setEventHandler(cEventHandler);
cStrategy.setDefaultTemplate(aDefaultTemplateType);
((RegistryAccepter) cRetriever).setTemplateRegistry(cTemplates);
loadingPolicy = LOAD_ON_DEMAND;
}
/**
* Constructs a <code>FileTemplateCache</code> with a directory in which it
* will look for template files.
*
* @param path
* the absolute path of the directory containing templates for
* this cache.
* @throws IllegalArgumentException
* the root directory is null
*/
public FileTemplateCache(String path) {
cStrategy = new LoadOnDemandCachingStrategy();
cRetriever = new FileRetriever(path);
cEventHandler = new CacheEventAdapter();
cTemplates = new TemplateRegistry();
aDefaultTemplateType = "template";
delay = 5;
maximumAge = 0;
cStrategy.setCacheRetriever(cRetriever);
cStrategy.setEventHandler(cEventHandler);
cStrategy.setDefaultTemplate(aDefaultTemplateType);
((RegistryAccepter) cRetriever).setTemplateRegistry(cTemplates);
loadingPolicy = LOAD_ON_DEMAND;
}
/**
* Constructs a <code>FileTemplateCache</code> with a directory in which it
* will look for template files.
*
* @param dir
* the directory containing templates for this cache.
* @throws IllegalArgumentException
* the root directory is null
*/
public FileTemplateCache(File dir) {
cStrategy = new LoadOnDemandCachingStrategy();
cRetriever = new FileRetriever(dir);
cEventHandler = new CacheEventAdapter();
cTemplates = new TemplateRegistry();
aDefaultTemplateType = "template";
delay = 5;
maximumAge = 0;
cStrategy.setCacheRetriever(cRetriever);
cStrategy.setEventHandler(cEventHandler);
cStrategy.setDefaultTemplate(aDefaultTemplateType);
((RegistryAccepter) cRetriever).setTemplateRegistry(cTemplates);
loadingPolicy = LOAD_ON_DEMAND;
}
/**
* Constructs a <code>FileTemplateCache</code> with a directory in which it
* will look for template files, and a delay representing the number of
* seconds between cache updates.
*
* @param path
* the absolute path of the directory containing templates for
* this cache.
* @param delay
* the number of seconds between cache updates.
*/
public FileTemplateCache(String path, long delay) {
this(path);
setDelay(delay);
}
/**
* Constructs a <code>FileTemplateCache</code> with a directory in which it
* will look for template files, and a delay representing the number of
* seconds between cache updates.
*
* @param dir
* the directory containing templates for this cache.
* @param delay
* the number of seconds between cache updates.
*/
public FileTemplateCache(File dir, long delay) {
this(dir);
setDelay(delay);
}
/**
* Returns the loading policy currently in effect
*
* @return a loading policy value
*/
public synchronized int getLoadingPolicy() {
return loadingPolicy;
}
/**
* <p>
* Sets the loading policy for this <code>FileTemplateCache</code>. If
* {@link #LOAD_ON_DEMAND}, templates will be loaded as they are requested,
* and each template's file modification date will be checked each time it
* is requested. If {@link #PRELOAD}, all templates in the cache directory
* and its subdirectories will be loaded when the cache is started, and new
* templates will be added to the cache each time it is updated. If
* {@link #LOAD_AD_HOC}, all templates in the cache directory and its
* subdirectories will be loaded when the cache is created and a particular
* template file's modification date will be checked each time the client
* requests the update of that and only that template.
* </p>
* <p>
* Defaults to {@link #LOAD_ON_DEMAND}.
* </p>
*
* @param loadingPolicy
* cache mode
* @throws IllegalArgumentException
* the caching policy is invalid
*/
public void setLoadingPolicy(int loadingPolicy) {
CachingStrategy cNewStrategy;
switch (loadingPolicy) {
case LOAD_AD_HOC:
cNewStrategy = new LoadAdHocCachingStrategy();
break;
case PRELOAD:
cNewStrategy = new PreloadCachingStrategy();
break;
case LOAD_ON_DEMAND:
cNewStrategy = new LoadOnDemandCachingStrategy();
break;
case NULL_CACHE:
cNewStrategy = new NullCachingStrategy();
break;
case LOAD_ON_DEMAND_WITH_REFRESH_CACHE:
cNewStrategy = new LoDWithRefreshCachingStrategy();
break;
default:
throw new IllegalArgumentException("Cannot determine caching policy to be used");
}
cNewStrategy.setCacheRetriever(cRetriever);
cNewStrategy.setEventHandler(cEventHandler);
cNewStrategy.setDefaultTemplate(aDefaultTemplateType);
cNewStrategy.setDelay(delay);
cNewStrategy.setMaximumAge(maximumAge);
cStrategy.stopAutoUpdate();
cNewStrategy.startAutoUpdate();
// Synchronized so that getLoadingPolicy won't lie to us
synchronized (this) {
this.loadingPolicy = loadingPolicy;
cStrategy = cNewStrategy;
}
}
/**
* Sets the template cache root directory.
*
* @param path
* the absolute path of the directory containing templates for
* this cache.
* @throws IllegalArgumentException
* the root directory is null
*/
public void setPath(String path) {
if (path == null) {
throw new IllegalArgumentException("Root cache path cannot be null");
}
cStrategy.clearCache();
cRetriever.setConnection(path);
}
/**
* Returns the template cache root directory.
*
* @return the absolute path of the directory containing templates for this
* cache.
*/
public String getPath() {
return cRetriever.getConnection();
}
/**
* Sets the template cache root directory.
*
* @param dir
* the root directory containing templates for this cache
* @throws IllegalArgumentException
* the root directory is null
*/
public void setDirectory(File dir) {
if (dir == null) {
throw new IllegalArgumentException("Root cache directory cannot be null");
}
setPath(dir.toString());
}
/**
* Returns the template cache root directory.
*
* @return the root directory containing templates for this cache
*/
public File getDirectory() {
return new File(cRetriever.getConnection());
}
/**
* <p>
* Sets the interval between two cache updates. This is meaningful only if
* the cache policy is set to {@link #LOAD_ON_DEMAND},
* {@link #LOAD_ON_DEMAND_WITH_REFRESH_CACHE} or {@link #PRELOAD}.
* </p>
*
* <p>
* Defaults to five seconds.
* </p>
*
* @param delay
* the number of seconds between cache updates
*/
public void setDelay(long delay) {
this.delay = delay;
cStrategy.setDelay(delay);
}
/**
* Returns the interval between two cache updates. This is meaningful only
* if the cache policy is set to {@link #LOAD_ON_DEMAND},
* {@link #LOAD_ON_DEMAND_WITH_REFRESH_CACHE} or {@link #PRELOAD}.
*
* @return the number of seconds between cache updates
*/
public long getDelay() {
return cStrategy.getDelay();
}
/**
* <p>
* 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
* set to {@link #LOAD_ON_DEMAND} or
* {@link #LOAD_ON_DEMAND_WITH_REFRESH_CACHE}.
*
* <p>
* Defaults to never expiring.
* </p>
*
* @param age
* the maximum age before an item is evicted from the cache, or 0
* to indicate that items should never be evicted
*/
public void setMaximumAge(int age) {
this.maximumAge = age;
cStrategy.setMaximumAge(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
* set to {@link #LOAD_ON_DEMAND} or
* {@link #LOAD_ON_DEMAND_WITH_REFRESH_CACHE}.
*
* @return the maximum age before an item is evicted from the cache, or 0 to
* indicate that items are never evicted
*/
public int getMaximumAge() {
return cStrategy.getMaximumAge();
}
/**
* Sets the character encoding to be used when reading template files. If
* <code>null</code> is specified, the default encoding will be used.
*
* @param encoding
* the name of the encoding to be used; this will be passed to
* the constructor of <code>InputStreamReader</code>.
*/
public void setEncoding(String encoding) {
aTextEncoding = encoding;
((TextEncoding) cRetriever).setEncoding(encoding);
}
/**
* Returns the character encoding to be used when reading template files. If
* <code>null</code> is returned, the default encoding is used.
*
* @return the name of the encoding to be used; this will be passed to the
* constructor of <code>InputStreamReader</code>.
*/
public String getEncoding() {
return ((TextEncoding) cRetriever).getEncoding();
}
/**
* Sets the template suffix. If set, files that do not have this suffix will
* be ignored when read into the cache.
*
* @param filenameSuffix
* the optional filename suffix of template files to be read for
* this cache.
*/
public void setFilenameSuffix(String filenameSuffix) {
((FileRetriever) cRetriever).setFilenameSuffix(filenameSuffix);
}
/**
* Returns the template suffix. If set, files that do not have this suffix
* will be ignored when read into the cache.
*
* @return the optional filename suffix of template files to be read for
* this cache.
*/
public String getFilenameSuffix() {
return ((FileRetriever) cRetriever).getFilenameSuffix();
}
/**
* Registers a {@link CacheListener} for this <code>Cache</code>.
*
* @param listener
* the <code>CacheListener</code> to be registered.
* @see CacheListener
*/
public void addCacheListener(CacheListener listener) {
cEventHandler.addCacheListener(listener);
}
/**
* Unregisters a {@link CacheListener} for a <code>Cache</code>.
*
* @param listener
* the <code>CacheListener</code> to be unregistered.
* @see CacheListener
*/
public void removeCacheListener(CacheListener listener) {
cEventHandler.removeCacheListener(listener);
}
/**
* Retrieves all the {@link CacheListener}s associated with this cache.
*
* @return an array of <code>CacheListener</code>s
*/
public CacheListener[] getCacheListeners() {
return cEventHandler.getCacheListeners();
}
/**
* Stops the updating of the cache. Normally do this immediately prior to
* cache destruction.
*/
public void stopAutoUpdate() {
cStrategy.stopAutoUpdate();
}
/**
* Returns a list of cached files.
*
* @return a list of cached files
*/
public Iterator listCachedFiles() {
return cStrategy.listCachedFiles();
}
/**
* Update a named template if in the {@link #LOAD_AD_HOC} mode . Do nothing
* if in other modes.
*
* @param name
* of template to update
*/
public void update(String name) {
try {
cStrategy.update(name);
} catch (InterruptedException e) {
// Propagate the interrupted status up to the caller
Thread.currentThread().interrupt();
}
}
/**
* Update a named template if in the {@link #LOAD_AD_HOC} mode. Do nothing
* if in other modes.
*
* @param name
* the name of template to update
* @param type
* the type of template to update
*/
public void update(String name, String type) {
try {
cStrategy.update(name, type);
} catch (InterruptedException e) {
// Propagate the interrupted status up to the caller
Thread.currentThread().interrupt();
}
}
/**
* Updates the cache. In {@link #LOAD_AD_HOC} mode, this does nothing.
*/
public void update() {
try {
cStrategy.update();
} catch (InterruptedException e) {
// Propagate the interrupted status up to the caller
Thread.currentThread().interrupt();
}
}
/**
* Gets the specified template from the cache, using the default template
* type.
*
* @param name
* a string uniquely identifying the template.
* @return the template corresponding to the name, or <code>null</code> if
* not found.
* @see #setDefaultTemplate
*/
public Cacheable getItem(String name) {
return cStrategy.getItem(name);
}
/**
* Gets the specified template type from the cache.
*
* @param name
* a string uniquely identifying the template.
* @param type
* the type of template to be retrieved
* @return the template corresponding to the name, or <code>null</code> if
* not found.
* @see #setDefaultTemplate
*/
public Cacheable getItem(String name, String type) {
return cStrategy.getItem(name, type);
}
/**
* Gets the {@link freemarker.template.cache.CacheRetriever} currently in
* use.
*
* @return the <code>CacheRetriever</code> used for retrieving templates
*/
public synchronized CacheRetriever getRetriever() {
return cRetriever;
}
/**
* Sets the {@link freemarker.template.cache.CacheRetriever} to be used for
* the cache.
*
* @param cRetriever
* the <code>CacheRetriever</code> to be used for retrieving
* templates
*/
public void setRetriever(CacheRetriever cRetriever) {
if (this.cRetriever != cRetriever) {
if (cRetriever instanceof RegistryAccepter) {
((RegistryAccepter) cRetriever).setTemplateRegistry(cTemplates);
}
((TextEncoding) cRetriever).setEncoding(aTextEncoding);
synchronized (this) {
this.cRetriever = cRetriever;
cStrategy.setCacheRetriever(cRetriever);
}
}
}
/**
* Retrieves the current {@link freemarker.template.cache.TemplateRegistry}
* in use.
*
* @return the <code>TemplateRegistry</code> to used when retrieving items
* to be cached
*/
public synchronized TemplateRegistry getTemplateRegistry() {
return cTemplates;
}
/**
* Sets a {@link freemarker.template.cache.TemplateRegistry} implementation
* to use when creating new templates.
*
* @param cRegistry
* the <code>TemplateRegistry</code> to be used when retrieving
* items to be cached
*/
public synchronized void setTemplateRegistry(TemplateRegistry cRegistry) {
cTemplates = cRegistry;
if (cRetriever instanceof RegistryAccepter) {
((RegistryAccepter) cRetriever).setTemplateRegistry(cTemplates);
}
}
/**
* Retrieves the default template type to be created when retrieving items
* from the cache.
*
* @return the type of template cached by default
*/
public String getDefaultTemplate() {
return aDefaultTemplateType;
}
/**
* Sets the default template type to be created when retrieving items from
* the cache. The types that can be set depend on the
* {@link freemarker.template.cache.TemplateRegistry}. Three types of object
* are registered with the <code>TemplateRegistry</code> by default:
* <ul>
* <li><code>template</code> -- returns a {@link Template} object.</li>
* <li><code>unparsed</code> -- returns an {@link UnparsedTemplate} object.
* This behaves like other <code>Template</code> objects, but its contents
* wont be parsed by FM-Classic.</li>
* <li><code>binary</code> -- returns a {@link BinaryData} object. This is
* useful for caching other file types, such as images.</li>
* </ul>
*
* @param aTemplateType
* the type of template to be cached by default
*/
public void setDefaultTemplate(String aTemplateType) {
aDefaultTemplateType = aTemplateType;
cStrategy.setDefaultTemplate(aTemplateType);
}
/**
* Returns a string representation of the object.
*
* @return a string representation of the object.
*/
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("FileTemplateCache, ");
buffer.append(cStrategy);
return buffer.toString();
}
}