Clover coverage report -
Coverage timestamp: Sun Mar 5 2006 06:05:21 CST
file stats: LOC: 750   Methods: 31
NCLOC: 314   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
ServletCacheAdministrator.java 0% 0% 0% 0%
coverage
 1    /*
 2    * Copyright (c) 2002-2003 by OpenSymphony
 3    * All rights reserved.
 4    */
 5    package com.opensymphony.oscache.web;
 6   
 7    import com.opensymphony.oscache.base.*;
 8    import com.opensymphony.oscache.base.events.CacheEventListener;
 9    import com.opensymphony.oscache.base.events.ScopeEvent;
 10    import com.opensymphony.oscache.base.events.ScopeEventListener;
 11    import com.opensymphony.oscache.base.events.ScopeEventType;
 12   
 13    import org.apache.commons.logging.Log;
 14    import org.apache.commons.logging.LogFactory;
 15   
 16    import java.io.Serializable;
 17   
 18    import java.util.*;
 19   
 20    import javax.servlet.ServletContext;
 21    import javax.servlet.http.HttpServletRequest;
 22    import javax.servlet.http.HttpSession;
 23    import javax.servlet.jsp.PageContext;
 24   
 25    /**
 26    * A ServletCacheAdministrator creates, flushes and administers the cache.
 27    * <p>
 28    * This is a "servlet Singleton". This means it's not a Singleton in the traditional sense,
 29    * that is stored in a static instance. It's a Singleton _per web app context_.
 30    * <p>
 31    * Once created it manages the cache path on disk through the oscache.properties
 32    * file, and also keeps track of the flush times.
 33    *
 34    * @author <a href="mailto:mike@atlassian.com">Mike Cannon-Brookes</a>
 35    * @author <a href="mailto:tgochenour@peregrine.com">Todd Gochenour</a>
 36    * @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
 37    * @author <a href="mailto:abergevin@pyxis-tech.com">Alain Bergevin</a>
 38    * @author <a href="&#109;a&#105;&#108;&#116;&#111;:chris&#64;swebtec.&#99;&#111;&#109;">Chris Miller</a>
 39    * @version $Revision: 1.2 $
 40    */
 41    public class ServletCacheAdministrator extends AbstractCacheAdministrator implements Serializable {
 42    private static final transient Log log = LogFactory.getLog(ServletCacheAdministrator.class);
 43   
 44    /**
 45    * Constants for properties read/written from/to file
 46    */
 47    private final static String CACHE_USE_HOST_DOMAIN_KEY = "cache.use.host.domain.in.key";
 48    private final static String CACHE_KEY_KEY = "cache.key";
 49   
 50    /**
 51    * The default cache key that is used to store the cache in context.
 52    */
 53    private final static String DEFAULT_CACHE_KEY = "__oscache_cache";
 54   
 55    /**
 56    * Constants for scope's name
 57    */
 58    public final static String SESSION_SCOPE_NAME = "session";
 59    public final static String APPLICATION_SCOPE_NAME = "application";
 60   
 61    /**
 62    * The key under which the CacheAdministrator will be stored in the ServletContext
 63    */
 64    private final static String CACHE_ADMINISTRATOR_KEY = "__oscache_admin";
 65   
 66    /**
 67    * Key used to store the current scope in the configuration. This is a hack
 68    * to let the scope information get passed through to the DiskPersistenceListener,
 69    * and will be removed in a future release.
 70    */
 71    public final static String HASH_KEY_SCOPE = "scope";
 72   
 73    /**
 74    * Key used to store the current session ID in the configuration. This is a hack
 75    * to let the scope information get passed through to the DiskPersistenceListener,
 76    * and will be removed in a future release.
 77    */
 78    public final static String HASH_KEY_SESSION_ID = "sessionId";
 79   
 80    /**
 81    * Key used to store the servlet container temporary directory in the configuration.
 82    * This is a hack to let the scope information get passed through to the
 83    * DiskPersistenceListener, and will be removed in a future release.
 84    */
 85    public final static String HASH_KEY_CONTEXT_TMPDIR = "context.tempdir";
 86   
 87    /**
 88    * The string to use as a file separator.
 89    */
 90    private final static String FILE_SEPARATOR = "/";
 91   
 92    /**
 93    * The character to use as a file separator.
 94    */
 95    private final static char FILE_SEPARATOR_CHAR = FILE_SEPARATOR.charAt(0);
 96   
 97    /**
 98    * Constant for Key generation.
 99    */
 100    private final static short AVERAGE_KEY_LENGTH = 30;
 101   
 102    /**
 103    * Usable caracters for key generation
 104    */
 105    private static final String m_strBase64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
 106   
 107    /**
 108    * Map containing the flush times of different scopes
 109    */
 110    private Map flushTimes;
 111   
 112    /**
 113    * Required so we can look up the app scope cache without forcing a session creation.
 114    */
 115    private transient ServletContext context;
 116   
 117    /**
 118    * Key to use for storing and retrieving Object in contexts (Servlet, session).
 119    */
 120    private String cacheKey;
 121   
 122    /**
 123    * Set property cache.use.host.domain.in.key=true to add domain information to key
 124    * generation for hosting multiple sites.
 125    */
 126    private boolean useHostDomainInKey = false;
 127   
 128    /**
 129    * Create the cache administrator.
 130    *
 131    * This will reset all the flush times and load the properties file.
 132    */
 133  0 private ServletCacheAdministrator(ServletContext context, Properties p) {
 134  0 super(p);
 135  0 config.set(HASH_KEY_CONTEXT_TMPDIR, context.getAttribute("javax.servlet.context.tempdir"));
 136   
 137  0 flushTimes = new HashMap();
 138  0 initHostDomainInKey();
 139  0 this.context = context;
 140    }
 141   
 142    /**
 143    * Obtain an instance of the CacheAdministrator
 144    *
 145    * @param context The ServletContext that this CacheAdministrator is a Singleton under
 146    * @return Returns the CacheAdministrator instance for this context
 147    */
 148  0 public static ServletCacheAdministrator getInstance(ServletContext context) {
 149  0 return getInstance(context, null);
 150    }
 151   
 152    /**
 153    * Obtain an instance of the CacheAdministrator
 154    *
 155    * @param context The ServletContext that this CacheAdministrator is a Singleton under
 156    * @param p the properties to use for the cache if the cache administrator has not been
 157    * created yet. Once the administrator has been created, the properties parameter is
 158    * ignored for all future invocations. If a null value is passed in, then the properties
 159    * are loaded from the oscache.properties file in the classpath.
 160    * @return Returns the CacheAdministrator instance for this context
 161    */
 162  0 public synchronized static ServletCacheAdministrator getInstance(ServletContext context, Properties p) {
 163   
 164  0 ServletCacheAdministrator admin = (ServletCacheAdministrator) context.getAttribute(CACHE_ADMINISTRATOR_KEY);
 165   
 166    // First time we need to create the administrator and store it in the
 167    // servlet context
 168  0 if (admin == null) {
 169  0 admin = new ServletCacheAdministrator(context, p);
 170  0 context.setAttribute(CACHE_ADMINISTRATOR_KEY, admin);
 171   
 172  0 if (log.isInfoEnabled()) {
 173  0 log.info("Created new instance of ServletCacheAdministrator");
 174    }
 175   
 176  0 admin.getAppScopeCache(context);
 177    }
 178   
 179  0 if (admin.context == null) {
 180  0 admin.context = context;
 181    }
 182   
 183  0 return admin;
 184    }
 185   
 186    /**
 187    * Shuts down the cache administrator. This should usually only be called
 188    * when the controlling application shuts down.
 189    */
 190  0 public static void destroyInstance(ServletContext context) {
 191  0 ServletCacheAdministrator admin;
 192  0 admin = (ServletCacheAdministrator) context.getAttribute(CACHE_ADMINISTRATOR_KEY);
 193   
 194  0 if (admin != null) {
 195    // Finalize the application scope cache
 196  0 Cache cache = (Cache) context.getAttribute(admin.getCacheKey());
 197   
 198  0 if (cache != null) {
 199  0 admin.finalizeListeners(cache);
 200  0 context.removeAttribute(admin.getCacheKey());
 201  0 context.removeAttribute(CACHE_ADMINISTRATOR_KEY);
 202  0 cache = null;
 203   
 204  0 if (log.isInfoEnabled()) {
 205  0 log.info("Shut down the ServletCacheAdministrator");
 206    }
 207    }
 208   
 209  0 admin = null;
 210    }
 211    }
 212   
 213    /**
 214    * Grabs the cache for the specified scope
 215    *
 216    * @param request The current request
 217    * @param scope The scope of this cache (<code>PageContext.APPLICATION_SCOPE</code>
 218    * or <code>PageContext.SESSION_SCOPE</code>)
 219    * @return The cache
 220    */
 221  0 public Cache getCache(HttpServletRequest request, int scope) {
 222  0 if (scope == PageContext.APPLICATION_SCOPE) {
 223  0 return getAppScopeCache(context);
 224    }
 225   
 226  0 if (scope == PageContext.SESSION_SCOPE) {
 227  0 return getSessionScopeCache(request.getSession(true));
 228    }
 229   
 230  0 throw new RuntimeException("The supplied scope value of " + scope + " is invalid. Acceptable values are PageContext.APPLICATION_SCOPE and PageContext.SESSION_SCOPE");
 231    }
 232   
 233    /**
 234    * A convenience method to retrieve the application scope cache
 235   
 236    * @param context the current <code>ServletContext</code>
 237    * @return the application scope cache. If none is present, one will
 238    * be created.
 239    */
 240  0 public Cache getAppScopeCache(ServletContext context) {
 241  0 Cache cache;
 242  0 Object obj = context.getAttribute(getCacheKey());
 243   
 244  0 if ((obj == null) || !(obj instanceof Cache)) {
 245  0 if (log.isInfoEnabled()) {
 246  0 log.info("Created new application-scoped cache at key: " + getCacheKey());
 247    }
 248   
 249  0 cache = createCache(PageContext.APPLICATION_SCOPE, null);
 250  0 context.setAttribute(getCacheKey(), cache);
 251    } else {
 252  0 cache = (Cache) obj;
 253    }
 254   
 255  0 return cache;
 256    }
 257   
 258    /**
 259    * A convenience method to retrieve the session scope cache
 260    *
 261    * @param session the current <code>HttpSession</code>
 262    * @return the session scope cache for this session. If none is present,
 263    * one will be created.
 264    */
 265  0 public Cache getSessionScopeCache(HttpSession session) {
 266  0 Cache cache;
 267  0 Object obj = session.getAttribute(getCacheKey());
 268   
 269  0 if ((obj == null) || !(obj instanceof Cache)) {
 270  0 if (log.isInfoEnabled()) {
 271  0 log.info("Created new session-scoped cache in session " + session.getId() + " at key: " + getCacheKey());
 272    }
 273   
 274  0 cache = createCache(PageContext.SESSION_SCOPE, session.getId());
 275  0 session.setAttribute(getCacheKey(), cache);
 276    } else {
 277  0 cache = (Cache) obj;
 278    }
 279   
 280  0 return cache;
 281    }
 282   
 283    /**
 284    * Get the cache key from the properties. Set it to a default value if it
 285    * is not present in the properties
 286    *
 287    * @return The cache.key property or the DEFAULT_CACHE_KEY
 288    */
 289  0 public String getCacheKey() {
 290  0 if (cacheKey == null) {
 291  0 cacheKey = getProperty(CACHE_KEY_KEY);
 292   
 293  0 if (cacheKey == null) {
 294  0 cacheKey = DEFAULT_CACHE_KEY;
 295    }
 296    }
 297   
 298  0 return cacheKey;
 299    }
 300   
 301    /**
 302    * Set the flush time for a specific scope to a specific time
 303    *
 304    * @param date The time to flush the scope
 305    * @param scope The scope to be flushed
 306    */
 307  0 public void setFlushTime(Date date, int scope) {
 308  0 if (log.isInfoEnabled()) {
 309  0 log.info("Flushing scope " + scope + " at " + date);
 310    }
 311   
 312  0 synchronized (flushTimes) {
 313  0 if (date != null) {
 314    // Trigger a SCOPE_FLUSHED event
 315  0 dispatchScopeEvent(ScopeEventType.SCOPE_FLUSHED, scope, date, null);
 316  0 flushTimes.put(new Integer(scope), date);
 317    } else {
 318  0 logError("setFlushTime called with a null date.");
 319  0 throw new IllegalArgumentException("setFlushTime called with a null date.");
 320    }
 321    }
 322    }
 323   
 324    /**
 325    * Set the flush time for a specific scope to the current time.
 326    *
 327    * @param scope The scope to be flushed
 328    */
 329  0 public void setFlushTime(int scope) {
 330  0 setFlushTime(new Date(), scope);
 331    }
 332   
 333    /**
 334    * Get the flush time for a particular scope.
 335    *
 336    * @param scope The scope to get the flush time for.
 337    * @return A date representing the time this scope was last flushed.
 338    * Returns null if it has never been flushed.
 339    */
 340  0 public Date getFlushTime(int scope) {
 341  0 synchronized (flushTimes) {
 342  0 return (Date) flushTimes.get(new Integer(scope));
 343    }
 344    }
 345   
 346    /**
 347    * Retrieve an item from the cache
 348    *
 349    * @param scope The cache scope
 350    * @param request The servlet request
 351    * @param key The key of the object to retrieve
 352    * @param refreshPeriod The time interval specifying if an entry needs refresh
 353    * @return The requested object
 354    * @throws NeedsRefreshException
 355    */
 356  0 public Object getFromCache(int scope, HttpServletRequest request, String key, int refreshPeriod) throws NeedsRefreshException {
 357  0 Cache cache = getCache(request, scope);
 358  0 key = this.generateEntryKey(key, request, scope);
 359  0 return cache.getFromCache(key, refreshPeriod);
 360    }
 361   
 362    /**
 363    * Checks if the given scope was flushed more recently than the CacheEntry provided.
 364    * Used to determine whether to refresh the particular CacheEntry.
 365    *
 366    * @param cacheEntry The cache entry which we're seeing whether to refresh
 367    * @param scope The scope we're checking
 368    *
 369    * @return Whether or not the scope has been flushed more recently than this cache entry was updated.
 370    */
 371  0 public boolean isScopeFlushed(CacheEntry cacheEntry, int scope) {
 372  0 Date flushDateTime = getFlushTime(scope);
 373   
 374  0 if (flushDateTime != null) {
 375  0 long lastUpdate = cacheEntry.getLastUpdate();
 376  0 return (flushDateTime.getTime() >= lastUpdate);
 377    } else {
 378  0 return false;
 379    }
 380    }
 381   
 382    /**
 383    * Register a listener for Cache Map events.
 384    *
 385    * @param listener The object that listens to events.
 386    */
 387  0 public void addScopeEventListener(ScopeEventListener listener) {
 388  0 listenerList.add(ScopeEventListener.class, listener);
 389    }
 390   
 391    /**
 392    * Cancels a pending cache update. This should only be called by a thread
 393    * that received a {@link NeedsRefreshException} and was unable to generate
 394    * some new cache content.
 395    *
 396    * @param scope The cache scope
 397    * @param request The servlet request
 398    * @param key The cache entry key to cancel the update of.
 399    */
 400  0 public void cancelUpdate(int scope, HttpServletRequest request, String key) {
 401  0 Cache cache = getCache(request, scope);
 402  0 key = this.generateEntryKey(key, request, scope);
 403  0 cache.cancelUpdate(key);
 404    }
 405   
 406    /**
 407    * Flush all scopes at a particular time
 408    *
 409    * @param date The time to flush the scope
 410    */
 411  0 public void flushAll(Date date) {
 412  0 synchronized (flushTimes) {
 413  0 setFlushTime(date, PageContext.APPLICATION_SCOPE);
 414  0 setFlushTime(date, PageContext.SESSION_SCOPE);
 415  0 setFlushTime(date, PageContext.REQUEST_SCOPE);
 416  0 setFlushTime(date, PageContext.PAGE_SCOPE);
 417    }
 418   
 419    // Trigger a flushAll event
 420  0 dispatchScopeEvent(ScopeEventType.ALL_SCOPES_FLUSHED, -1, date, null);
 421    }
 422   
 423    /**
 424    * Flush all scopes instantly.
 425    */
 426  0 public void flushAll() {
 427  0 flushAll(new Date());
 428    }
 429   
 430    /**
 431    * Generates a cache entry key.
 432    *
 433    * If the string key is not specified, the HTTP request URI and QueryString is used.
 434    * Operating systems that have a filename limitation less than 255 or have
 435    * filenames that are case insensitive may have issues with key generation where
 436    * two distinct pages map to the same key.
 437    * <p>
 438    * POST Requests (which have no distinguishing
 439    * query string) may also generate identical keys for what is actually different pages.
 440    * In these cases, specify an explicit key attribute for the CacheTag.
 441    *
 442    * @param key The key entered by the user
 443    * @param request The current request
 444    * @param scope The scope this cache entry is under
 445    * @return The generated cache key
 446    */
 447  0 public String generateEntryKey(String key, HttpServletRequest request, int scope) {
 448  0 return generateEntryKey(key, request, scope, null, null);
 449    }
 450   
 451    /**
 452    * Generates a cache entry key.
 453    *
 454    * If the string key is not specified, the HTTP request URI and QueryString is used.
 455    * Operating systems that have a filename limitation less than 255 or have
 456    * filenames that are case insensitive may have issues with key generation where
 457    * two distinct pages map to the same key.
 458    * <p>
 459    * POST Requests (which have no distinguishing
 460    * query string) may also generate identical keys for what is actually different pages.
 461    * In these cases, specify an explicit key attribute for the CacheTag.
 462    *
 463    * @param key The key entered by the user
 464    * @param request The current request
 465    * @param scope The scope this cache entry is under
 466    * @param language The ISO-639 language code to distinguish different pages in application scope
 467    * @return The generated cache key
 468    */
 469  0 public String generateEntryKey(String key, HttpServletRequest request, int scope, String language) {
 470  0 return generateEntryKey(key, request, scope, language, null);
 471    }
 472   
 473    /**
 474    * Generates a cache entry key.
 475    * <p>
 476    * If the string key is not specified, the HTTP request URI and QueryString is used.