/* * 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 freemarker.template.TemplateException; /** * Implements a load-ad-hoc caching strategy. * * @author Nicholas Cull * @version $Id: LoadAdHocCachingStrategy.java 987 2004-10-05 10:13:24Z run2000 * $ */ public final class LoadAdHocCachingStrategy extends BaseCachingStrategy { private HashMap cache = new HashMap(); private String defaultTemplate; /** Creates new LoadAdHocCachingStrategy. */ public LoadAdHocCachingStrategy() { } /** * 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 void setDelay(long delay) { // Do nothing -- ad-hoc doesn't do auto-updates } /** * 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 long getDelay() { return 0; } /** * 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 -- ad-hoc doesn't auto-expire cache items } /** * 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. * * @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); } /** * Gets an item of the specified type from the cache. * * @param name * a string uniquely identifying the item. * @param type * the type of item to be retrieved * @return the item corresponding to the name, or <code>null</code> if not * found. */ 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()))) { update(name, type); element = (CacheElement) cache.get(name); if ((element == null) || (!type.equals(element.getType()))) { eventHandler.fireElementUpdateFailed(this, name, new TemplateException("Couldn't update element to new type")); return null; } } return element.getObject(); } /** * Returns an iterator over a list of {@link CacheElement} instances. * * @return the iterator over a list of <code>CacheElement</code> instances * that correspond to templates in the cache */ public Iterator listCachedFiles() { return Collections.unmodifiableCollection(cache.values()).iterator(); } /** * Begins automatic updates of the cache. */ public void startAutoUpdate() { try { loadItems(); } catch (InterruptedException e) { // Propagate the interrupted status back up to the caller Thread.currentThread().interrupt(); } } /** * Stops automatically updating the cache. */ public void stopAutoUpdate() { // Do nothing } /** * Asks for a "blank" update. It is up to the implementation to determine * what has to be updated. */ public void update() { // Do nothing for ad-hoc loading } /** * Asks for the named object to be updated. * * @param name * the name of the object to update */ public void update(String name) { update(name, defaultTemplate); } /** * 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) { Cacheable item; long lastModified; CacheElement element; if (!connectionOk()) { return; } element = (CacheElement) cache.get(name); try { lastModified = retriever.lastModified(name); } catch (TemplateException e) { eventHandler.fireElementUpdateFailed(this, name, e); return; } if (element == null) { // To avoid synchronisation issues, clone the existing map, // add the new element, then replace with the new cache HashMap newCache = (HashMap) cache.clone(); try { item = retriever.loadData(name, type); } catch (TemplateException e) { eventHandler.fireElementUpdateFailed(this, name, e); return; } if (item != null) { item.setCache(this); newCache.put(name, new CacheElement(name, type, item, lastModified)); synchronized (this) { cache = newCache; } eventHandler.fireElementUpdated(this, name, lastModified); } } else if (lastModified > element.lastModified()) { // Create a new element with the updated contents CacheElement newElement; try { item = retriever.loadData(name, type); } catch (TemplateException e) { eventHandler.fireElementUpdateFailed(this, name, e); return; } if (item != null) { item.setCache(this); newElement = new CacheElement(name, type, item, lastModified); // Replace the existing element -- should be atomic operation synchronized (this) { cache.put(name, newElement); } eventHandler.fireElementUpdated(this, name, lastModified); } // If we have an element, but of the wrong type, reget the element. } else if ((type != null) && (!type.equals(element.getType()))) { // Create a new element of the correct type try { item = retriever.loadData(name, type); } catch (TemplateException e) { eventHandler.fireElementUpdateFailed(this, name, e); return; } 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); } } } /** * 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; } /** * Load all items in the data store. */ private void loadItems() throws InterruptedException { try { List visitedFiles = retriever.getPreloadData(); if (Thread.currentThread().interrupted()) { // Abort the update throw new InterruptedException(); } loadItems(visitedFiles); } catch (TemplateException e) { eventHandler.fireCacheUnavailable(this, e); return; } } /** * Load all items in a given list into the cache. * * @param items * a list of strings representing an item to load in the cache. */ private void loadItems(List items) throws InterruptedException { HashMap newCache = new HashMap((int) (items.size() * 1.4), (float) 0.75); Iterator iName = items.iterator(); String name; Cacheable item = null; long lastModified; Thread currentThread = Thread.currentThread(); while (iName.hasNext()) { name = (String) iName.next(); try { lastModified = retriever.lastModified(name); } catch (TemplateException e) { eventHandler.fireElementUpdateFailed(this, name, e); continue; } try { item = retriever.loadData(name, defaultTemplate); } catch (TemplateException e) { eventHandler.fireElementUpdateFailed(this, name, e); } if (item != null) { item.setCache(this); } newCache.put(name, new CacheElement(name, defaultTemplate, item, lastModified)); eventHandler.fireElementUpdated(this, name, lastModified); // Check whether the thread has been interrupted in the mean time. // If so, exit the loop immediately. if (currentThread.interrupted()) { throw new InterruptedException(); } } synchronized (this) { cache = newCache; } } /** * 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("LoadAdHocCachingStrategy, "); buffer.append(cache.size()); buffer.append(" cached items. "); if (retriever != null) { buffer.append(retriever.toString()); } return buffer.toString(); } }