Clover coverage report - Coverage timestamp: do jan 22 2004 21:12:32 CET FRAMES NO FRAMES |
file stats: | LOC: | 793 |  | Methods: | 22
| NCLOC: | 408 |  | Classes: | 1
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 |  |
      *     &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.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 &lt;UseCached /&gt; 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 |  |