Clover coverage report -
Coverage timestamp: Sa Jul 7 2007 09:11:40 CEST
file stats: LOC: 824   Methods: 23
NCLOC: 428   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
CacheTag.java 0% 0% 0% 0%
coverage
 1    /*
 2    * Copyright (c) 2002-2003 by OpenSymphony
 3    * All rights reserved.
 4    */
 5    package com.opensymphony.oscache.web.tag;
 6   
 7    import com.opensymphony.oscache.base.Cache;
 8    import com.opensymphony.oscache.base.NeedsRefreshException;
 9    import com.opensymphony.oscache.util.StringUtil;
 10    import com.opensymphony.oscache.web.ServletCacheAdministrator;
 11    import com.opensymphony.oscache.web.WebEntryRefreshPolicy;
 12   
 13    import org.apache.commons.logging.Log;
 14    import org.apache.commons.logging.LogFactory;
 15   
 16    import java.io.IOException;
 17   
 18    import java.util.ArrayList;
 19    import java.util.List;
 20   
 21    import javax.servlet.http.HttpServletRequest;
 22    import javax.servlet.jsp.JspTagException;
 23    import javax.servlet.jsp.PageContext;
 24    import javax.servlet.jsp.tagext.BodyTagSupport;
 25    import javax.servlet.jsp.tagext.TryCatchFinally;
 26   
 27    /**
 28    * CacheTag is a tag that allows for server-side caching of post-processed JSP content.<p>
 29    *
 30    * It also gives great programatic control over refreshing, flushing and updating the cache.<p>
 31    *
 32    * Usage Example:
 33    * <pre><code>
 34    * &lt;%@ taglib uri="oscache" prefix="cache" %&gt;
 35    * &lt;cache:cache key="mycache"
 36    * scope="application"
 37    * refresh="false"
 38    * time="30">
 39    * jsp content here... refreshed every 30 seconds
 40    * &lt;/cache:cache&gt;
 41    * </code></pre>
 42    *
 43    * @author <a href="mailto:mike@atlassian.com">Mike Cannon-Brookes</a>
 44    * @author <a href="mailto:tgochenour@peregrine.com">Todd Gochenour</a>
 45    * @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
 46    * @author <a href="mailto:abergevin@pyxis-tech.com">Alain Bergevin</a>
 47    * @version $Revision: 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    }