|
|||||||||||||||||||
| Source file | Conditionals | Statements | Methods | TOTAL | |||||||||||||||
| CacheTag.java | 0% | 0% | 0% | 0% |
|
||||||||||||||
| 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 |
* <%@ taglib uri="oscache" prefix="cache" %>
|
|
| 35 |
* <cache:cache key="mycache"
|
|
| 36 |
* scope="application"
|
|
| 37 |
* refresh="false"
|
|
| 38 |
* time="30">
|
|
| 39 |
* jsp content here... refreshed every 30 seconds
|
|
| 40 |
* </cache:cache>
|
|
| 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.4 $
|
|
| 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 | 0 |
this.groups = StringUtil.split(groups, ',');
|
| 210 |
} |
|
| 211 |
|
|
| 212 |
/**
|
|
| 213 |
* Adds to the groups for this cache entry.
|
|
| 214 |
*
|
|
| 215 |
* @param group A group to which the cache entry should belong.
|
|
| 216 |
*/
|
|
| 217 | 0 |
void addGroup(String group) {
|
| 218 | 0 |
if (groups == null) { |
| 219 | 0 |
groups = new ArrayList();
|
| 220 |
} |
|
| 221 |
|
|
| 222 | 0 |
groups.add(group); |
| 223 |
} |
|
| 224 |
|
|
| 225 |
/**
|
|
| 226 |
* Set the key for this cache entry.
|
|
| 227 |
*
|
|
| 228 |
* @param key The key for this cache entry.
|
|
| 229 |
*/
|
|
| 230 | 0 |
public void setKey(String key) { |
| 231 | 0 |
this.key = key;
|
| 232 |
} |
|
| 233 |
|
|
| 234 |
/**
|
|
| 235 |
* Set the ISO-639 language code to distinguish different pages in application scope
|
|
| 236 |
*
|
|
| 237 |
* @param language The language code for this cache entry.
|
|
| 238 |
*/
|
|
| 239 | 0 |
public void setLanguage(String language) { |
| 240 | 0 |
this.language = language;
|
| 241 |
} |
|
| 242 |
|
|
| 243 |
/**
|
|
| 244 |
* This method allows the user to programatically decide whether the cached
|
|
| 245 |
* content should be refreshed immediately.
|
|
| 246 |
*
|
|
| 247 |
* @param refresh Whether or not to refresh this cache entry immediately.
|
|
| 248 |
*/
|
|
| 249 | 0 |
public void setRefresh(boolean refresh) { |
| 250 | 0 |
this.refresh = refresh;
|
| 251 |
} |
|
| 252 |
|
|
| 253 |
/**
|
|
| 254 |
* Setting this to <code>true</code> prevents the cache from writing any output
|
|
| 255 |
* to the response, however the JSP content is still cached as normal.
|
|
| 256 |
* @param mode The cache mode to use.
|
|
| 257 |
*/
|
|
| 258 | 0 |
public void setMode(String mode) { |
| 259 | 0 |
if ("silent".equalsIgnoreCase(mode)) { |
| 260 | 0 |
this.mode = SILENT_MODE;
|
| 261 |
} else {
|
|
| 262 | 0 |
this.mode = 0;
|
| 263 |
} |
|
| 264 |
} |
|
| 265 |
|
|
| 266 |
/**
|
|
| 267 |
* Class used to handle the refresh policy logic
|
|
| 268 |
*/
|
|
| 269 | 0 |
public void setRefreshpolicyclass(String refreshPolicyClass) { |
| 270 | 0 |
this.refreshPolicyClass = refreshPolicyClass;
|
| 271 |
} |
|
| 272 |
|
|
| 273 |
/**
|
|
| 274 |
* Parameters that will be passed to the init method of the
|
|
| 275 |
* refresh policy instance.
|
|
| 276 |
*/
|
|
| 277 | 0 |
public void setRefreshpolicyparam(String refreshPolicyParam) { |
| 278 | 0 |
this.refreshPolicyParam = refreshPolicyParam;
|
| 279 |
} |
|
| 280 |
|
|
| 281 |
// ----------- setMethods ------------------------------------------------------
|
|
| 282 |
|
|
| 283 |
/**
|
|
| 284 |
* Set the scope of this cache.
|
|
| 285 |
* <p>
|
|
| 286 |
* @param scope The scope of this cache. Either "application" (default) or "session".
|
|
| 287 |
*/
|
|
| 288 | 0 |
public void setScope(String scope) { |
| 289 | 0 |
if (scope.equalsIgnoreCase(ServletCacheAdministrator.SESSION_SCOPE_NAME)) {
|
| 290 | 0 |
this.scope = PageContext.SESSION_SCOPE;
|
| 291 |
} else {
|
|
| 292 | 0 |
this.scope = PageContext.APPLICATION_SCOPE;
|
| 293 |
} |
|
| 294 |
} |
|
| 295 |
|
|
| 296 |
/**
|
|
| 297 |
* Set the time this cache entry will be cached for (in seconds)
|
|
| 298 |
*
|
|
| 299 |
* @param time The time to cache this content (in seconds). Passing in
|
|
| 300 |
* a time of zero will turn off the caching. A negative value for the
|
|
| 301 |
* time will result in the cached content never expiring (ie, the cached
|
|
| 302 |
* content will always be served if it is present)
|
|
| 303 |
*/
|
|
| 304 | 0 |
public void setTime(int time) { |
| 305 | 0 |
this.time = time;
|
| 306 |
} |
|
| 307 |
|
|
| 308 |
/**
|
|
| 309 |
* This controls whether or not the body of the tag is evaluated or used.<p>
|
|
| 310 |
*
|
|
| 311 |
* It is most often called by the <UseCached /> tag to tell this tag to
|
|
| 312 |
* use the cached content.
|
|
| 313 |
*
|
|
| 314 |
* @see UseCachedTag
|
|
| 315 |
* @param useBody Whether or not to use the cached content.
|
|
| 316 |
*/
|
|
| 317 | 0 |
public void setUseBody(boolean useBody) { |
| 318 | 0 |
if (log.isInfoEnabled()) {
|
| 319 | 0 |
log.info("<cache>: Set useBody to " + useBody);
|
| 320 |
} |
|
| 321 |
|
|
| 322 | 0 |
this.useBody = useBody;
|
| 323 |
} |
|
| 324 |
|
|
| 325 |
/**
|
|
| 326 |
* After the cache body, either update the cache, serve new cached content or
|
|
| 327 |
* indicate an error.
|
|
| 328 |
*
|
|
| 329 |
* @throws JspTagException The standard exception thrown.
|
|
| 330 |
* @return The standard BodyTag return.
|
|
| 331 |
*/
|
|
| 332 | 0 |
public int doAfterBody() throws JspTagException { |
| 333 | 0 |
String body = null;
|
| 334 |
|
|
| 335 | 0 |
try {
|
| 336 |
// if we have a body, and we have not been told to use the cached version
|
|
| 337 | 0 |
if ((bodyContent != null) && (useBody || (time == 0)) && ((body = bodyContent.getString()) != null)) { |
| 338 | 0 |
if ((time != 0) || (refreshPolicyClass != null)) { |
| 339 |
// Instantiate custom refresh policy if needed
|
|
| 340 | 0 |
WebEntryRefreshPolicy policy = null;
|
| 341 |
|
|
| 342 | 0 |
if (refreshPolicyClass != null) { |
| 343 | 0 |
try {
|
| 344 | 0 |
policy = (WebEntryRefreshPolicy) Class.forName(refreshPolicyClass).newInstance(); |
| 345 | 0 |
policy.init(actualKey, refreshPolicyParam); |
| 346 |
} catch (Exception e) {
|
|
| 347 | 0 |
if (log.isInfoEnabled()) {
|
| 348 | 0 |
log.info("<cache>: Problem instantiating or initializing refresh policy : " + refreshPolicyClass);
|
| 349 |
} |
|
| 350 |
} |
|
| 351 |
} |
|
| 352 |
|
|
| 353 | 0 |
if (log.isInfoEnabled()) {
|
| 354 | 0 |
log.info("<cache>: Updating cache entry with new content : " + actualKey);
|
| 355 |
} |
|
| 356 |
|
|
| 357 | 0 |
cancelUpdateRequired = false;
|
| 358 |
|
|
| 359 | 0 |
if ((groups == null) || groups.isEmpty()) { |
| 360 | 0 |
cache.putInCache(actualKey, body, policy); |
| 361 |
} else {
|
|
| 362 | 0 |
String[] groupArray = new String[groups.size()];
|
| 363 | 0 |
groups.toArray(groupArray); |
| 364 | 0 |
cache.putInCache(actualKey, body, groupArray, policy, null);
|
| 365 |
} |
|
| 366 |
} |
|
| 367 |
} |
|
| 368 |
// otherwise if we have been told to use the cached content and we have cached content
|
|
| 369 |
else {
|
|
| 370 | 0 |
if (!useBody && (content != null)) { |
| 371 | 0 |
if (log.isInfoEnabled()) {
|
| 372 | 0 |
log.info("<cache>: Using cached version as instructed, useBody = false : " + actualKey);
|
| 373 |
} |
|
| 374 |
|
|
| 375 | 0 |
body = content; |
| 376 |
} |
|
| 377 |
// either the cached entry is blank and a subtag has said don't useBody, or body is null
|
|
| 378 |
else {
|
|
| 379 | 0 |
if (log.isInfoEnabled()) {
|
| 380 | 0 |
log.info("<cache>: Missing cached content : " + actualKey);
|
| 381 |
} |
|
| 382 |
|
|
| 383 | 0 |
body = "Missing cached content";
|
| 384 |
} |
|
| 385 |
} |
|
| 386 |
|
|
| 387 |
// Only display anything if we're not running in silent mode
|
|
| 388 | 0 |
if (mode != SILENT_MODE) {
|
| 389 | 0 |
bodyContent.clearBody(); |
| 390 | 0 |
bodyContent.write(body); |
| 391 | 0 |
bodyContent.writeOut(bodyContent.getEnclosingWriter()); |
| 392 |
} |
|
| 393 |
} catch (java.io.IOException e) {
|
|
| 394 | 0 |
throw new JspTagException("IO Error: " + e.getMessage()); |
| 395 |
} |
|
| 396 |
|
|
| 397 | 0 |
return SKIP_BODY;
|
| 398 |
} |
|
| 399 |
|
|
| 400 | 0 |
public void doCatch(Throwable throwable) throws Throwable { |
| 401 | 0 |
throw throwable;
|
| 402 |
} |
|
| 403 |
|
|
| 404 |
/**
|
|
| 405 |
* The end tag - clean up variables used.
|
|
| 406 |
*
|
|
| 407 |
* @throws JspTagException The standard exception thrown.
|
|
| 408 |
* @return The standard BodyTag return.
|
|
| 409 |
*/
|
|
| 410 | 0 |
public int doEndTag() throws JspTagException { |
| 411 | 0 |
return EVAL_PAGE;
|
| 412 |
} |
|
| 413 |
|
|
| 414 | 0 |
public void doFinally() { |
| 415 | 0 |
if (cancelUpdateRequired && (actualKey != null)) { |
| 416 | 0 |
cache.cancelUpdate(actualKey); |
| 417 |
} |
|
| 418 |
} |
|
| 419 |
|
|
| 420 |
/**
|
|
| 421 |
* The start of the tag.
|
|
| 422 |
* <p>
|
|
| 423 |
* Grabs the administrator, the cache, the specific cache entry, then decides
|
|
| 424 |
* whether to refresh.
|
|
| 425 |
* <p>
|
|
| 426 |
* If no refresh is needed, this serves the cached content directly.
|
|
| 427 |
*
|
|
| 428 |
* @throws JspTagException The standard exception thrown.
|
|
| 429 |
* @return The standard doStartTag() return.
|
|
| 430 |
*/
|
|
| 431 | 0 |
public int doStartTag() throws JspTagException { |
| 432 | 0 |
cancelUpdateRequired = false;
|
| 433 | 0 |
useBody = true;
|
| 434 | 0 |
content = null;
|
| 435 |
|
|
| 436 |
// We can only skip the body if the cache has the data
|
|
| 437 | 0 |
int returnCode = EVAL_BODY_BUFFERED;
|
| 438 |
|
|
| 439 | 0 |
if (admin == null) { |
| 440 | 0 |
admin = ServletCacheAdministrator.getInstance(pageContext.getServletContext()); |
| 441 |
} |
|
| 442 |
|
|
| 443 |
// Retrieve the cache
|
|
| 444 | 0 |
cache = admin.getCache((HttpServletRequest) pageContext.getRequest(), scope); |
| 445 |
|
|
| 446 |
// This allows to have multiple cache tags on a single page without
|
|
| 447 |
// having to specify keys. However, nested cache tags are not supported.
|
|
| 448 |
// In that case you would have to supply a key.
|
|
| 449 | 0 |
String suffix = null;
|
| 450 |
|
|
| 451 | 0 |
if (key == null) { |
| 452 | 0 |
synchronized (pageContext.getRequest()) {
|
| 453 | 0 |
Object o = pageContext.getRequest().getAttribute(CACHE_TAG_COUNTER_KEY); |
| 454 |
|
|
| 455 | 0 |
if (o == null) { |
| 456 | 0 |
suffix = "1";
|
| 457 |
} else {
|
|
| 458 | 0 |
suffix = Integer.toString(Integer.parseInt((String) o) + 1); |
| 459 |
} |
|
| 460 |
} |
|
| 461 |
|
|
| 462 | 0 |
pageContext.getRequest().setAttribute(CACHE_TAG_COUNTER_KEY, suffix); |
| 463 |
} |
|
| 464 |
|
|
| 465 |
// Generate the actual cache key
|
|
| 466 | 0 |
actualKey = admin.generateEntryKey(key, (HttpServletRequest) pageContext.getRequest(), scope, language, suffix); |
| 467 |
|
|
| 468 |
/*
|
|
| 469 |
if
|
|
| 470 |
- refresh is not set,
|
|
| 471 |
- the cacheEntry itself does not need to be refreshed before 'time' and
|
|
| 472 |
- the administrator has not had the cache entry's scope flushed
|
|
| 473 |
|
|
| 474 |
send out the cached version!
|
|
| 475 |
*/
|
|
| 476 | 0 |
try {
|
| 477 | 0 |
if (refresh) {
|
| 478 |
// Force a refresh
|
|
| 479 | 0 |
content = (String) cache.getFromCache(actualKey, 0, cron); |
| 480 |
} else {
|
|
| 481 |
// Use the specified refresh period
|
|
| 482 | 0 |
content = (String) cache.getFromCache(actualKey, time, cron); |
| 483 |
} |
|
| 484 |
|
|
| 485 | 0 |
try {
|
| 486 | 0 |
if (log.isDebugEnabled()) {
|
| 487 | 0 |
log.debug("<cache>: Using cached entry : " + actualKey);
|
| 488 |
} |
|
| 489 |
|
|
| 490 |
// Ensure that the cache returns the data correctly. Else re-evaluate the body
|
|
| 491 | 0 |
if ((content != null)) { |
| 492 | 0 |
if (mode != SILENT_MODE) {
|
| 493 | 0 |
pageContext.getOut().write(content); |
| 494 |
} |
|
| 495 |
|
|
| 496 | 0 |
returnCode = SKIP_BODY; |
| 497 |
} |
|
| 498 |
} catch (IOException e) {
|
|
| 499 | 0 |
throw new JspTagException("IO Exception: " + e.getMessage()); |
| 500 |
} |
|
| 501 |
} catch (NeedsRefreshException nre) {
|
|
| 502 | 0 |
cancelUpdateRequired = true;
|
| 503 | 0 |
content = (String) nre.getCacheContent(); |
| 504 |
} |
|
| 505 |
|
|
| 506 | 0 |
if (returnCode == EVAL_BODY_BUFFERED) {
|
| 507 | 0 |
if (log.isInfoEnabled()) {
|
| 508 | 0 |
log.info("<cache>: Cached content not used: New cache entry, cache stale or scope flushed : " + actualKey);
|
| 509 |
} |
|
| 510 |
} |
|
| 511 |
|
|
| 512 | 0 |
return returnCode;
|
| 513 |
} |
|
| 514 |
|
|
| 515 |
/**
|
|
| 516 |
* Convert a SimpleDateFormat string to seconds
|
|
| 517 |
* Acceptable format are :
|
|
| 518 |
* <ul>
|
|
| 519 |
* <li>0s (seconds)
|
|
| 520 |
* <li>0m (minute)
|
|
| 521 |
* <li>0h (hour)
|
|
| 522 |
* <li>0d (day)
|
|
| 523 |
* <li>0w (week)
|
|
| 524 |
* </ul>
|
|
| 525 |
* @param duration The simple date time to parse
|
|
| 526 |
* @return The value in seconds
|
|
| 527 |
*/
|
|
| 528 | 0 |
private int parseDuration(String duration) { |
| 529 | 0 |
int time = 0;
|
| 530 |
|
|
| 531 |
//Detect if the factor is specified
|
|
| 532 | 0 |
try {
|
| 533 | 0 |
time = Integer.parseInt(duration); |
| 534 |
} catch (Exception ex) {
|
|
| 535 |
//Extract number and ajust this number with the time factor
|
|
| 536 | 0 |
for (int i = 0; i < duration.length(); i++) { |
| 537 | 0 |
if (!Character.isDigit(duration.charAt(i))) {
|
| 538 | 0 |
time = Integer.parseInt(duration.substring(0, i)); |
| 539 |
|
|
| 540 | 0 |
switch ((int) duration.charAt(i)) { |
| 541 |
case (int) 's': |
|
| 542 | 0 |
time *= SECOND; |
| 543 | 0 |
break;
|
| 544 |
case (int) 'm': |
|
| 545 | 0 |
time *= MINUTE; |
| 546 | 0 |
break;
|
| 547 |
case (int) 'h': |
|
| 548 | 0 |
time *= HOUR; |
| 549 | 0 |
break;
|
| 550 |
case (int) 'd': |
|
| 551 | 0 |
time *= DAY; |
| 552 | 0 |
break;
|
| 553 |
case (int) 'w': |
|
| 554 | 0 |
time *= WEEK; |
| 555 | 0 |
break;
|
| 556 |
default:
|
|
| 557 |
//no defined use as is
|
|
| 558 |
} |
|
| 559 |
|
|
| 560 | 0 |
break;
|
| 561 |
} |
|
| 562 |
|
|
| 563 |
// if
|
|
| 564 |
} |
|
| 565 |
|
|
| 566 |
// for
|
|
| 567 |
} |
|
| 568 |
|
|
| 569 |
// catch
|
|
| 570 | 0 |
return time;
|
| 571 |
} |
|
| 572 |
|
|
| 573 |
/**
|
|
| 574 |
* Parse an ISO-8601 format date and return it's value in seconds
|
|
| 575 |
*
|
|
| 576 |
* @param duration The ISO-8601 date
|
|
| 577 |
* @return The equivalent number of seconds
|
|
| 578 |
* @throws Exception
|
|
| 579 |
*/
|
|
| 580 | 0 |
private int parseISO_8601_Duration(String duration) throws Exception { |
| 581 | 0 |
int years = 0;
|
| 582 | 0 |
int months = 0;
|
| 583 | 0 |
int days = 0;
|
| 584 | 0 |
int hours = 0;
|
| 585 | 0 |
int mins = 0;
|
| 586 | 0 |
int secs = 0;
|
| 587 |
|
|
| 588 |
// If there is a negative sign, it must be first
|
|
| 589 |
// If it is present, we will ignore it
|
|
| 590 | 0 |
int index = duration.indexOf("-"); |
| 591 |
|
|
| 592 | 0 |
if (index > 0) {
|
| 593 | 0 |
throw new Exception("Invalid duration (- must be at the beginning)"); |
| 594 |
} |
|
| 595 |
|
|
| 596 |
// First caracter must be P
|
|
| 597 | 0 |
String workValue = duration.substring(index + 1); |
| 598 |
|
|
| 599 | 0 |
if (workValue.charAt(0) != 'P') {
|
| 600 | 0 |
throw new Exception("Invalid duration (P must be at the beginning)"); |
| 601 |
} |
|
| 602 |
|
|
| 603 |
// Must contain a value
|
|
| 604 | 0 |
workValue = workValue.substring(1); |
| 605 |
|
|
| 606 | 0 |
if (workValue.length() == 0) {
|
| 607 | 0 |
throw new Exception("Invalid duration (nothing specified)"); |
| 608 |
} |
|
| 609 |
|
|
| 610 |
// Check if there is a T
|
|
| 611 | 0 |
index = workValue.indexOf('T');
|
| 612 |
|
|
| 613 | 0 |
String timeString = "";
|
| 614 |
|
|
| 615 | 0 |
if (index > 0) {
|
| 616 | 0 |
timeString = workValue.substring(index + 1); |
| 617 |
|
|
| 618 |
// Time cannot be empty
|
|
| 619 | 0 |
if (timeString.equals("")) { |
| 620 | 0 |
throw new Exception("Invalid duration (T with no time)"); |
| 621 |
} |
|
| 622 |
|
|
| 623 | 0 |
workValue = workValue.substring(0, index); |
| 624 | 0 |
} else if (index == 0) { |
| 625 | 0 |
timeString = workValue.substring(1); |
| 626 | 0 |
workValue = "";
|
| 627 |
} |
|
| 628 |
|
|
| 629 | 0 |
if (!workValue.equals("")) { |
| 630 | 0 |
validateDateFormat(workValue); |
| 631 |
|
|
| 632 | 0 |
int yearIndex = workValue.indexOf('Y');
|
| 633 | 0 |
int monthIndex = workValue.indexOf('M');
|
| 634 | 0 |
int dayIndex = workValue.indexOf('D');
|
| 635 |
|
|
| 636 | 0 |
if ((yearIndex != -1) && (monthIndex != -1) && (yearIndex > monthIndex)) {
|
| 637 | 0 |
throw new Exception("Invalid duration (Date part not properly specified)"); |
| 638 |
} |
|
| 639 |
|
|
| 640 | 0 |
if ((yearIndex != -1) && (dayIndex != -1) && (yearIndex > dayIndex)) {
|
| 641 | 0 |
throw new Exception("Invalid duration (Date part not properly specified)"); |
| 642 |
} |
|
| 643 |
|
|
| 644 | 0 |
if ((dayIndex != -1) && (monthIndex != -1) && (monthIndex > dayIndex)) {
|
| 645 | 0 |
throw new Exception("Invalid duration (Date part not properly specified)"); |
| 646 |
} |
|
| 647 |
|
|
| 648 | 0 |
if (yearIndex >= 0) {
|
| 649 | 0 |
years = (new Integer(workValue.substring(0, yearIndex))).intValue();
|
| 650 |
} |
|
| 651 |
|
|
| 652 | 0 |
if (monthIndex >= 0) {
|
| 653 | 0 |
months = (new Integer(workValue.substring(yearIndex + 1, monthIndex))).intValue();
|
| 654 |
} |
|
| 655 |
|
|
| 656 | 0 |
if (dayIndex >= 0) {
|
| 657 | 0 |
if (monthIndex >= 0) {
|
| 658 | 0 |
days = (new Integer(workValue.substring(monthIndex + 1, dayIndex))).intValue();
|
| 659 |
} else {
|
|
| 660 | 0 |
if (yearIndex >= 0) {
|
| 661 | 0 |
days = (new Integer(workValue.substring(yearIndex + 1, dayIndex))).intValue();
|
| 662 |
} else {
|
|
| 663 | 0 |
days = (new Integer(workValue.substring(0, dayIndex))).intValue();
|
| 664 |
} |
|
| 665 |
} |
|
| 666 |
} |
|
| 667 |
} |
|
| 668 |
|
|
| 669 | 0 |
if (!timeString.equals("")) { |
| 670 | 0 |
validateHourFormat(timeString); |
| 671 |
|
|
| 672 | 0 |
int hourIndex = timeString.indexOf('H');
|
| 673 | 0 |
int minuteIndex = timeString.indexOf('M');
|
| 674 | 0 |
int secondIndex = timeString.indexOf('S');
|
| 675 |
|
|
| 676 | 0 |
if ((hourIndex != -1) && (minuteIndex != -1) && (hourIndex > minuteIndex)) {
|
| 677 | 0 |
throw new Exception("Invalid duration (Time part not properly specified)"); |
| 678 |
} |
|
| 679 |
|
|
| 680 | 0 |
if ((hourIndex != -1) && (secondIndex != -1) && (hourIndex > secondIndex)) {
|
| 681 | 0 |
throw new Exception("Invalid duration (Time part not properly specified)"); |
| 682 |
} |
|
| 683 |
|
|
| 684 | 0 |
if ((secondIndex != -1) && (minuteIndex != -1) && (minuteIndex > secondIndex)) {
|
| 685 | 0 |
throw new Exception("Invalid duration (Time part not properly specified)"); |
| 686 |
} |
|
| 687 |
|
|
| 688 | 0 |
if (hourIndex >= 0) {
|
| 689 | 0 |
hours = (new Integer(timeString.substring(0, hourIndex))).intValue();
|
| 690 |
} |
|
| 691 |
|
|
| 692 | 0 |
if (minuteIndex >= 0) {
|
| 693 | 0 |
mins = (new Integer(timeString.substring(hourIndex + 1, minuteIndex))).intValue();
|
| 694 |
} |
|
| 695 |
|
|
| 696 | 0 |
if (secondIndex >= 0) {
|
| 697 | 0 |
if (timeString.length() != (secondIndex + 1)) {
|
| 698 | 0 |
throw new Exception("Invalid duration (Time part not properly specified)"); |
| 699 |
} |
|
| 700 |
|
|
| 701 | 0 |
if (minuteIndex >= 0) {
|
| 702 | 0 |
timeString = timeString.substring(minuteIndex + 1, timeString.length() - 1); |
| 703 |
} else {
|
|
| 704 | 0 |
if (hourIndex >= 0) {
|
| 705 | 0 |
timeString = timeString.substring(hourIndex + 1, timeString.length() - 1); |
| 706 |
} else {
|
|
| 707 | 0 |
timeString = timeString.substring(0, timeString.length() - 1); |
| 708 |
} |
|
| 709 |
} |
|
| 710 |
|
|
| 711 | 0 |
if (timeString.indexOf('.') == (timeString.length() - 1)) {
|
| 712 | 0 |
throw new Exception("Invalid duration (Time part not properly specified)"); |
| 713 |
} |
|
| 714 |
|
|
| 715 | 0 |
secs = (new Double(timeString)).intValue();
|
| 716 |
} |
|
| 717 |
} |
|
| 718 |
|
|
| 719 |
// Compute Value
|
|
| 720 | 0 |
return secs + (mins * MINUTE) + (hours * HOUR) + (days * DAY) + (months * MONTH) + (years * YEAR);
|
| 721 |
} |
|
| 722 |
|
|
| 723 |
/**
|
|
| 724 |
* Validate the basic date format
|
|
| 725 |
*
|
|
| 726 |
* @param basicDate The string to validate
|
|
| 727 |
* @throws Exception
|
|
| 728 |
*/
|
|
| 729 | 0 |
private void validateDateFormat(String basicDate) throws Exception { |
| 730 | 0 |
int yearCounter = 0;
|
| 731 | 0 |
int monthCounter = 0;
|
| 732 | 0 |
int dayCounter = 0;
|
| 733 |
|
|
| 734 | 0 |
for (int counter = 0; counter < basicDate.length(); counter++) { |
| 735 |
// Check if there's any other caracters than Y, M, D and numbers
|
|
| 736 | 0 |
if (!Character.isDigit(basicDate.charAt(counter)) && (basicDate.charAt(counter) != 'Y') && (basicDate.charAt(counter) != 'M') && (basicDate.charAt(counter) != 'D')) {
|
| 737 | 0 |
throw new Exception("Invalid duration (Date part not properly specified)"); |
| 738 |
} |
|
| 739 |
|
|
| 740 |
// Check if the allowed caracters are present more than 1 time
|
|
| 741 | 0 |
if (basicDate.charAt(counter) == 'Y') {
|
| 742 | 0 |
yearCounter++; |
| 743 |
} |
|
| 744 |
|
|
| 745 | 0 |
if (basicDate.charAt(counter) == 'M') {
|
| 746 | 0 |
monthCounter++; |
| 747 |
} |
|
| 748 |
|
|
| 749 | 0 |
if (basicDate.charAt(counter) == 'D') {
|
| 750 | 0 |
dayCounter++; |
| 751 |
} |
|
| 752 |
} |
|
| 753 |
|
|
| 754 | 0 |
if ((yearCounter > 1) || (monthCounter > 1) || (dayCounter > 1)) {
|
| 755 | 0 |
throw new Exception("Invalid duration (Date part not properly specified)"); |
| 756 |
} |
|
| 757 |
} |
|
| 758 |
|
|
| 759 |
/**
|
|
| 760 |
* Validate the basic hour format
|
|
| 761 |
*
|
|
| 762 |
* @param basicHour The string to validate
|
|
| 763 |
* @throws Exception
|
|
| 764 |
*/
|
|
| 765 | 0 |
private void validateHourFormat(String basicHour) throws Exception { |
| 766 | 0 |
int minuteCounter = 0;
|
| 767 | 0 |
int secondCounter = 0;
|
| 768 | 0 |
int hourCounter = 0;
|
| 769 |
|
|
| 770 | 0 |
for (int counter = 0; counter < basicHour.length(); counter++) { |
| 771 | 0 |
if (!Character.isDigit(basicHour.charAt(counter)) && (basicHour.charAt(counter) != 'H') && (basicHour.charAt(counter) != 'M') && (basicHour.charAt(counter) != 'S') && (basicHour.charAt(counter) != '.')) {
|
| 772 | 0 |
throw new Exception("Invalid duration (Time part not properly specified)"); |
| 773 |
} |
|
| 774 |
|
|
| 775 | 0 |
if (basicHour.charAt(counter) == 'H') {
|
| 776 | 0 |
hourCounter++; |
| 777 |
} |
|
| 778 |
|
|
| 779 | 0 |
if (basicHour.charAt(counter) == 'M') {
|
| 780 | 0 |
minuteCounter++; |
| 781 |
} |
|
| 782 |
|
|
| 783 | 0 |
if (basicHour.charAt(counter) == 'S') {
|
| 784 | 0 |
secondCounter++; |
| 785 |
} |
|
| 786 |
} |
|
| 787 |
|
|
| 788 | 0 |
if ((hourCounter > 1) || (minuteCounter > 1) || (secondCounter > 1)) {
|
| 789 | 0 |
throw new Exception("Invalid duration (Time part not properly specified)"); |
| 790 |
} |
|
| 791 |
} |
|
| 792 |
} |
|
| 793 |
|
|
||||||||||