Clover coverage report - Coverage timestamp: Sa Jul 7 2007 09:11:40 CEST FRAMES NO FRAMES |
file stats: | LOC: | 824 |  | Methods: | 23
| NCLOC: | 428 |  | 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: 331 $
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 | if (key == null) {
484 | 0 | synchronized (pageContext.getRequest()) {
485 | 0 | Object o = pageContext.getRequest().getAttribute(CACHE_TAG_COUNTER_KEY);
486 |  |
487 | 0 | if (o == null) {
488 | 0 | suffix = "1";
489 |  | } else {
490 | 0 | suffix = Integer.toString(Integer.parseInt((String) o) + 1);
491 |  | }
492 |  | }
493 |  |
494 | 0 | pageContext.getRequest().setAttribute(CACHE_TAG_COUNTER_KEY, suffix);
495 |  | }
496 |  |
497 |  | // Generate the actual cache key
498 | 0 | actualKey = admin.generateEntryKey(key, (HttpServletRequest) pageContext.getRequest(), scope, language, suffix);
499 |  |
500 |  | /*
501 |  | if
502 |  | - refresh is not set,
503 |  | - the cacheEntry itself does not need to be refreshed before 'time' and
504 |  | - the administrator has not had the cache entry's scope flushed
505 |  |
506 |  | send out the cached version!
507 |  | */
508 | 0 | try {
509 | 0 | if (refresh) {
510 |  | // Force a refresh
511 | 0 | content = (String) cache.getFromCache(actualKey, 0, cron);
512 |  | } else {
513 |  | // Use the specified refresh period
514 | 0 | content = (String) cache.getFromCache(actualKey, time, cron);
515 |  | }
516 |  |
517 | 0 | try {
518 | 0 | if (log.isDebugEnabled()) {
519 | 0 | log.debug("<cache>: Using cached entry : " + actualKey);
520 |  | }
521 |  |
522 |  | // Ensure that the cache returns the data correctly. Else re-evaluate the body
523 | 0 | if ((content != null)) {
524 | 0 | if (mode != SILENT_MODE) {
525 | 0 | pageContext.getOut().write(content);
526 |  | }
527 |  |
528 | 0 | returnCode = SKIP_BODY;
529 |  | }
530 |  | } catch (IOException e) {
531 | 0 | throw new JspTagException("IO Exception: " + e.getMessage());
532 |  | }
533 |  | } catch (NeedsRefreshException nre) {
534 | 0 | cancelUpdateRequired = true;
535 | 0 | content = (String) nre.getCacheContent();
536 |  | }
537 |  |
538 | 0 | if (returnCode == EVAL_BODY_BUFFERED) {
539 | 0 | if (log.isDebugEnabled()) {
540 | 0 | log.debug("<cache>: Cached content not used: New cache entry, cache stale or scope flushed : " + actualKey);
541 |  | }
542 |  | }
543 |  |
544 | 0 | return returnCode;
545 |  | }
546 |  |
547 |  | /**
548 |  | * Convert a SimpleDateFormat string to seconds
549 |  | * Acceptable format are :
550 |  | * <ul>
551 |  | * <li>0s (seconds)
552 |  | * <li>0m (minute)
553 |  | * <li>0h (hour)
554 |  | * <li>0d (day)
555 |  | * <li>0w (week)
556 |  | * </ul>
557 |  | * @param duration The simple date time to parse
558 |  | * @return The value in seconds
559 |  | */
560 | 0 | private int parseDuration(String duration) {
561 | 0 | int time = 0;
562 |  |
563 |  | //Detect if the factor is specified
564 | 0 | try {
565 | 0 | time = Integer.parseInt(duration);
566 |  | } catch (Exception ex) {
567 |  | //Extract number and ajust this number with the time factor
568 | 0 | for (int i = 0; i < duration.length(); i++) {
569 | 0 | if (!Character.isDigit(duration.charAt(i))) {
570 | 0 | time = Integer.parseInt(duration.substring(0, i));
571 |  |
572 | 0 | switch ((int) duration.charAt(i)) {
573 | 0 | case (int) 's':
574 | 0 | time *= SECOND;
575 | 0 | break;
576 | 0 | case (int) 'm':
577 | 0 | time *= MINUTE;
578 | 0 | break;
579 | 0 | case (int) 'h':
580 | 0 | time *= HOUR;
581 | 0 | break;
582 | 0 | case (int) 'd':
583 | 0 | time *= DAY;
584 | 0 | break;
585 | 0 | case (int) 'w':
586 | 0 | time *= WEEK;
587 | 0 | break;
588 | 0 | default:
589 |  | //no defined use as is
590 |  | }
591 |  |
592 | 0 | break;
593 |  | }
594 |  |
595 |  | // if
596 |  | }
597 |  |
598 |  | // for
599 |  | }
600 |  |
601 |  | // catch
602 | 0 | return time;
603 |  | }
604 |  |
605 |  | /**
606 |  | * Parse an ISO-8601 format date and return it's value in seconds
607 |  | *
608 |  | * @param duration The ISO-8601 date
609 |  | * @return The equivalent number of seconds
610 |  | * @throws Exception
611 |  | */
612 | 0 | private int parseISO_8601_Duration(String duration) throws Exception {
613 | 0 | int years = 0;
614 | 0 | int months = 0;
615 | 0 | int days = 0;
616 | 0 | int hours = 0;
617 | 0 | int mins = 0;
618 | 0 | int secs = 0;
619 |  |
620 |  | // If there is a negative sign, it must be first
621 |  | // If it is present, we will ignore it
622 | 0 | int index = duration.indexOf("-");
623 |  |
624 | 0 | if (index > 0) {
625 | 0 | throw new Exception("Invalid duration (- must be at the beginning)");
626 |  | }
627 |  |
628 |  | // First caracter must be P
629 | 0 | String workValue = duration.substring(index + 1);
630 |  |
631 | 0 | if (workValue.charAt(0) != 'P') {
632 | 0 | throw new Exception("Invalid duration (P must be at the beginning)");
633 |  | }
634 |  |
635 |  | // Must contain a value
636 | 0 | workValue = workValue.substring(1);
637 |  |
638 | 0 | if (workValue.length() == 0) {
639 | 0 | throw new Exception("Invalid duration (nothing specified)");
640 |  | }
641 |  |
642 |  | // Check if there is a T
643 | 0 | index = workValue.indexOf('T');
644 |  |
645 | 0 | String timeString = "";
646 |  |
647 | 0 | if (index > 0) {
648 | 0 | timeString = workValue.substring(index + 1);
649 |  |
650 |  | // Time cannot be empty
651 | 0 | if (timeString.equals("")) {
652 | 0 | throw new Exception("Invalid duration (T with no time)");
653 |  | }
654 |  |
655 | 0 | workValue = workValue.substring(0, index);
656 | 0 | } else if (index == 0) {
657 | 0 | timeString = workValue.substring(1);
658 | 0 | workValue = "";
659 |  | }
660 |  |
661 | 0 | if (!workValue.equals("")) {
662 | 0 | validateDateFormat(workValue);
663 |  |
664 | 0 | int yearIndex = workValue.indexOf('Y');
665 | 0 | int monthIndex = workValue.indexOf('M');
666 | 0 | int dayIndex = workValue.indexOf('D');
667 |  |
668 | 0 | if ((yearIndex != -1) && (monthIndex != -1) && (yearIndex > monthIndex)) {
669 | 0 | throw new Exception("Invalid duration (Date part not properly specified)");
670 |  | }
671 |  |
672 | 0 | if ((yearIndex != -1) && (dayIndex != -1) && (yearIndex > dayIndex)) {
673 | 0 | throw new Exception("Invalid duration (Date part not properly specified)");
674 |  | }
675 |  |
676 | 0 | if ((dayIndex != -1) && (monthIndex != -1) && (monthIndex > dayIndex)) {
677 | 0 | throw new Exception("Invalid duration (Date part not properly specified)");
678 |  | }
679 |  |
680 | 0 | if (yearIndex >= 0) {
681 | 0 | years = (new Integer(workValue.substring(0, yearIndex))).intValue();
682 |  | }
683 |  |
684 | 0 | if (monthIndex >= 0) {
685 | 0 | months = (new Integer(workValue.substring(yearIndex + 1, monthIndex))).intValue();
686 |  | }
687 |  |
688 | 0 | if (dayIndex >= 0) {
689 | 0 | if (monthIndex >= 0) {
690 | 0 | days = (new Integer(workValue.substring(monthIndex + 1, dayIndex))).intValue();
691 |  | } else {
692 | 0 | if (yearIndex >= 0) {
693 | 0 | days = (new Integer(workValue.substring(yearIndex + 1, dayIndex))).intValue();
694 |  | } else {
695 | 0 | days = (new Integer(workValue.substring(0, dayIndex))).intValue();
696 |  | }
697 |  | }
698 |  | }
699 |  | }
700 |  |
701 | 0 | if (!timeString.equals("")) {
702 | 0 | validateHourFormat(timeString);
703 |  |
704 | 0 | int hourIndex = timeString.indexOf('H');
705 | 0 | int minuteIndex = timeString.indexOf('M');
706 | 0 | int secondIndex = timeString.indexOf('S');
707 |  |
708 | 0 | if ((hourIndex != -1) && (minuteIndex != -1) && (hourIndex > minuteIndex)) {
709 | 0 | throw new Exception("Invalid duration (Time part not properly specified)");
710 |  | }
711 |  |
712 | 0 | if ((hourIndex != -1) && (secondIndex != -1) && (hourIndex > secondIndex)) {
713 | 0 | throw new Exception("Invalid duration (Time part not properly specified)");
714 |  | }
715 |  |
716 | 0 | if ((secondIndex != -1) && (minuteIndex != -1) && (minuteIndex > secondIndex)) {
717 | 0 | throw new Exception("Invalid duration (Time part not properly specified)");
718 |  | }
719 |  |
720 | 0 | if (hourIndex >= 0) {
721 | 0 | hours = (new Integer(timeString.substring(0, hourIndex))).intValue();
722 |  | }
723 |  |
724 | 0 | if (minuteIndex >= 0) {
725 | 0 | mins = (new Integer(timeString.substring(hourIndex + 1, minuteIndex))).intValue();
726 |  | }
727 |  |
728 | 0 | if (secondIndex >= 0) {
729 | 0 | if (timeString.length() != (secondIndex + 1)) {
730 | 0 | throw new Exception("Invalid duration (Time part not properly specified)");
731 |  | }
732 |  |
733 | 0 | if (minuteIndex >= 0) {
734 | 0 | timeString = timeString.substring(minuteIndex + 1, timeString.length() - 1);
735 |  | } else {
736 | 0 | if (hourIndex >= 0) {
737 | 0 | timeString = timeString.substring(hourIndex + 1, timeString.length() - 1);
738 |  | } else {
739 | 0 | timeString = timeString.substring(0, timeString.length() - 1);
740 |  | }
741 |  | }
742 |  |
743 | 0 | if (timeString.indexOf('.') == (timeString.length() - 1)) {
744 | 0 | throw new Exception("Invalid duration (Time part not properly specified)");
745 |  | }
746 |  |
747 | 0 | secs = (new Double(timeString)).intValue();
748 |  | }
749 |  | }
750 |  |
751 |  | // Compute Value
752 | 0 | return secs + (mins * MINUTE) + (hours * HOUR) + (days * DAY) + (months * MONTH) + (years * YEAR);
753 |  | }
754 |  |
755 |  | /**
756 |  | * Validate the basic date format
757 |  | *
758 |  | * @param basicDate The string to validate
759 |  | * @throws Exception
760 |  | */
761 | 0 | private void validateDateFormat(String basicDate) throws Exception {
762 | 0 | int yearCounter = 0;
763 | 0 | int monthCounter = 0;
764 | 0 | int dayCounter = 0;
765 |  |
766 | 0 | for (int counter = 0; counter < basicDate.length(); counter++) {
767 |  | // Check if there's any other caracters than Y, M, D and numbers
768 | 0 | if (!Character.isDigit(basicDate.charAt(counter)) && (basicDate.charAt(counter) != 'Y') && (basicDate.charAt(counter) != 'M') && (basicDate.charAt(counter) != 'D')) {
769 | 0 | throw new Exception("Invalid duration (Date part not properly specified)");
770 |  | }
771 |  |
772 |  | // Check if the allowed caracters are present more than 1 time
773 | 0 | if (basicDate.charAt(counter) == 'Y') {
774 | 0 | yearCounter++;
775 |  | }
776 |  |
777 | 0 | if (basicDate.charAt(counter) == 'M') {
778 | 0 | monthCounter++;
779 |  | }
780 |  |
781 | 0 | if (basicDate.charAt(counter) == 'D') {
782 | 0 | dayCounter++;
783 |  | }
784 |  | }
785 |  |
786 | 0 | if ((yearCounter > 1) || (monthCounter > 1) || (dayCounter > 1)) {
787 | 0 | throw new Exception("Invalid duration (Date part not properly specified)");
788 |  | }
789 |  | }
790 |  |
791 |  | /**
792 |  | * Validate the basic hour format
793 |  | *
794 |  | * @param basicHour The string to validate
795 |  | * @throws Exception
796 |  | */
797 | 0 | private void validateHourFormat(String basicHour) throws Exception {
798 | 0 | int minuteCounter = 0;
799 | 0 | int secondCounter = 0;
800 | 0 | int hourCounter = 0;
801 |  |
802 | 0 | for (int counter = 0; counter < basicHour.length(); counter++) {
803 | 0 | if (!Character.isDigit(basicHour.charAt(counter)) && (basicHour.charAt(counter) != 'H') && (basicHour.charAt(counter) != 'M') && (basicHour.charAt(counter) != 'S') && (basicHour.charAt(counter) != '.')) {
804 | 0 | throw new Exception("Invalid duration (Time part not properly specified)");
805 |  | }
806 |  |
807 | 0 | if (basicHour.charAt(counter) == 'H') {
808 | 0 | hourCounter++;
809 |  | }
810 |  |
811 | 0 | if (basicHour.charAt(counter) == 'M') {
812 | 0 | minuteCounter++;
813 |  | }
814 |  |
815 | 0 | if (basicHour.charAt(counter) == 'S') {
816 | 0 | secondCounter++;
817 |  | }
818 |  | }
819 |  |
820 | 0 | if ((hourCounter > 1) || (minuteCounter > 1) || (secondCounter > 1)) {
821 | 0 | throw new Exception("Invalid duration (Time part not properly specified)");
822 |  | }
823 |  | }
824 |  | }