1   package com.atlassian.core.cron;
2   
3   import com.atlassian.core.util.DateUtils;
4   import org.apache.commons.lang.StringUtils;
5   
6   import java.util.HashMap;
7   import java.util.Map;
8   
9   /**
10   * Represents the form state for the CronEditorWebComponent.
11   */
12  public class CronEditorBean
13  {
14  
15      // Defines the modes
16      public static final String DAILY_SPEC_MODE = "daily";
17      public static final String DAYS_OF_WEEK_SPEC_MODE = "daysOfWeek";
18      public static final String DAYS_OF_MONTH_SPEC_MODE = "daysOfMonth";
19      public static final String ADVANCED_MODE = "advanced";
20      public static final String DOT = ".";
21  
22      // Defines the params that get submitted via the cron editor form
23      private static final String CRON_STRING = "cronString";
24      private static final String DAILY_WEEKLY_MONTHLY = "dailyWeeklyMonthly";
25      private static final String RUN_ONCE_MINS = "runOnceMins";
26      private static final String RUN_ONCE_HOURS = "runOnceHours";
27      private static final String RUN_ONCE_MERIDIAN = "runOnceMeridian";
28      private static final String RUN_FROM_HOURS = "runFromHours";
29      private static final String RUN_FROM_MERIDIAN = "runFromMeridian";
30      private static final String RUN_TO_HOURS = "runToHours";
31      private static final String RUN_TO_MERIDIAN = "runToMeridian";
32      private static final String WEEKDAY = "weekday";
33      private static final String DAY = "day";
34      private static final String WEEK = "week";
35      private static final String DAYS_OF_MONTH_OPT = "daysOfMonthOpt";
36      private static final String MONTH_DAY = "monthDay";
37      private static final String INTERVAL = "interval";
38      private static final String DAY_OF_WEEK_OF_MONTH = "dayOfWeekOfMonth";
39  
40      private Map params;
41      private String cronString;
42      private String mode;
43      private boolean dayOfWeekOfMonth;
44      private String dayOfMonth;
45      private String minutes;
46      private String hoursRunOnce;
47      private String hoursRunOnceMeridian;
48      private String hoursFrom;
49      private String hoursFromMeridian;
50      private String hoursTo;
51      private String hoursToMeridian;
52      private String specifiedDaysOfWeek;
53      private String dayInMonthOrdinal;
54      private String incrementInMinutes;
55      private String seconds;
56  
57      public CronEditorBean()
58      {
59          this.params = new HashMap();
60      }
61  
62      /**
63       * Initialises to the state defined by the given params, which are identified
64       * by the presence of paramPrefix on the key.
65       *
66       * @param paramPrefix the prefix used on each of the params.
67       * @param params      state parameters to use.
68       */
69      public CronEditorBean(String paramPrefix, /*<String, String[]>*/ Map params)
70      {
71          this.params = params;
72          this.cronString = getParam(paramPrefix, CRON_STRING);
73          this.mode = getParam(paramPrefix, DAILY_WEEKLY_MONTHLY);
74          // note that we don't expect seconds from the UI
75          this.minutes = getParam(paramPrefix, RUN_ONCE_MINS);
76          this.hoursRunOnce = getParam(paramPrefix, RUN_ONCE_HOURS);
77          this.hoursRunOnceMeridian = getParam(paramPrefix, RUN_ONCE_MERIDIAN);
78          this.hoursFrom = getParam(paramPrefix, RUN_FROM_HOURS);
79          this.hoursFromMeridian = getParam(paramPrefix, RUN_FROM_MERIDIAN);
80          this.hoursTo = getParam(paramPrefix, RUN_TO_HOURS);
81          this.hoursToMeridian = getParam(paramPrefix, RUN_TO_MERIDIAN);
82          String[] daysOfWeek = (String[]) params.get(paramPrefix + DOT + WEEKDAY);
83          if (DAYS_OF_MONTH_SPEC_MODE.equals(mode))
84          {
85              this.specifiedDaysOfWeek = getParam(paramPrefix, DAY);
86              this.dayInMonthOrdinal = getParam(paramPrefix, WEEK);
87              // Find the sub-mode
88              String dayOfWeekOfMonthString = getParam(paramPrefix, DAYS_OF_MONTH_OPT);
89              this.dayOfWeekOfMonth = DAY_OF_WEEK_OF_MONTH.equals(dayOfWeekOfMonthString);
90          }
91          else if (DAYS_OF_WEEK_SPEC_MODE.equals(mode))
92          {
93              this.specifiedDaysOfWeek = StringUtils.join(daysOfWeek, ',');
94          }
95  
96          this.dayOfMonth = getParam(paramPrefix, MONTH_DAY);
97          this.incrementInMinutes = getParam(paramPrefix, INTERVAL);
98      }
99  
100     /**
101      * Used to validate the hours to see that the from hour is before the to hour, if specified.
102      * Note: to allow the range to run "all-day" (eg. from 12AM to 12AM), we need to also allow the from and to times to
103      * be equal.
104      *
105      * @return true if from is before to or if they have not been specified.
106      */
107     public boolean isRangeHoursValid()
108     {
109         if (hoursFrom != null && hoursFromMeridian != null && hoursTo != null && hoursToMeridian != null && incrementInMinutes != null && !incrementInMinutes.equals("0"))
110         {
111             try
112             {
113                 int hoursFromInt = Integer.parseInt(hoursFrom);
114                 int hoursToInt = Integer.parseInt(hoursTo);
115                 return DateUtils.get24HourTime(hoursFromMeridian, hoursFromInt) <= DateUtils.get24HourTime(hoursToMeridian, hoursToInt);
116             }
117             catch (NumberFormatException e)
118             {
119                 // Something is freaky with the hours, hmmmm....
120                 return false;
121             }
122         }
123         return true;
124     }
125 
126     /**
127      * Indicates that a range of time is being specified.
128      *
129      * @return true only if the form state indicates a from and to time.
130      */
131     public boolean isRange()
132     {
133         return !incrementInMinutes.equals("0") && (isDailyMode() || isDayPerWeekMode());
134     }
135 
136     /**
137      * Indicates that the range of time specified spans across the full 24 hours in a day (which is indicated by the
138      * computed hoursFrom in 24 hour time being equal to the computed hoursTo in 24 hour time).
139      *
140      * @return true only if we have specified a range, and that range spans across the 24 hours.
141      */
142     public boolean is24HourRange()
143     {
144         boolean result = false;
145         if (isRange())
146         {
147             int hoursFromInt = Integer.parseInt(hoursFrom);
148             int hoursToInt = Integer.parseInt(hoursTo);
149             result = DateUtils.get24HourTime(hoursFromMeridian, hoursFromInt) == DateUtils.get24HourTime(hoursToMeridian, hoursToInt);
150         }
151         return result;
152     }
153 
154     /**
155      * Returns the cron string that the object was constructed with. This method does not guarantee that the returned
156      * cron string is valid according the the {@link org.quartz.CronTrigger}.
157      *
158      * @return unmodified cronString
159      */
160     public String getCronString()
161     {
162         return cronString;
163     }
164 
165     /**
166      * Will return true if the passed in cron string is not valid for the editor.
167      *
168      * @return true if the cron string can not be handled, false otherwise.
169      */
170     public boolean isAdvancedMode()
171     {
172         return ADVANCED_MODE.equals(mode);
173     }
174 
175     /**
176      * Will return true if the editors daily mode can handle the provided cron string.
177      *
178      * @return if we're in daily mode.
179      */
180     public boolean isDailyMode()
181     {
182         return DAILY_SPEC_MODE.equals(mode);
183     }
184 
185     /**
186      * Will return true if the editors day per week mode can handle the provided cron string.
187      *
188      * @return true if {@link #mode} is equal to {@link #DAYS_OF_WEEK_SPEC_MODE}, false otherwise
189      */
190     public boolean isDayPerWeekMode()
191     {
192         return DAYS_OF_WEEK_SPEC_MODE.equals(mode);
193     }
194 
195     /**
196      * Will return true if the editors days per month mode can handle the provided cron string.
197      *
198      * @return true if {@link #mode} is equal to {@link #DAYS_OF_MONTH_SPEC_MODE}, false otherwise
199      */
200     public boolean isDaysPerMonthMode()
201     {
202         return DAYS_OF_MONTH_SPEC_MODE.equals(mode);
203     }
204 
205     /**
206      * Returns true if {@link #isDaysPerMonthMode()} is true and the sub-mode of day of week per month has been
207      * selected.
208      */
209     public boolean isDayOfWeekOfMonth()
210     {
211         return dayOfWeekOfMonth;
212     }
213 
214     /**
215      * Gets the day of month field specified in the cron string. Should be between 1-31 or L.
216      */
217     public String getDayOfMonth()
218     {
219         return dayOfMonth;
220     }
221 
222     /**
223      * Gets the minutes specified. Should be between 0-59.
224      */
225     public String getMinutes()
226     {
227         return minutes;
228     }
229 
230     /**
231      * Returns the lower bound of the hour range if this entry has a range.
232      *
233      * This end of the range is inclusive - e.g. if HoursFrom is 3PM and HoursTo is 5PM, the duration of the range
234      * is 2 hours.
235      *
236      * @return the lower bound or null if this is not a range hour entry.
237      */
238     public String getHoursFrom()
239     {
240         return hoursFrom;
241     }
242 
243     /**
244      * Returns the upper bound of the hour range if this entry has a range.
245      *
246      * This end of the range is exclusive - e.g. if HoursFrom is 3PM and HoursTo is 5PM, the duration of the range
247      * is 2 hours.
248      *
249      * @return the upper bound or null if this is not a range hour entry.
250      */
251     public String getHoursTo()
252     {
253         return hoursTo;
254     }
255 
256     /**
257      * Returns the meridian indicator @{link #AM} or @{link #PM} for the lower bound of a range entry.
258      *
259      * @return the meridian belonging to the lower bound hour or null if this is not a range entry.
260      */
261     public String getHoursFromMeridian()
262     {
263         return hoursFromMeridian;
264     }
265 
266     /**
267      * Returns the meridian indicator @{link #AM} or @{link #PM} for the upper bound of a range entry.
268      *
269      * @return the meridian belonging to the upper bound hour or null if this is not a range entry.
270      */
271     public String getHoursToMeridian()
272     {
273         return hoursToMeridian;
274     }
275 
276     /**
277      * Returns the single hour value for this entry if it is a run once entry.
278      *
279      * @return the hour value or null if this is not a run once hour entry.
280      */
281     public String getHoursRunOnce()
282     {
283         return hoursRunOnce;
284     }
285 
286     /**
287      * Returns the meridian indicator @{link #AM} or @{link #PM} for the entry if it is a run once entry.
288      *
289      * @return the meridian belonging single hour value or null if this is not a run once entry.
290      */
291     public String getHoursRunOnceMeridian()
292     {
293         return hoursRunOnceMeridian;
294     }
295 
296     /**
297      * Returns true if the passed in day has been specified, false otherwise.
298      *
299      * @param dayStr a string representing a day (e.g. 1-7).
300      * @return true if the day has been specified, false otherwise.
301      */
302     public boolean isDaySpecified(String dayStr)
303     {
304         return specifiedDaysOfWeek != null && StringUtils.contains(specifiedDaysOfWeek, dayStr);
305     }
306 
307     /**
308      * Returns a number that represents the first, second third etc. day of the week in a month.
309      *
310      * @return the ordinal or null if this entry doesn't specify it.
311      */
312     public String getDayInMonthOrdinal()
313     {
314         return dayInMonthOrdinal;
315     }
316 
317     /**
318      * Returns all the days that have been specified in a comma separated list.
319      *
320      * @return string representing days (e.g. "1,2,3").
321      */
322     public String getSpecifiedDaysPerWeek()
323     {
324         return specifiedDaysOfWeek;
325     }
326 
327     /**
328      * Used to determine the total increment in minutes derived by the hour and minutes fields' increment parts.
329      * An increment of 0 indicates no repetition based on the hours or minutes fields and will happen if the
330      * repetition is once a day.
331      *
332      * @return a minute increment or "0"
333      */
334     public String getIncrementInMinutes()
335     {
336         return incrementInMinutes;
337     }
338 
339     /**
340      * The full cron string that may have been specified. This can come from the advanced tab where the user has
341      * specified their own cron string. This does not validate the cron string.
342      *
343      * @param cronString a valid cron string.
344      */
345     public void setCronString(String cronString)
346     {
347         this.cronString = cronString;
348     }
349 
350     public void setMode(String mode)
351     {
352         this.mode = mode;
353     }
354 
355     public void setDayOfWeekOfMonth(boolean dayOfWeekOfMonth)
356     {
357         this.dayOfWeekOfMonth = dayOfWeekOfMonth;
358     }
359 
360     public void setDayOfMonth(String dayOfMonth)
361     {
362         this.dayOfMonth = dayOfMonth;
363     }
364 
365     public void setMinutes(String minutes)
366     {
367         this.minutes = minutes;
368     }
369 
370     public void setHoursFrom(String hoursFrom)
371     {
372         this.hoursFrom = hoursFrom;
373     }
374 
375     public void setHoursTo(String hoursTo)
376     {
377         this.hoursTo = hoursTo;
378     }
379 
380     public void setHoursFromMeridian(String hoursFromMeridian)
381     {
382         this.hoursFromMeridian = hoursFromMeridian;
383     }
384 
385     public void setHoursToMeridian(String hoursToMeridian)
386     {
387         this.hoursToMeridian = hoursToMeridian;
388     }
389 
390     public void setHoursRunOnce(String hoursRunOnce)
391     {
392         this.hoursRunOnce = hoursRunOnce;
393     }
394 
395     public void setHoursRunOnceMeridian(String hoursRunOnceMeridian)
396     {
397         this.hoursRunOnceMeridian = hoursRunOnceMeridian;
398     }
399 
400     public void setSpecifiedDaysOfWeek(String specifiedDaysOfWeek)
401     {
402         this.specifiedDaysOfWeek = specifiedDaysOfWeek;
403     }
404 
405     public void setDayInMonthOrdinal(String dayInMonthOrdinal)
406     {
407         this.dayInMonthOrdinal = dayInMonthOrdinal;
408     }
409 
410     /**
411      * Set the interval of repetition. "0" indicates there is no repetition.
412      *
413      * @param incrementInMinutes the interval or "0"
414      */
415     public void setIncrementInMinutes(String incrementInMinutes)
416     {
417         this.incrementInMinutes = incrementInMinutes;
418     }
419 
420     /**
421      * Looks up a single String value with the given prefixed key.
422      *
423      * @param paramPrefix prefix the parameter must have
424      * @param key         key value to use for lookup.
425      * @return the first String or null.
426      */
427     private String getParam(String paramPrefix, String key)
428     {
429         String[] paramArr = (String[]) params.get(paramPrefix + DOT + key);
430         if (paramArr != null && paramArr.length == 1)
431         {
432             return paramArr[0];
433         }
434         return null;
435     }
436 
437     public void setSeconds(String seconds)
438     {
439         this.seconds = seconds;
440     }
441 
442     public String getSeconds()
443     {
444         return seconds;
445     }
446 }