001: /*
002: * $Id: RecurrenceRule.java,v 1.5 2003/12/16 07:09:11 jonesde Exp $
003: *
004: * Copyright (c) 2001, 2002 The Open For Business Project - www.ofbiz.org
005: *
006: * Permission is hereby granted, free of charge, to any person obtaining a
007: * copy of this software and associated documentation files (the "Software"),
008: * to deal in the Software without restriction, including without limitation
009: * the rights to use, copy, modify, merge, publish, distribute, sublicense,
010: * and/or sell copies of the Software, and to permit persons to whom the
011: * Software is furnished to do so, subject to the following conditions:
012: *
013: * The above copyright notice and this permission notice shall be included
014: * in all copies or substantial portions of the Software.
015: *
016: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
017: * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
018: * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
019: * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
020: * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
021: * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
022: * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
023: *
024: */
025: package org.ofbiz.service.calendar;
026:
027: import java.util.Arrays;
028: import java.util.Calendar;
029: import java.util.Date;
030: import java.util.Iterator;
031: import java.util.List;
032:
033: import org.ofbiz.base.util.Debug;
034: import org.ofbiz.base.util.StringUtil;
035: import org.ofbiz.base.util.UtilMisc;
036: import org.ofbiz.entity.GenericDelegator;
037: import org.ofbiz.entity.GenericEntityException;
038: import org.ofbiz.entity.GenericValue;
039:
040: /**
041: * Recurrence Rule Object
042: *
043: * @author <a href="mailto:jaz@ofbiz.org">Andy Zeneski</a>
044: * @version $Revision: 1.5 $
045: * @since 2.0
046: */
047: public class RecurrenceRule {
048:
049: public static final String module = RecurrenceRule.class.getName();
050:
051: // **********************
052: // * byXXX constants
053: // **********************
054: public static final int MIN_SEC = 0;
055: public static final int MAX_SEC = 59;
056: public static final int MIN_MIN = 0;
057: public static final int MAX_MIN = 59;
058: public static final int MIN_HR = 0;
059: public static final int MAX_HR = 23;
060: public static final int MIN_MTH_DAY = -31;
061: public static final int MAX_MTH_DAY = 31;
062: public static final int MIN_YEAR_DAY = -366;
063: public static final int MAX_YEAR_DAY = 366;
064: public static final int MIN_WEEK_NO = -53;
065: public static final int MAX_WEEK_NO = 53;
066: public static final int MIN_MTH = 1;
067: public static final int MAX_MTH = 12;
068:
069: // **********************
070: // * Frequency constants
071: // **********************
072: /** Frequency SECONDLY */
073: public static final int SECONDLY = 1;
074:
075: /** Frequency MINUTELY */
076: public static final int MINUTELY = 2;
077:
078: /** Frequency HOURLY */
079: public static final int HOURLY = 3;
080:
081: /** Frequency DAILY */
082: public static final int DAILY = 4;
083:
084: /** Frequency WEEKLY */
085: public static final int WEEKLY = 5;
086:
087: /** Frequency MONTHLY */
088: public static final int MONTHLY = 6;
089:
090: /** Frequency YEARLY */
091: public static final int YEARLY = 7;
092:
093: // **********************
094: // * GenericValue object
095: // **********************
096: protected GenericValue rule;
097:
098: // **********************
099: // * Parsed byXXX lists
100: // **********************
101: protected List bySecondList;
102: protected List byMinuteList;
103: protected List byHourList;
104: protected List byDayList;
105: protected List byMonthDayList;
106: protected List byYearDayList;
107: protected List byWeekNoList;
108: protected List byMonthList;
109: protected List bySetPosList;
110:
111: /**
112: * Creates a new RecurrenceRule object from a RecurrenceInfo entity.
113: *@param rule GenericValue object defining this rule.
114: */
115: public RecurrenceRule(GenericValue rule)
116: throws RecurrenceRuleException {
117: this .rule = rule;
118: if (!rule.getEntityName().equals("RecurrenceRule"))
119: throw new RecurrenceRuleException(
120: "Invalid RecurrenceRule Value object.");
121: init();
122: }
123:
124: /**
125: * Initializes the rules for this RecurrenceInfo object.
126: *@throws RecurrenceRuleException
127: */
128: public void init() throws RecurrenceRuleException {
129: // Check the validity of the rule
130: String freq = rule.getString("frequency");
131:
132: if (!checkFreq(freq))
133: throw new RecurrenceRuleException(
134: "Recurrence FREQUENCY is a required parameter.");
135: if (rule.getLong("intervalNumber").longValue() < 1)
136: throw new RecurrenceRuleException(
137: "Recurrence INTERVAL must be a positive integer.");
138:
139: // Initialize the byXXX lists
140: bySecondList = StringUtil.split(rule.getString("bySecondList"),
141: ",");
142: byMinuteList = StringUtil.split(rule.getString("byMinuteList"),
143: ",");
144: byHourList = StringUtil
145: .split(rule.getString("byHourList"), ",");
146: byDayList = StringUtil.split(rule.getString("byDayList"), ",");
147: byMonthDayList = StringUtil.split(rule
148: .getString("byMonthDayList"), ",");
149: byYearDayList = StringUtil.split(rule
150: .getString("byYearDayList"), ",");
151: byWeekNoList = StringUtil.split(rule.getString("byWeekNoList"),
152: ",");
153: byMonthList = StringUtil.split(rule.getString("byMonthList"),
154: ",");
155: bySetPosList = StringUtil.split(rule.getString("bySetPosList"),
156: ",");
157: }
158:
159: /**
160: * Gets the current date/time.
161: *@return long Timestamp of the current date/time
162: */
163: private long now() {
164: return System.currentTimeMillis();
165: }
166:
167: // Checks for a valid frequency property.
168: private boolean checkFreq(String freq) {
169: if (freq == null)
170: return false;
171: if (freq.equalsIgnoreCase("SECONDLY"))
172: return true;
173: if (freq.equalsIgnoreCase("MINUTELY"))
174: return true;
175: if (freq.equalsIgnoreCase("HOURLY"))
176: return true;
177: if (freq.equalsIgnoreCase("DAILY"))
178: return true;
179: if (freq.equalsIgnoreCase("WEEKLY"))
180: return true;
181: if (freq.equalsIgnoreCase("MONTHLY"))
182: return true;
183: if (freq.equalsIgnoreCase("YEARLY"))
184: return true;
185: return false;
186: }
187:
188: /**
189: * Gets the end time of the recurrence rule or 0 if none.
190: *@return long The timestamp of the end time for this rule or 0 for none.
191: */
192: public long getEndTime() {
193: if (rule == null) {
194: Debug.logVerbose("Rule is null.", module);
195: return -1;
196: }
197: long time = 0;
198: java.sql.Timestamp stamp = null;
199:
200: stamp = rule.getTimestamp("untilDateTime");
201: Debug.logVerbose("Stamp value: " + stamp, module);
202:
203: if (stamp != null) {
204: long nanos = (long) stamp.getNanos();
205: time = stamp.getTime();
206: time += (nanos / 1000000);
207: }
208: Debug.logVerbose("Returning time: " + time, module);
209: return time;
210: }
211:
212: /**
213: * Get the number of times this recurrence will run (-1 until end time).
214: *@return long The number of time this recurrence will run.
215: */
216: public long getCount() {
217: if (rule.get("countNumber") != null)
218: return rule.getLong("countNumber").longValue();
219: return 0;
220: }
221:
222: /**
223: * Returns the frequency name of the recurrence.
224: *@return String The name of this frequency.
225: */
226: public String getFrequencyName() {
227: return rule.getString("frequency").toUpperCase();
228: }
229:
230: /**
231: * Returns the frequency of this recurrence.
232: *@return int The reference value for the frequency
233: */
234: public int getFrequency() {
235: String freq = rule.getString("frequency");
236:
237: if (freq == null)
238: return 0;
239: if (freq.equalsIgnoreCase("SECONDLY"))
240: return SECONDLY;
241: if (freq.equalsIgnoreCase("MINUTELY"))
242: return MINUTELY;
243: if (freq.equalsIgnoreCase("HOURLY"))
244: return HOURLY;
245: if (freq.equalsIgnoreCase("DAILY"))
246: return DAILY;
247: if (freq.equalsIgnoreCase("WEEKLY"))
248: return WEEKLY;
249: if (freq.equalsIgnoreCase("MONTHLY"))
250: return MONTHLY;
251: if (freq.equalsIgnoreCase("YEARLY"))
252: return YEARLY;
253: return 0;
254: }
255:
256: /**
257: * Returns the interval of the frequency.
258: *@return long Interval value
259: */
260: public long getInterval() {
261: if (rule.get("intervalNumber") == null)
262: return 1;
263: return rule.getLong("intervalNumber").longValue();
264: }
265:
266: /**
267: * Returns the interval of the frequency as an int.
268: *@return The interval of this frequency as an integer.
269: */
270: public int getIntervalInt() {
271: // if (Debug.verboseOn()) Debug.logVerbose("[RecurrenceInfo.getInterval] : " + getInterval(), module);
272: return (int) getInterval();
273: }
274:
275: /**
276: * Returns the next recurrence of this rule.
277: *@param startTime The time this recurrence first began.
278: *@param fromTime The time to base the next recurrence on.
279: *@param currentCount The total number of times the recurrence has run.
280: *@return long The next recurrence as a long.
281: */
282: public long next(long startTime, long fromTime, long currentCount) {
283: // Set up the values
284: if (startTime == 0)
285: startTime = RecurrenceUtil.now();
286: if (fromTime == 0)
287: fromTime = startTime;
288:
289: // Test the end time of the recurrence.
290: if (getEndTime() != 0 && getEndTime() <= RecurrenceUtil.now())
291: return 0;
292: Debug.logVerbose("Rule NOT expired by end time.", module);
293:
294: // Test the recurrence limit.
295: if (getCount() != -1 && currentCount >= getCount())
296: return 0;
297: Debug.logVerbose("Rule NOT expired by max count.", module);
298:
299: boolean isSeeking = true;
300: long nextRuntime = 0;
301: long seekTime = fromTime;
302: int loopProtection = 0;
303: int maxLoop = (10 * 10 * 10 * 10 * 10);
304:
305: while (isSeeking && loopProtection < maxLoop) {
306: Date nextRun = getNextFreq(startTime, seekTime);
307: seekTime = nextRun.getTime();
308: if (validByRule(nextRun)) {
309: isSeeking = false;
310: nextRuntime = nextRun.getTime();
311: }
312: loopProtection++;
313: }
314: return nextRuntime;
315: }
316:
317: /**
318: * Tests the date to see if it falls within the rules
319: *@param startDate date object to test
320: *@return True if the date is within the rules
321: */
322: public boolean isValid(Date startDate, Date date) {
323: return isValid(startDate.getTime(), date.getTime());
324: }
325:
326: /**
327: * Tests the date to see if it falls within the rules
328: *@param startTime date object to test
329: *@return True if the date is within the rules
330: */
331: public boolean isValid(long startTime, long dateTime) {
332: long testTime = startTime;
333:
334: if (testTime == dateTime)
335: return true;
336: while (testTime < dateTime) {
337: testTime = next(startTime, testTime, 1);
338: if (testTime == dateTime)
339: return true;
340: }
341: return false;
342: }
343:
344: /**
345: * Removes this rule from the persistant store.
346: *@throws RecurrenceRuleException
347: */
348: public void remove() throws RecurrenceRuleException {
349: try {
350: rule.remove();
351: } catch (GenericEntityException e) {
352: throw new RecurrenceRuleException(e.getMessage(), e);
353: }
354: }
355:
356: // Gets the next frequency/interval recurrence from specified time
357: private Date getNextFreq(long startTime, long fromTime) {
358: // Build a Calendar object
359: Calendar cal = Calendar.getInstance();
360:
361: cal.setTime(new Date(startTime));
362:
363: long nextStartTime = startTime;
364:
365: while (nextStartTime < fromTime) {
366: // if (Debug.verboseOn()) Debug.logVerbose("[RecurrenceInfo.getNextFreq] : Updating time - " + getFrequency(), module);
367: switch (getFrequency()) {
368: case SECONDLY:
369: cal.add(Calendar.SECOND, getIntervalInt());
370: break;
371:
372: case MINUTELY:
373: cal.add(Calendar.MINUTE, getIntervalInt());
374: break;
375:
376: case HOURLY:
377: cal.add(Calendar.HOUR, getIntervalInt());
378: break;
379:
380: case DAILY:
381: cal.add(Calendar.DAY_OF_MONTH, getIntervalInt());
382: break;
383:
384: case WEEKLY:
385: cal.add(Calendar.WEEK_OF_YEAR, getIntervalInt());
386: break;
387:
388: case MONTHLY:
389: cal.add(Calendar.MONTH, getIntervalInt());
390: break;
391:
392: case YEARLY:
393: cal.add(Calendar.YEAR, getIntervalInt());
394: break;
395:
396: default:
397: return null; // should never happen
398: }
399: nextStartTime = cal.getTime().getTime();
400: }
401: return new Date(nextStartTime);
402: }
403:
404: // Checks to see if a date is valid by the byXXX rules
405: private boolean validByRule(Date date) {
406: // Build a Calendar object
407: Calendar cal = Calendar.getInstance();
408:
409: cal.setTime(date);
410:
411: // Test each byXXX rule.
412: if (bySecondList != null && bySecondList.size() > 0) {
413: if (!bySecondList.contains(new Integer(cal
414: .get(Calendar.SECOND))))
415: return false;
416: }
417: if (byMinuteList != null && byMinuteList.size() > 0) {
418: if (!byMinuteList.contains(new Integer(cal
419: .get(Calendar.MINUTE))))
420: return false;
421: }
422: if (byHourList != null && byHourList.size() > 0) {
423: if (!byHourList
424: .contains(new Integer(cal.get(Calendar.HOUR))))
425: return false;
426: }
427: if (byDayList != null && byDayList.size() > 0) {
428: Iterator iter = byDayList.iterator();
429: boolean foundDay = false;
430:
431: while (iter.hasNext() && !foundDay) {
432: String dayRule = (String) iter.next();
433: String dayString = getDailyString(dayRule);
434:
435: if (Calendar.DAY_OF_WEEK == getCalendarDay(dayString)) {
436: if ((hasNumber(dayRule))
437: && (getFrequency() == MONTHLY || getFrequency() == YEARLY)) {
438: int modifier = getDailyNumber(dayRule);
439:
440: if (modifier == 0)
441: foundDay = true;
442:
443: if (getFrequency() == MONTHLY) {
444: // figure if we are the nth xDAY if this month
445: int currentPos = cal
446: .get(Calendar.WEEK_OF_MONTH);
447: int dayPosCalc = cal
448: .get(Calendar.DAY_OF_MONTH)
449: - ((currentPos - 1) * 7);
450:
451: if (dayPosCalc < 1)
452: currentPos--;
453: if (modifier > 0) {
454: if (currentPos == modifier) {
455: foundDay = true;
456: }
457: } else if (modifier < 0) {
458: int maxDay = cal
459: .getActualMaximum(Calendar.DAY_OF_MONTH);
460: int firstDay = dayPosCalc > 0 ? dayPosCalc
461: : dayPosCalc + 7;
462: int totalDay = ((maxDay - firstDay) / 7) + 1;
463: int this Diff = (currentPos - totalDay) - 1;
464:
465: if (this Diff == modifier) {
466: foundDay = true;
467: }
468: }
469: } else if (getFrequency() == YEARLY) {
470: // figure if we are the nth xDAY if this year
471: int currentPos = cal
472: .get(Calendar.WEEK_OF_YEAR);
473: int dayPosCalc = cal
474: .get(Calendar.DAY_OF_YEAR)
475: - ((currentPos - 1) * 7);
476:
477: if (dayPosCalc < 1) {
478: currentPos--;
479: }
480: if (modifier > 0) {
481: if (currentPos == modifier) {
482: foundDay = true;
483: }
484: } else if (modifier < 0) {
485: int maxDay = cal
486: .getActualMaximum(Calendar.DAY_OF_YEAR);
487: int firstDay = dayPosCalc > 0 ? dayPosCalc
488: : dayPosCalc + 7;
489: int totalDay = ((maxDay - firstDay) / 7) + 1;
490: int this Diff = (currentPos - totalDay) - 1;
491:
492: if (this Diff == modifier) {
493: foundDay = true;
494: }
495: }
496: }
497: } else {
498: // we are a DOW only rule
499: foundDay = true;
500: }
501: }
502: }
503: if (!foundDay) {
504: return false;
505: }
506: }
507: if (byMonthDayList != null && byMonthDayList.size() > 0) {
508: Iterator iter = byMonthDayList.iterator();
509: boolean foundDay = false;
510:
511: while (iter.hasNext() && !foundDay) {
512: int day = 0;
513: String dayStr = (String) iter.next();
514:
515: try {
516: day = Integer.parseInt(dayStr);
517: } catch (NumberFormatException nfe) {
518: Debug.logError(nfe, "Error parsing day string "
519: + dayStr + ": " + nfe.toString(), module);
520: }
521: int maxDay = cal
522: .getActualMaximum(Calendar.DAY_OF_MONTH);
523: int currentDay = cal.get(Calendar.DAY_OF_MONTH);
524:
525: if (day > 0 && day == currentDay) {
526: foundDay = true;
527: }
528: if (day < 0 && day == ((currentDay - maxDay) - 1)) {
529: foundDay = true;
530: }
531: }
532: if (!foundDay) {
533: return false;
534: }
535: }
536: if (byYearDayList != null && byYearDayList.size() > 0) {
537: Iterator iter = byYearDayList.iterator();
538: boolean foundDay = false;
539:
540: while (iter.hasNext() && !foundDay) {
541: int day = 0;
542: String dayStr = (String) iter.next();
543:
544: try {
545: day = Integer.parseInt(dayStr);
546: } catch (NumberFormatException nfe) {
547: Debug.logError(nfe, "Error parsing day string "
548: + dayStr + ": " + nfe.toString(), module);
549: }
550: int maxDay = cal.getActualMaximum(Calendar.DAY_OF_YEAR);
551: int currentDay = cal.get(Calendar.DAY_OF_YEAR);
552:
553: if (day > 0 && day == currentDay)
554: foundDay = true;
555: if (day < 0 && day == ((currentDay - maxDay) - 1))
556: foundDay = true;
557: }
558: if (!foundDay)
559: return false;
560: }
561: if (byWeekNoList != null && byWeekNoList.size() > 0) {
562: Iterator iter = byWeekNoList.iterator();
563: boolean foundWeek = false;
564:
565: while (iter.hasNext() && !foundWeek) {
566: int week = 0;
567: String weekStr = (String) iter.next();
568:
569: try {
570: week = Integer.parseInt(weekStr);
571: } catch (NumberFormatException nfe) {
572: Debug.logError(nfe, "Error parsing week string "
573: + weekStr + ": " + nfe.toString(), module);
574: }
575: int maxWeek = cal
576: .getActualMaximum(Calendar.WEEK_OF_YEAR);
577: int currentWeek = cal.get(Calendar.WEEK_OF_YEAR);
578:
579: if (week > 0 && week == currentWeek)
580: foundWeek = true;
581: if (week < 0 && week == ((currentWeek - maxWeek) - 1))
582: foundWeek = true;
583: }
584: if (!foundWeek)
585: return false;
586: }
587: if (byMonthList != null && byMonthList.size() > 0) {
588: Iterator iter = byMonthList.iterator();
589: boolean foundMonth = false;
590:
591: while (iter.hasNext() && !foundMonth) {
592: int month = 0;
593: String monthStr = (String) iter.next();
594:
595: try {
596: month = Integer.parseInt(monthStr);
597: } catch (NumberFormatException nfe) {
598: Debug.logError(nfe, "Error parsing month string "
599: + monthStr + ": " + nfe.toString(), module);
600: }
601: if (month == cal.get(Calendar.MONTH)) {
602: foundMonth = true;
603: }
604: }
605: if (!foundMonth)
606: return false;
607: }
608:
609: return true;
610: }
611:
612: // Tests a string for the contents of a number at the beginning
613: private boolean hasNumber(String str) {
614: String list[] = { "+", "-", "1", "2", "3", "4", "5", "6", "7",
615: "8", "9", "0" };
616: List numberList = Arrays.asList(list);
617: String firstChar = str.substring(0, 0);
618:
619: if (numberList.contains(firstChar))
620: return true;
621: return false;
622: }
623:
624: // Gets the numeric value of the number at the beginning of the string
625: private int getDailyNumber(String str) {
626: int number = 0;
627: StringBuffer numberBuf = new StringBuffer();
628:
629: for (int i = 0; i < str.length(); i++) {
630: String this Char = str.substring(i, i);
631:
632: if (hasNumber(this Char))
633: numberBuf.append(this Char);
634: }
635: String numberStr = numberBuf.toString();
636:
637: if (numberStr.length() > 0
638: && (numberStr.length() > 1 || (numberStr.charAt(0) != '+' && numberStr
639: .charAt(0) != '-'))) {
640: try {
641: number = Integer.parseInt(numberStr);
642: } catch (NumberFormatException nfe) {
643: Debug.logError(nfe,
644: "Error parsing daily number string "
645: + numberStr + ": " + nfe.toString(),
646: module);
647: }
648: }
649: return number;
650: }
651:
652: // Gets the string part of the combined number+string
653: private String getDailyString(String str) {
654: StringBuffer sBuf = new StringBuffer();
655:
656: for (int i = 0; i < str.length(); i++) {
657: String this Char = str.substring(i, i);
658:
659: if (!hasNumber(this Char)) {
660: sBuf.append(this Char);
661: }
662: }
663: return sBuf.toString();
664: }
665:
666: // Returns the Calendar day of the rule day string
667: private int getCalendarDay(String day) {
668: if (day.equalsIgnoreCase("MO"))
669: return Calendar.MONDAY;
670: if (day.equalsIgnoreCase("TU"))
671: return Calendar.TUESDAY;
672: if (day.equalsIgnoreCase("WE"))
673: return Calendar.WEDNESDAY;
674: if (day.equalsIgnoreCase("TH"))
675: return Calendar.THURSDAY;
676: if (day.equalsIgnoreCase("FR"))
677: return Calendar.FRIDAY;
678: if (day.equalsIgnoreCase("SA"))
679: return Calendar.SATURDAY;
680: if (day.equalsIgnoreCase("SU"))
681: return Calendar.SUNDAY;
682: return 0;
683: }
684:
685: public String primaryKey() {
686: return rule.getString("recurrenceRuleId");
687: }
688:
689: public static RecurrenceRule makeRule(GenericDelegator delegator,
690: int frequency, int interval, int count)
691: throws RecurrenceRuleException {
692: return makeRule(delegator, frequency, interval, count, 0);
693: }
694:
695: public static RecurrenceRule makeRule(GenericDelegator delegator,
696: int frequency, int interval, long endTime)
697: throws RecurrenceRuleException {
698: return makeRule(delegator, frequency, interval, -1, endTime);
699: }
700:
701: public static RecurrenceRule makeRule(GenericDelegator delegator,
702: int frequency, int interval, int count, long endTime)
703: throws RecurrenceRuleException {
704: String freq[] = { "", "SECONDLY", "MINUTELY", "HOURLY",
705: "DAILY", "WEEKLY", "MONTHLY", "YEARLY" };
706:
707: if (frequency < 1 || frequency > 7)
708: throw new RecurrenceRuleException("Invalid frequency");
709: if (interval < 0)
710: throw new RecurrenceRuleException("Invalid interval");
711:
712: String freqStr = freq[frequency];
713:
714: try {
715: String ruleId = delegator.getNextSeqId("RecurrenceRule")
716: .toString();
717: GenericValue value = delegator.makeValue("RecurrenceRule",
718: UtilMisc.toMap("recurrenceRuleId", ruleId));
719:
720: value.set("frequency", freqStr);
721: value.set("intervalNumber", new Long(interval));
722: value.set("countNumber", new Long(count));
723: if (endTime > 0) {
724: value.set("untilDateTime", new java.sql.Timestamp(
725: endTime));
726: }
727: delegator.create(value);
728: RecurrenceRule newRule = new RecurrenceRule(value);
729:
730: return newRule;
731: } catch (GenericEntityException ee) {
732: throw new RecurrenceRuleException(ee.getMessage(), ee);
733: } catch (RecurrenceRuleException re) {
734: throw re;
735: }
736: }
737: }
|