Clover coverage report -
Coverage timestamp: Sun Mar 5 2006 06:05:21 CST
file stats: LOC: 824   Methods: 23
NCLOC: 428   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
CacheTag.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.tag;
 6   
 7    import com.opensymphony.oscache.base.Cache;
 8    import com.opensymphony.oscache.base.NeedsRefreshException;
 9    import com.opensymphony.oscache.util.StringUtil;
 10    import com.opensymphony.oscache.web.ServletCacheAdministrator;
 11    import com.opensymphony.oscache.web.WebEntryRefreshPolicy;
 12   
 13    import org.apache.commons.logging.Log;
 14    import org.apache.commons.logging.LogFactory;
 15   
 16    import java.io.IOException;
 17   
 18    import java.util.ArrayList;
 19    import java.util.List;
 20   
 21    import javax.servlet.http.HttpServletRequest;
 22    import javax.servlet.jsp.JspTagException;
 23    import javax.servlet.jsp.PageContext;
 24    import javax.servlet.jsp.tagext.BodyTagSupport;
 25    import javax.servlet.jsp.tagext.TryCatchFinally;
 26   
 27    /**
 28    * CacheTag is a tag that allows for server-side caching of post-processed JSP content.<p>
 29    *
 30    * It also gives great programatic control over refreshing, flushing and updating the cache.<p>
 31    *
 32    * Usage Example:
 33    * <pre><code>
 34    * &lt;%@ taglib uri="oscache" prefix="cache" %&gt;
 35    * &lt;cache:cache key="mycache"
 36    * scope="application"
 37    * refresh="false"
 38    * time="30">
 39    * jsp content here... refreshed every 30 seconds
 40    * &lt;/cache:cache&gt;
 41    * </code></pre>
 42    *
 43    * @author <a href="mailto:mike@atlassian.com">Mike Cannon-Brookes</a>
 44    * @author <a href="mailto:tgochenour@peregrine.com">Todd Gochenour</a>
 45    * @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
 46    * @author <a href="mailto:abergevin@pyxis-tech.com">Alain Bergevin</a>
 47    * @version $Revision: 1.2 $
 48    */
 49    public class CacheTag extends BodyTagSupport implements TryCatchFinally {
 50    /**
 51    * Constants for time computation
 52    */
 53    private final static int SECOND = 1;
 54    private final static int MINUTE = 60 * SECOND;
 55    private final static int HOUR = 60 * MINUTE;
 56    private final static int DAY = 24 * HOUR;
 57    private final static int WEEK = 7 * DAY;
 58    private final static int MONTH = 30 * DAY;
 59    private final static int YEAR = 365 * DAY;
 60   
 61    /**
 62    * The key under which the tag counter will be stored in the request
 63    */
 64    private final static String CACHE_TAG_COUNTER_KEY = "__oscache_tag_counter";
 65   
 66    /**
 67    * Constants for refresh time
 68    */
 69    final static private int ONE_MINUTE = 60;
 70    final static private int ONE_HOUR = 60 * ONE_MINUTE;
 71    final static private int DEFAULT_TIMEOUT = ONE_HOUR;
 72    private static transient Log log = LogFactory.getLog(CacheTag.class);
 73   
 74    /**
 75    * Cache modes
 76    */
 77    final static private int SILENT_MODE = 1;
 78   
 79    /**
 80    * A flag to indicate whether a NeedsRefreshException was thrown and
 81    * the update needs to be cancelled
 82    */
 83    boolean cancelUpdateRequired = false;
 84    private Cache cache = null;
 85   
 86    /**
 87    * If no groups are specified, the cached content does not get put into any groups
 88    */
 89    private List groups = null;
 90    private ServletCacheAdministrator admin = null;
 91   
 92    /**
 93    * The actual key to use. This is generated based on the supplied key, scope etc.
 94    */
 95    private String actualKey = null;
 96   
 97    /**
 98    * The content that was retrieved from cache
 99    */
 100    private String content = null;
 101   
 102    /**
 103    * The cron expression that is used to expire cache entries at specific dates and/or times.
 104    */
 105    private String cron = null;
 106   
 107    /**
 108    * if cache key is null, the request URI is used
 109    */
 110    private String key = null;
 111   
 112    /**
 113    * The ISO-639 language code to distinguish different pages in application scope
 114    */
 115    private String language = null;
 116   
 117    /**
 118    * Class used to handle the refresh policy logic
 119    */
 120    private String refreshPolicyClass = null;
 121   
 122    /**
 123    * Parameters that will be passed to the init method of the
 124    * refresh policy instance.
 125    */
 126    private String refreshPolicyParam = null;
 127   
 128    /**
 129    * Whether the cache should be refreshed instantly
 130    */
 131    private boolean refresh = false;
 132   
 133    /**
 134    * used for subtags to tell this tag that we should use the cached version
 135    */
 136    private boolean useBody = true;
 137   
 138    /**
 139    * The cache mode. Valid values are SILENT_MODE
 140    */
 141    private int mode = 0;
 142   
 143    /**
 144    * The cache scope to use
 145    */
 146    private int scope = PageContext.APPLICATION_SCOPE;
 147   
 148    /**
 149    * time (in seconds) before cache should be refreshed
 150    */
 151    private int time = DEFAULT_TIMEOUT;
 152   
 153    /**
 154    * Set the time this cache entry will be cached for. A date and/or time in
 155    * either ISO-8601 format or a simple format can be specified. The acceptable
 156    * syntax for the simple format can be any one of the following:
 157    *
 158    * <ul>
 159    * <li>0 (seconds)
 160    * <li>0s (seconds)
 161    * <li>0m (minutes)
 162    * <li>0h (hours)
 163    * <li>0d (days)
 164    * <li>0w (weeks)
 165    * </ul>
 166    *
 167    * @param duration The duration to cache this content (using either the simple
 168    * or the ISO-8601 format). Passing in a duration of zero will turn off the
 169    * caching, while a negative value will result in the cached content never
 170    * expiring (ie, the cached content will always be served as long as it is
 171    * present).
 172    */
 173  0 public void setDuration(String duration) {
 174  0 try {
 175    // Try Simple Date Format Duration first because it's faster
 176  0 this.time = parseDuration(duration);
 177    } catch (Exception ex) {
 178  0 if (log.isDebugEnabled()) {
 179  0 log.debug("Failed parsing simple duration format '" + duration + "' (" + ex.getMessage() + "). Trying ISO-8601 format...");
 180    }
 181   
 182  0 try {
 183    // Try ISO-8601 Duration
 184  0 this.time = parseISO_8601_Duration(duration);
 185    } catch (Exception ex1) {
 186    // An invalid duration entered, not much impact.
 187    // The default timeout will be used
 188  0 log.warn("The requested cache duration '" + duration + "' is invalid (" + ex1.getMessage() + "). Reverting to the default timeout");
 189  0 this.time = DEFAULT_TIMEOUT;
 190    }
 191    }
 192    }
 193   
 194    /**
 195    * Sets the cron expression that should be used to expire content at specific
 196    * dates and/or times.
 197    */
 198  0 public void setCron(String cron) {
 199  0 this.cron = cron;
 200    }
 201   
 202    /**
 203    * Sets the groups for this cache entry. Any existing groups will
 204    * be replaced.
 205    *
 206    * @param groups A comma-delimited list of groups that the cache entry belongs to.
 207    */
 208  0 public void setGroups(String groups) {
 209    // FIXME: ArrayList doesn't avoid duplicates
 210  0 this.groups = StringUtil.split(groups, ',');
 211    }
 212   
 213    /**
 214    * Adds to the groups for this cache entry.
 215    *
 216    * @param group A group to which the cache entry should belong.
 217    */
 218  0 void addGroup(String group) {
 219  0 if (groups == null) {
 220    // FIXME: ArrayList doesn't avoid duplicates
 221  0 groups = new ArrayList();
 222    }
 223   
 224  0 groups.add(group);
 225    }
 226   
 227    /**
 228    * Adds comma-delimited list of groups that the cache entry belongs to.
 229    *
 230    * @param groups A comma-delimited list of groups that the cache entry belongs to also.
 231    */
 232  0 void addGroups(String groupsString) {
 233  0 if (groups == null) {
 234    // FIXME: ArrayList doesn't avoid duplicates
 235  0 groups = new ArrayList();
 236    }
 237   
 238  0 groups.addAll(StringUtil.split(groupsString, ','));
 239    }
 240   
 241    /**
 242    * Set the key for this cache entry.
 243    *
 244    * @param key The key for this cache entry.
 245    */
 246  0 public void setKey(String key) {
 247  0 this.key = key;
 248    }
 249   
 250    /**
 251    * Set the ISO-639 language code to distinguish different pages in application scope
 252    *
 253    * @param language The language code for this cache entry.
 254    */
 255  0 public void setLanguage(String language) {
 256  0 this.language = language;
 257    }
 258   
 259    /**
 260    * This method allows the user to programatically decide whether the cached
 261    * content should be refreshed immediately.
 262    *
 263    * @param refresh Whether or not to refresh this cache entry immediately.
 264    */
 265  0 public void setRefresh(boolean refresh) {
 266  0 this.refresh = refresh;
 267    }
 268   
 269    /**
 270    * Setting this to <code>true</code> prevents the cache from writing any output
 271    * to the response, however the JSP content is still cached as normal.
 272    * @param mode The cache mode to use.
 273    */
 274  0 public void setMode(String mode) {
 275  0 if ("silent".equalsIgnoreCase(mode)) {
 276  0 this.mode = SILENT_MODE;
 277    } else {
 278  0 this.mode = 0;
 279    }
 280    }
 281   
 282    /**
 283    * Class used to handle the refresh policy logic
 284    */
 285  0 public void setRefreshpolicyclass(String refreshPolicyClass) {
 286  0 this.refreshPolicyClass = refreshPolicyClass;
 287    }
 288   
 289    /**
 290    * Parameters that will be passed to the init method of the
 291    * refresh policy instance.
 292    */
 293  0 public void setRefreshpolicyparam(String refreshPolicyParam) {
 294  0 this.refreshPolicyParam = refreshPolicyParam;
 295    }
 296   
 297    // ----------- setMethods ------------------------------------------------------
 298   
 299    /**
 300    * Set the scope of this cache.
 301    * <p>
 302    * @param scope The scope of this cache. Either "application" (default) or "session".
 303    */
 304  0 public void setScope(String scope) {
 305  0 if (scope.equalsIgnoreCase(ServletCacheAdministrator.SESSION_SCOPE_NAME)) {
 306  0 this.scope = PageContext.SESSION_SCOPE;
 307    } else {
 308  0 this.scope = PageContext.APPLICATION_SCOPE;
 309    }
 310    }
 311   
 312    /**
 313    * Set the time this cache entry will be cached for (in seconds)
 314    *
 315    * @param time The time to cache this content (in seconds). Passing in
 316    * a time of zero will turn off the caching. A negative value for the
 317    * time will result in the cached content never expiring (ie, the cached
 318    * content will always be served if it is present)
 319    */
 320  0 public void setTime(int time) {
 321  0 this.time = time;
 322    }
 323   
 324    /**
 325    * This controls whether or not the body of the tag is evaluated or used.<p>
 326    *
 327    * It is most often called by the &lt;UseCached /&gt; tag to tell this tag to
 328    * use the cached content.
 329    *
 330    * @see UseCachedTag
 331    * @param useBody Whether or not to use the cached content.
 332    */
 333  0 public void setUseBody(boolean useBody) {
 334  0 if (log.isDebugEnabled()) {
 335  0 log.debug("<cache>: Set useBody to " + useBody);
 336    }
 337   
 338  0 this.useBody = useBody;
 339    }
 340   
 341    /**
 342    * After the cache body, either update the cache, serve new cached content or
 343    * indicate an error.
 344    *
 345    * @throws JspTagException The standard exception thrown.
 346    * @return The standard BodyTag return.
 347    */
 348  0 public int doAfterBody() throws JspTagException {
 349  0 String body = null;
 350   
 351  0 try {
 352    // if we have a body, and we have not been told to use the cached version
 353  0 if ((bodyContent != null) && (useBody || (time == 0)) && ((body = bodyContent.getString()) != null)) {
 354  0 if ((time != 0) || (refreshPolicyClass != null)) {
 355    // Instantiate custom refresh policy if needed
 356  0 WebEntryRefreshPolicy policy = null;
 357   
 358  0 if (refreshPolicyClass != null) {
 359  0 try {
 360  0 policy = (WebEntryRefreshPolicy) Class.forName(refreshPolicyClass).newInstance();
 361  0 policy.init(actualKey, refreshPolicyParam);
 362    } catch (Exception e) {
 363  0 if (log.isInfoEnabled()) {
 364  0 log.info("<cache>: Problem instantiating or initializing refresh policy : " + refreshPolicyClass);
 365    }
 366    }
 367    }
 368   
 369  0 if (log.isDebugEnabled()) {
 370  0 log.debug("<cache>: Updating cache entry with new content : " + actualKey);
 371    }
 372   
 373  0 cancelUpdateRequired = false;
 374   
 375  0 if ((groups == null) || groups.isEmpty()) {
 376  0 cache.putInCache(actualKey, body, policy);
 377    } else {
 378  0 String[] groupArray = new String[groups.size()];
 379  0 groups.toArray(groupArray);
 380  0 cache.putInCache(actualKey, body, groupArray, policy, null);
 381    }
 382    }
 383    }
 384    // otherwise if we have been told to use the cached content and we have cached content
 385    else {
 386  0 if (!useBody && (content != null)) {
 387  0 if (log.isInfoEnabled()) {
 388  0 log.info("<cache>: Using cached version as instructed, useBody = false : " + actualKey);
 389    }
 390   
 391  0 body = content;
 392    }
 393    // either the cached entry is blank and a subtag has said don't useBody, or body is null
 394    else {
 395  0 if (log.isInfoEnabled()) {
 396  0 log.info("<cache>: Missing cached content : " + actualKey);
 397    }
 398   
 399  0 body = "Missing cached content";
 400    }
 401    }
 402   
 403    // Only display anything if we're not running in silent mode
 404  0 if (mode != SILENT_MODE) {
 405  0 bodyContent.clearBody();
 406  0 bodyContent.write(body);
 407  0 bodyContent.writeOut(bodyContent.getEnclosingWriter());
 408    }
 409    } catch (java.io.IOException e) {
 410  0 throw new JspTagException("IO Error: " + e.getMessage());
 411    }
 412   
 413  0 return SKIP_BODY;
 414    }
 415   
 416  0 public void doCatch(Throwable throwable) throws Throwable {
 417  0 throw throwable;
 418    }
 419   
 420    /**
 421    * The end tag - clean up variables used.
 422    *
 423    * @throws JspTagException The standard exception thrown.
 424    * @return The standard BodyTag return.
 425    */
 426  0 public int doEndTag() throws JspTagException {
 427  0 return EVAL_PAGE;
 428    }
 429   
 430  0 public void doFinally() {
 431  0 if (cancelUpdateRequired && (actualKey != null)) {
 432  0 cache.cancelUpdate(actualKey);
 433    }
 434   
 435    // reset all states, CACHE-144
 436  0 groups = null;
 437  0 scope = PageContext.APPLICATION_SCOPE;
 438  0 cron = null;
 439  0 key = null;
 440  0 language = null;
 441  0 refreshPolicyClass = null;
 442  0 refreshPolicyParam = null;
 443  0 time = DEFAULT_TIMEOUT;
 444  0 refresh = false;
 445  0 mode = 0;
 446    }
 447   
 448    /**
 449    * The start of the tag.
 450    * <p>
 451    * Grabs the administrator, the cache, the specific cache entry, then decides
 452    * whether to refresh.
 453    * <p>
 454    * If no refresh is needed, this serves the cached content directly.
 455    *
 456    * @throws JspTagException The standard exception thrown.
 457    * @return The standard doStartTag() return.
 458    */
 459  0 public int doStartTag() throws JspTagException {
 460  0 cancelUpdateRequired = false;
 461  0 useBody = true;
 462  0 content = null;
 463   
 464    // We can only skip the body if the cache has the data
 465  0 int returnCode = EVAL_BODY_BUFFERED;
 466   
 467  0 if (admin == null) {
 468  0 admin = ServletCacheAdministrator.getInstance(pageContext.getServletContext());
 469    }
 470   
 471    // Retrieve the cache
 472  0 if (scope == PageContext.SESSION_SCOPE) {
 473  0 cache = admin.getSessionScopeCache(((HttpServletRequest) pageContext.getRequest()).getSession(true));
 474    } else {
 475  0 cache = admin.getAppScopeCache(pageContext.getServletContext());
 476    }
 477   
 478    // This allows to have multiple cache tags on a single page without
 479    // having to specify keys. However, nested cache tags are not supported.
 480    // In that case you would have to supply a key.
 481  0 String suffix = null;
 482   
 483  0