/* * RHQ Management Platform * Copyright 2010-2011, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.coregui.client.components.trigger; import java.util.Date; import java.util.EnumSet; import java.util.LinkedHashMap; import java.util.TreeSet; import com.smartgwt.client.widgets.HTMLFlow; import com.smartgwt.client.widgets.Img; import com.smartgwt.client.widgets.events.ClickEvent; import com.smartgwt.client.widgets.events.ClickHandler; import com.smartgwt.client.widgets.form.DynamicForm; import com.smartgwt.client.widgets.form.fields.DateTimeItem; import com.smartgwt.client.widgets.form.fields.FormItem; import com.smartgwt.client.widgets.form.fields.FormItemIcon; import com.smartgwt.client.widgets.form.fields.RadioGroupItem; import com.smartgwt.client.widgets.form.fields.SpacerItem; import com.smartgwt.client.widgets.form.fields.TextItem; import com.smartgwt.client.widgets.form.fields.events.ChangedEvent; import com.smartgwt.client.widgets.form.fields.events.ChangedHandler; import com.smartgwt.client.widgets.form.fields.events.IconClickEvent; import com.smartgwt.client.widgets.form.fields.events.IconClickHandler; import com.smartgwt.client.widgets.tab.Tab; import com.smartgwt.client.widgets.tab.TabSet; import org.rhq.core.domain.common.JobTrigger; import org.rhq.coregui.client.CoreGUI; import org.rhq.coregui.client.components.form.DurationItem; import org.rhq.coregui.client.components.form.EnhancedDynamicForm; import org.rhq.coregui.client.components.form.TimeUnit; import org.rhq.coregui.client.components.form.UnitType; import org.rhq.coregui.client.util.enhanced.EnhancedVLayout; import org.rhq.coregui.client.util.message.Message; /** * A SmartGWT widget that provides the ability to create a new {@link JobTrigger job trigger}, or view or edit an * existing job trigger. * * @author Ian Springer */ public class JobTriggerEditor extends EnhancedVLayout { // Field Names private static final String FIELD_MODE = "mode"; private static final String FIELD_REPEAT_INTERVAL = "repeatInterval"; private static final String FIELD_REPEAT_DURATION = "repeatDuration"; private static final String FIELD_END_TIME = "endTime"; private static final String FIELD_START_TYPE = "startType"; private static final String FIELD_START_TIME = "startTime"; private static final String FIELD_START_DELAY = "startDelay"; private static final String FIELD_RECURRENCE_TYPE = "recurrenceType"; private static final String FIELD_CRON_EXPRESSION = "cronExpression"; private JobTrigger jobTrigger; private boolean isReadOnly; private EnhancedVLayout calendarModeLayout; private EnhancedVLayout cronModeLayout; private EnhancedDynamicForm modeForm; private EnhancedDynamicForm calendarTypeForm; private DynamicForm laterForm; private DynamicForm repeatForm; private DynamicForm cronForm; // These flags allow us to determine the trigger type. private boolean isStartLater; private boolean isRecurring; private boolean isRepeatDuration; private boolean isEndTime; private boolean isStartDelay; private boolean isStartTime; public JobTriggerEditor(boolean isReadOnly) { super(); this.isReadOnly = isReadOnly; } /** * View or edit an existing job trigger. * * @param jobTrigger */ public JobTriggerEditor(JobTrigger jobTrigger) { super(); this.jobTrigger = jobTrigger; this.isReadOnly = true; } public void setJobTrigger(JobTrigger jobTrigger) { this.jobTrigger = jobTrigger; this.isReadOnly = true; if (isDrawn()) { refresh(); } } private void refresh() { if (this.jobTrigger != null) { FormItem modeItem = this.modeForm.getItem(FIELD_MODE); if (this.jobTrigger.getRecurrenceType() == JobTrigger.RecurrenceType.CRON_EXPRESSION) { modeItem.setValue("cron"); changeMode("cron"); this.cronForm.setValue("cronExpression", this.jobTrigger.getCronExpression()); } else { modeItem.setValue("calendar"); this.calendarTypeForm.hide(); changeMode("calendar"); FormItem startTypeItem = this.laterForm.getItem(FIELD_START_TYPE); startTypeItem.setValue("on"); DurationItem startDelayItem = (DurationItem) this.laterForm.getItem(FIELD_START_DELAY); FormItem startTimeItem = this.laterForm.getField(FIELD_START_TIME); changeStartType("on", startDelayItem, startTimeItem); startTimeItem.setValue(this.jobTrigger.getStartDate()); FormItem calendarTypeItem = this.calendarTypeForm.getField("calendarType"); if (this.jobTrigger.getRecurrenceType() == JobTrigger.RecurrenceType.REPEAT_INTERVAL) { calendarTypeItem.setValue("laterAndRepeat"); changeCalendarType("laterAndRepeat"); DurationItem repeatIntervalItem = (DurationItem) this.repeatForm.getItem(FIELD_REPEAT_INTERVAL); repeatIntervalItem.setAndFormatValue(this.jobTrigger.getRepeatInterval()); FormItem endTimeItem = this.repeatForm.getField(FIELD_END_TIME); DurationItem repeatDurationItem = (DurationItem) this.repeatForm.getItem(FIELD_REPEAT_DURATION); FormItem recurrenceTypeItem = this.repeatForm.getField(FIELD_RECURRENCE_TYPE); if (this.jobTrigger.getRepeatCount() != null) { recurrenceTypeItem.setValue("for"); changeRecurrenceType("for", endTimeItem, repeatDurationItem); repeatDurationItem.setValue(this.jobTrigger.getRepeatCount(), UnitType.ITERATIONS); } else if (this.jobTrigger.getEndDate() != null) { recurrenceTypeItem.setValue("until"); changeRecurrenceType("until", endTimeItem, repeatDurationItem); endTimeItem.setValue(this.jobTrigger.getEndDate()); } else { recurrenceTypeItem.setValue("indefinitely"); changeRecurrenceType("indefinitely", endTimeItem, repeatDurationItem); } } else { calendarTypeItem.setValue("later"); changeCalendarType("later"); } } } } @Override protected void onDraw() { super.onDraw(); this.modeForm = new EnhancedDynamicForm(this.isReadOnly); this.modeForm.setNumCols(3); this.modeForm.setColWidths("140", "220", "*"); RadioGroupItem modeItem = new RadioGroupItem(FIELD_MODE, MSG.widget_jobTriggerEditor_field_mode()); LinkedHashMap<String, String> modeValueMap = new LinkedHashMap<String, String>(); modeValueMap.put("calendar", MSG.widget_jobTriggerEditor_value_calendar()); modeValueMap.put("cron", MSG.widget_jobTriggerEditor_value_cronExpression()); modeItem.setValueMap(modeValueMap); modeItem.setVertical(false); modeItem.setShowTitle(true); modeItem.setValue("calendar"); this.modeForm.setFields(modeItem); addMember(this.modeForm); this.calendarModeLayout = new EnhancedVLayout(); this.calendarTypeForm = new EnhancedDynamicForm(this.isReadOnly); RadioGroupItem calendarTypeItem = new RadioGroupItem("calendarType"); calendarTypeItem.setWidth(440); calendarTypeItem.setShowTitle(false); LinkedHashMap<String, String> calendarTypeValueMap = new LinkedHashMap<String, String>(); calendarTypeValueMap.put("now", MSG.widget_jobTriggerEditor_value_now()); calendarTypeValueMap.put("nowAndRepeat", MSG.widget_jobTriggerEditor_value_nowAndRepeat()); calendarTypeValueMap.put("later", MSG.widget_jobTriggerEditor_value_later()); calendarTypeValueMap.put("laterAndRepeat", MSG.widget_jobTriggerEditor_value_laterAndRepeat()); calendarTypeItem.setValueMap(calendarTypeValueMap); calendarTypeItem.setVertical(false); calendarTypeItem.setValue("now"); this.calendarTypeForm.setFields(calendarTypeItem); this.calendarModeLayout.addMember(this.calendarTypeForm); addMember(this.calendarModeLayout); this.cronModeLayout = new EnhancedVLayout(); this.cronModeLayout.setVisible(false); this.cronForm = new DynamicForm(); TextItem cronExpressionItem = new TextItem(FIELD_CRON_EXPRESSION, MSG.widget_jobTriggerEditor_field_cronExpression()); cronExpressionItem.setRequired(true); cronExpressionItem.setWidth(340); this.cronForm.setFields(cronExpressionItem); this.cronModeLayout.addMember(this.cronForm); final TabSet cronHelpTabSet = new TabSet(); cronHelpTabSet.setWidth100(); cronHelpTabSet.setHeight(200); Img closeIcon = new Img("[SKIN]/headerIcons/close.png", 16, 16); closeIcon.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { cronHelpTabSet.hide(); } }); cronHelpTabSet.setTabBarControls(closeIcon); Tab formatTab = new Tab(MSG.widget_jobTriggerEditor_tab_format()); HTMLFlow formatPane = new HTMLFlow(); formatPane.setWidth100(); formatPane .setContents("<p>A cron expression is a string comprised of 6 or 7 fields separated by white space. Fields can contain any of the\n" + "allowed values, along with various combinations of the allowed special characters for that field. The fields are as\n" + "follows:</p>\n" + "<table cellpadding=\"3\" cellspacing=\"1\">\n" + " <tbody>\n" + "\n" + " <tr>\n" + " <th>Field Name</th>\n" + " <th>Mandatory</th>\n" + " <th>Allowed Values</th>\n" + " <th>Allowed Special Characters</th>\n" + " </tr>\n" + " <tr>\n" + "\n" + " <td>Seconds</td>\n" + " <td>YES</td>\n" + "\n" + " <td>0-59</td>\n" + " <td>, - * /</td>\n" + " </tr>\n" + " <tr>\n" + "\n" + " <td>Minutes</td>\n" + " <td>YES</td>\n" + " <td>0-59</td>\n" + "\n" + " <td>, - * /</td>\n" + " </tr>\n" + " <tr>\n" + "\n" + " <td>Hours</td>\n" + " <td>YES</td>\n" + " <td>0-23</td>\n" + " <td>, - * /</td>\n" + "\n" + " </tr>\n" + " <tr>\n" + "\n" + " <td>Day of month</td>\n" + " <td>YES</td>\n" + " <td>1-31</td>\n" + " <td>, - * ? / L W<br clear=\"all\" />\n" + " </td>\n" + " </tr>\n" + " <tr>\n" + "\n" + " <td>Month</td>\n" + " <td>YES</td>\n" + " <td>1-12 or JAN-DEC</td>\n" + " <td>, - * /</td>\n" + " </tr>\n" + " <tr>\n" + "\n" + " <td>Day of week</td>\n" + "\n" + " <td>YES</td>\n" + " <td>1-7 or SUN-SAT</td>\n" + " <td>, - * ? / L #</td>\n" + " </tr>\n" + " <tr>\n" + "\n" + " <td>Year</td>\n" + " <td>NO</td>\n" + "\n" + " <td>empty, 1970-2099</td>\n" + " <td>, - * /</td>\n" + " </tr>\n" + " </tbody>\n" + "\n" + "</table>\n" + "<p>So cron expressions can be as simple as this: <tt>0 * * ? * *</tt> to run every minute on the minute<br />\n" + "or more complex, like this: <tt>0/5 14,18,3-39,52 * ? JAN,MAR,SEP MON-FRI 2002-2015</tt></p>\n" + "\n" + "<h2><a name=\"CronTriggersTutorial-Specialcharacters\"></a>Special Characters</h2>\n" + "\n" + "<ul>\n" + " <li><tt><b>*</b></tt> (<em>\"all values\"</em>) - used to select all values within a field. For example, \"*\"\n" + " in the minute field means <em>\"every minute\"</em>.</li>\n" + "\n" + "</ul>\n" + "\n" + "\n" + "<ul>\n" + " <li><tt><b>?</b></tt> (<em>\"no specific value\"</em>) - useful when you need to specify something in one of the\n" + " two fields in which the character is allowed, but not the other. For example, if I want my trigger to fire on a\n" + " particular day of the month (say, the 10th), but don't care what day of the week that happens to be, I would put\n" + " \"10\" in the day-of-month field, and \"?\" in the day-of-week field. See the examples below for clarification.</li>\n" + "\n" + "</ul>\n" + "\n" + "\n" + "<ul>\n" + " <li><tt><b>-</b></tt> - used to specify ranges. For example, \"10-12\" in the hour field means <em>\"the\n" + " hours 10, 11 and 12\"</em>.</li>\n" + "\n" + "</ul>\n" + "\n" + "\n" + "<ul>\n" + " <li><tt><b>,</b></tt> - used to specify additional values. For example, \"MON,WED,FRI\" in the day-of-week\n" + " field means <em>\"the days Monday, Wednesday, and Friday\"</em>.</li>\n" + "\n" + "</ul>\n" + "\n" + "\n" + "<ul>\n" + "\n" + " <li><tt><b>/</b></tt> - used to specify increments. For example, \"0/15\" in the seconds field means <em>\"the\n" + " seconds 0, 15, 30, and 45\"</em>. And \"5/15\" in the seconds field means <em>\"the seconds 5, 20, 35, and 50\"</em>. You can\n" + " also specify '/' after the '<b>' character - in this case '</b>' is equivalent to having '0' before the '/'. '1/3'\n" + " in the day-of-month field means <em>\"fire every 3 days starting on the first day of the month\"</em>.</li>\n" + "\n" + "</ul>\n" + "\n" + "<ul>\n" + " <li><tt><b>L</b></tt> (<em>\"last\"</em>) - has different meaning in each of the two fields in which it is\n" + " allowed. For example, the value \"L\" in the day-of-month field means <em>\"the last day of the month\"</em> - day\n" + " 31 for January, day 28 for February on non-leap years. If used in the day-of-week field by itself, it simply means\n" + " \"7\" or \"SAT\". But if used in the day-of-week field after another value, it means <em>\"the last xxx day of the\n" + " month\"</em> - for example \"6L\" means <em>\"the last friday of the month\"</em>. When using the 'L' option, it is\n" + " important not to specify lists, or ranges of values, as you'll get confusing results.</li>\n" + "\n" + "</ul>\n" + "\n" + "\n" + "<ul>\n" + " <li><tt><b>W</b></tt> (<em>\"weekday\"</em>) - used to specify the weekday (Monday-Friday) nearest the given day.\n" + " As an example, if you were to specify \"15W\" as the value for the day-of-month field, the meaning is: <em>\"the\n" + " nearest weekday to the 15th of the month\"</em>. So if the 15th is a Saturday, the trigger will fire on Friday the 14th.\n" + " If the 15th is a Sunday, the trigger will fire on Monday the 16th. If the 15th is a Tuesday, then it will fire on\n" + " Tuesday the 15th. However if you specify \"1W\" as the value for day-of-month, and the 1st is a Saturday, the trigger\n" + " will fire on Monday the 3rd, as it will not 'jump' over the boundary of a month's days. The 'W' character can only\n" + " be specified when the day-of-month is a single day, not a range or list of days.\n" + " <div class=\"tip\">\n" + " The 'L' and 'W' characters can also be combined in the day-of-month field to yield 'LW', which\n" + " translates to <em>\"last weekday of the month\"</em>.\n" + " </div>\n" + "\n" + " </li>\n" + "\n" + " <li><tt><b>#</b></tt> - used to specify \"the nth\" XXX day of the month. For example, the value of \"6#3\"\n" + " in the day-of-week field means <em>\"the third Friday of the month\"</em> (day 6 = Friday and \"#3\" = the 3rd one in\n" + " the month). Other examples: \"2#1\" = the first Monday of the month and \"4#5\" = the fifth Wednesday of the month. Note\n" + " that if you specify \"#5\" and there is not 5 of the given day-of-week in the month, then no firing will occur that\n" + " month.\n" + " <div class=\"tip\">\n" + " The legal characters and the names of months and days of the week are not case sensitive. <tt>MON</tt>\n" + " is the same as <tt>mon</tt>.\n" + " </div>\n" + "\n" + " </li>\n" + "</ul>" + "<h2><a name=\"CronTriggersTutorial-Notes\"></a>Notes</h2>\n" + "\n" + "<ul>\n" + " <li>Support for specifying both a day-of-week and a day-of-month value is not complete (you must currently use\n" + " the '?' character in one of these fields).</li>\n" + " <li>Be careful when setting fire times between mid-night and 1:00 AM - \"daylight savings\" can cause a skip or a\n" + " repeat depending on whether the time moves back or jumps forward.</li>\n" + "\n" + "</ul>"); formatTab.setPane(formatPane); Tab examplesTab = new Tab(MSG.widget_jobTriggerEditor_tab_examples()); HTMLFlow examplesPane = new HTMLFlow(); examplesPane.setWidth100(); examplesPane.setContents("<table cellpadding=\"3\" cellspacing=\"1\">\n" + " <tbody>\n" + " <tr>\n" + " <th>Expression</th>\n" + "\n" + " <th>Meaning</th>\n" + " </tr>\n" + " <tr>\n" + " <td><tt>0 0 12 * * ?</tt></td>\n" + "\n" + " <td>Fire at 12pm (noon) every day</td>\n" + " </tr>\n" + " <tr>\n" + "\n" + " <td><tt>0 15 10 ? * *</tt></td>\n" + " <td>Fire at 10:15am every day</td>\n" + " </tr>\n" + " <tr>\n" + " <td><tt>0 15 10 * * ?</tt></td>\n" + "\n" + " <td>Fire at 10:15am every day</td>\n" + "\n" + " </tr>\n" + " <tr>\n" + " <td><tt>0 15 10 * * ? *</tt></td>\n" + " <td>Fire at 10:15am every day</td>\n" + " </tr>\n" + " <tr>\n" + " <td><tt>0 15 10 * * ? 2005</tt></td>\n" + "\n" + " <td>Fire at 10:15am every day during the year 2005</td>\n" + " </tr>\n" + " <tr>\n" + " <td><tt>0 * 14 * * ?</tt></td>\n" + " <td>Fire every minute starting at 2pm and ending at 2:59pm, every day</td>\n" + " </tr>\n" + " <tr>\n" + "\n" + " <td><tt>0 0/5 14 * * ?</tt></td>\n" + "\n" + " <td>Fire every 5 minutes starting at 2pm and ending at 2:55pm, every day</td>\n" + " </tr>\n" + " <tr>\n" + " <td><tt>0 0/5 14,18 * * ?</tt></td>\n" + " <td>Fire every 5 minutes starting at 2pm and ending at 2:55pm, AND fire every 5\n" + " minutes starting at 6pm and ending at 6:55pm, every day</td>\n" + "\n" + " </tr>\n" + " <tr>\n" + " <td><tt>0 0-5 14 * * ?</tt></td>\n" + "\n" + " <td>Fire every minute starting at 2pm and ending at 2:05pm, every day</td>\n" + " </tr>\n" + " <tr>\n" + " <td><tt>0 10,44 14 ? 3 WED</tt></td>\n" + "\n" + " <td>Fire at 2:10pm and at 2:44pm every Wednesday in the month of March.</td>\n" + " </tr>\n" + " <tr>\n" + " <td><tt>0 15 10 ? * MON-FRI</tt></td>\n" + "\n" + " <td>Fire at 10:15am every Monday, Tuesday, Wednesday, Thursday and Friday</td>\n" + " </tr>\n" + " <tr>\n" + "\n" + " <td><tt>0 15 10 15 * ?</tt></td>\n" + " <td>Fire at 10:15am on the 15th day of every month</td>\n" + " </tr>\n" + " <tr>\n" + " <td><tt>0 15 10 L * ?</tt></td>\n" + "\n" + " <td>Fire at 10:15am on the last day of every month</td>\n" + "\n" + " </tr>\n" + " <tr>\n" + " <td><tt>0 15 10 ? * 6L</tt></td>\n" + " <td>Fire at 10:15am on the last Friday of every month</td>\n" + " </tr>\n" + " <tr>\n" + " <td><tt>0 15 10 ? * 6L</tt></td>\n" + "\n" + " <td>Fire at 10:15am on the last Friday of every month</td>\n" + " </tr>\n" + " <tr>\n" + " <td><tt>0 15 10 ? * 6L 2002-2005</tt></td>\n" + " <td>Fire at 10:15am on every last friday of every month during the years 2002,\n" + " 2003, 2004 and 2005</td>\n" + " </tr>\n" + " <tr>\n" + "\n" + " <td><tt>0 15 10 ? * 6#3</tt></td>\n" + "\n" + " <td>Fire at 10:15am on the third Friday of every month</td>\n" + " </tr>\n" + " <tr>\n" + " <td><tt>0 0 12 1/5 * ?</tt></td>\n" + " <td>Fire at 12pm (noon) every 5 days every month, starting on the first day of the\n" + " month.</td>\n" + "\n" + " </tr>\n" + " <tr>\n" + " <td><tt>0 11 11 11 11 ?</tt></td>\n" + "\n" + " <td>Fire every November 11th at 11:11am.</td>\n" + " </tr>\n" + " </tbody>\n" + "</table>"); examplesTab.setPane(examplesPane); cronHelpTabSet.addTab(formatTab); cronHelpTabSet.addTab(examplesTab); cronHelpTabSet.setVisible(false); FormItemIcon helpIcon = new FormItemIcon(); helpIcon.setSrc("[SKIN]/actions/help.png"); cronExpressionItem.setIcons(helpIcon); cronExpressionItem.addIconClickHandler(new IconClickHandler() { public void onIconClick(IconClickEvent event) { cronHelpTabSet.show(); } }); this.cronModeLayout.addMember(cronHelpTabSet); addMember(this.cronModeLayout); modeItem.addChangedHandler(new ChangedHandler() { public void onChanged(ChangedEvent event) { String mode = (String) event.getValue(); changeMode(mode); } }); this.laterForm = createLaterForm(); this.calendarModeLayout.addMember(this.laterForm); this.repeatForm = createRepeatForm(); this.calendarModeLayout.addMember(this.repeatForm); calendarTypeItem.addChangedHandler(new ChangedHandler() { public void onChanged(ChangedEvent event) { String calendarType = (String) event.getValue(); changeCalendarType(calendarType); } }); refresh(); } private void changeCalendarType(String calendarType) { if (calendarType.equals("now")) { this.isStartLater = false; this.isRecurring = false; } else if (calendarType.equals("nowAndRepeat")) { this.isStartLater = false; this.isRecurring = true; FormItem repeatIntervalItem = repeatForm.getItem(FIELD_REPEAT_INTERVAL); repeatIntervalItem.setTitle(MSG.widget_jobTriggerEditor_field_repeatInterval_now()); repeatIntervalItem.redraw(); } else if (calendarType.equals("later")) { this.isStartLater = true; this.isRecurring = false; } else { // value.equals("laterAndRepeat") this.isStartLater = true; this.isRecurring = true; FormItem repeatIntervalItem = repeatForm.getItem(FIELD_REPEAT_INTERVAL); repeatIntervalItem.setTitle(MSG.widget_jobTriggerEditor_field_repeatInterval_later()); repeatIntervalItem.redraw(); } if (isStartLater) { laterForm.show(); } else { laterForm.hide(); } if (isRecurring) { repeatForm.show(); } else { repeatForm.hide(); } } private void changeMode(String mode) { if (mode.equals("calendar")) { calendarModeLayout.show(); cronModeLayout.hide(); } else { // cron expression mode calendarModeLayout.hide(); cronModeLayout.show(); } } private DynamicForm createRepeatForm() { final EnhancedDynamicForm repeatForm = new EnhancedDynamicForm(this.isReadOnly); repeatForm.setNumCols(6); repeatForm.setColWidths(140, 130, 130, 130, 130); TreeSet<TimeUnit> supportedUnits = new TreeSet<TimeUnit>(); supportedUnits.add(TimeUnit.SECONDS); supportedUnits.add(TimeUnit.MINUTES); supportedUnits.add(TimeUnit.HOURS); supportedUnits.add(TimeUnit.DAYS); supportedUnits.add(TimeUnit.WEEKS); supportedUnits.add(TimeUnit.MONTHS); supportedUnits.add(TimeUnit.YEARS); DurationItem repeatIntervalItem = new DurationItem(FIELD_REPEAT_INTERVAL, MSG.widget_jobTriggerEditor_field_repeatInterval_now(), supportedUnits, false, this.isReadOnly); repeatIntervalItem.setRequired(true); repeatIntervalItem.setContextualHelp(MSG.widget_jobTriggerEditor_fieldHelp_repeatInterval()); RadioGroupItem recurrenceTypeItem = new RadioGroupItem(FIELD_RECURRENCE_TYPE); recurrenceTypeItem.setRequired(true); recurrenceTypeItem.setShowTitle(false); LinkedHashMap<String, String> recurrenceTypeValueMap = new LinkedHashMap<String, String>(); recurrenceTypeValueMap.put("for", MSG.widget_jobTriggerEditor_value_for()); recurrenceTypeValueMap.put("until", MSG.widget_jobTriggerEditor_value_until()); recurrenceTypeValueMap.put("indefinitely", MSG.widget_jobTriggerEditor_value_indefinitely()); recurrenceTypeItem.setValueMap(recurrenceTypeValueMap); supportedUnits = new TreeSet<TimeUnit>(); supportedUnits.add(TimeUnit.SECONDS); supportedUnits.add(TimeUnit.MINUTES); supportedUnits.add(TimeUnit.HOURS); supportedUnits.add(TimeUnit.DAYS); supportedUnits.add(TimeUnit.WEEKS); supportedUnits.add(TimeUnit.MONTHS); supportedUnits.add(TimeUnit.YEARS); final DurationItem repeatDurationItem = new DurationItem(FIELD_REPEAT_DURATION, null, supportedUnits, true, this.isReadOnly); repeatDurationItem.setShowTitle(false); repeatDurationItem.setVisible(false); repeatDurationItem.setContextualHelp(MSG.widget_jobTriggerEditor_fieldHelp_repeatDuration()); final DateTimeItem endTimeItem = createDateTimeItem(FIELD_END_TIME); endTimeItem.setShowTitle(false); endTimeItem.setVisible(false); SpacerItem spacerItem = new SpacerItem(); recurrenceTypeItem.addChangedHandler(new ChangedHandler() { public void onChanged(ChangedEvent event) { String recurrenceType = (String) event.getValue(); changeRecurrenceType(recurrenceType, endTimeItem, repeatDurationItem); } }); repeatForm.setFields(repeatIntervalItem, recurrenceTypeItem, repeatDurationItem, endTimeItem, spacerItem); repeatForm.setVisible(false); return repeatForm; } private void changeRecurrenceType(String recurrenceType, FormItem endTimeItem, DurationItem repeatDurationItem) { if (recurrenceType.equals("for")) { this.isEndTime = false; this.isRepeatDuration = true; } else if (recurrenceType.equals("until")) { this.isEndTime = true; this.isRepeatDuration = false; } else { // indefinite this.isEndTime = false; this.isRepeatDuration = false; } endTimeItem.setRequired(this.isEndTime); if (this.isEndTime) { endTimeItem.show(); } else { endTimeItem.hide(); } repeatDurationItem.setRequired(this.isRepeatDuration); if (this.isRepeatDuration) { repeatDurationItem.show(); } else { repeatDurationItem.hide(); } } private DynamicForm createLaterForm() { final EnhancedDynamicForm laterForm = new EnhancedDynamicForm(this.isReadOnly); laterForm.setNumCols(4); laterForm.setColWidths(140, 130, 130); RadioGroupItem startTypeItem = new RadioGroupItem(FIELD_START_TYPE, MSG.widget_jobTriggerEditor_field_startType()); LinkedHashMap<String, String> startTypeValueMap = new LinkedHashMap<String, String>(); startTypeValueMap.put("on", MSG.widget_jobTriggerEditor_value_on()); startTypeValueMap.put("in", MSG.widget_jobTriggerEditor_value_in()); startTypeItem.setValueMap(startTypeValueMap); startTypeItem.setShowTitle(true); final DateTimeItem startTimeItem = createDateTimeItem(FIELD_START_TIME); TreeSet<TimeUnit> supportedUnits = new TreeSet<TimeUnit>(); supportedUnits.add(TimeUnit.SECONDS); supportedUnits.add(TimeUnit.MINUTES); supportedUnits.add(TimeUnit.HOURS); supportedUnits.add(TimeUnit.DAYS); supportedUnits.add(TimeUnit.WEEKS); supportedUnits.add(TimeUnit.MONTHS); supportedUnits.add(TimeUnit.YEARS); final DurationItem startDelayItem = new DurationItem(FIELD_START_DELAY, null, supportedUnits, false, this.isReadOnly); startDelayItem.setShowTitle(false); startDelayItem.setVisible(false); startDelayItem.setContextualHelp(MSG.widget_jobTriggerEditor_fieldHelp_startDelay()); SpacerItem spacerItem = new SpacerItem(); startTypeItem.addChangedHandler(new ChangedHandler() { public void onChanged(ChangedEvent event) { String startType = (String) event.getValue(); changeStartType(startType, startDelayItem, startTimeItem); } }); laterForm.setFields(startTypeItem, startTimeItem, startDelayItem, spacerItem); laterForm.setVisible(false); return laterForm; } private void changeStartType(String startType, DurationItem startDelayItem, FormItem startTimeItem) { if (startType.equals("on")) { this.isStartDelay = false; this.isStartTime = true; } else { // value.equals("in") this.isStartDelay = true; this.isStartTime = false; } startDelayItem.setRequired(this.isStartDelay); if (this.isStartDelay) { startDelayItem.show(); } else { startDelayItem.hide(); } startTimeItem.setRequired(this.isStartTime); if (this.isStartTime) { startTimeItem.show(); } else { startTimeItem.hide(); } } /** * Returns the start time, if one was specified, otherwise returns null to indicate an immediate start. * * @return the start time, if one was specified, otherwise returns null to indicate an immediate start */ public Date getStartTime() { if (!this.isStartLater) { return null; } Date startTime; if (this.isStartDelay) { // start delay - compute start time DurationItem startDelayItem = (DurationItem) this.laterForm.getItem(FIELD_START_DELAY); long delay = startDelayItem.getValueAsLong() * 1000; long startTimestamp = System.currentTimeMillis() + delay; startTime = new Date(startTimestamp); } else { // start time DateTimeItem startTimeItem = (DateTimeItem) this.laterForm.getField(FIELD_START_TIME); startTime = startTimeItem.getValueAsDate(); } return startTime; } public Long getRepeatInterval() { Long intervalMillis; if (this.isRecurring) { DurationItem repeatInterval = (DurationItem) this.repeatForm.getItem(FIELD_REPEAT_INTERVAL); intervalMillis = repeatInterval.getValueAsLong() * 1000; } else { intervalMillis = null; } return intervalMillis; } public Integer getRepeatCount() { Integer repetitions; if (this.isRecurring) { if (this.isRepeatDuration) { DurationItem repeatDurationItem = (DurationItem) this.repeatForm.getItem(FIELD_REPEAT_DURATION); if (repeatDurationItem.getUnitType() == UnitType.ITERATIONS) { // n repetitions repetitions = repeatDurationItem.getValueAsInteger(); } else { // n units of time - compute end time repetitions = null; } } else { repetitions = null; } } else { repetitions = null; } return repetitions; } public Date getEndTime() { Date endTime; if (this.isRecurring) { if (this.isRepeatDuration) { DurationItem repeatDurationItem = (DurationItem) this.repeatForm.getItem(FIELD_REPEAT_DURATION); if (repeatDurationItem.getUnitType() == UnitType.ITERATIONS) { // n repetitions endTime = null; } else { // n units of time - compute end time long delay = repeatDurationItem.getValueAsLong() * 1000; long endTimestamp = System.currentTimeMillis() + delay; endTime = new Date(endTimestamp); } } else if (this.isEndTime) { DateTimeItem endTimeItem = (DateTimeItem) this.repeatForm.getField(FIELD_END_TIME); endTime = endTimeItem.getValueAsDate(); } else { endTime = null; } } else { endTime = null; } return endTime; } public String getCronExpression() { return this.cronForm.getValueAsString(FIELD_CRON_EXPRESSION); } public boolean validate() { // TODO (ips, 01/12/11): Use custom validators to do the startTime / endTime validation instead of the code // below; that way field-specific validation errors will be used, rather than messages in // the message bar. boolean isValid = true; Date currentTime = new Date(); Date startTime = getStartTime(); Date endTime = getEndTime(); if (this.isStartLater) { isValid = isValid && this.laterForm.validate(); if (startTime != null) { if (startTime.before(currentTime)) { Message message = new Message(MSG.widget_jobTriggerEditor_message_startTimeMustBeInFuture(), Message.Severity.Error, EnumSet.of(Message.Option.Transient)); CoreGUI.getMessageCenter().notify(message); isValid = false; } if (this.isRecurring && endTime != null) { if (endTime.before(startTime)) { Message message = new Message( MSG.widget_jobTriggerEditor_message_endTimeMustBeAfterStartTime(), Message.Severity.Error, EnumSet.of(Message.Option.Transient)); CoreGUI.getMessageCenter().notify(message); isValid = false; } } } } if (this.isRecurring) { isValid = isValid && this.repeatForm.validate(); if (endTime != null) { if (endTime.before(currentTime)) { Message message = new Message(MSG.widget_jobTriggerEditor_message_endTimeMustBeAfterStartTime(), Message.Severity.Error, EnumSet.of(Message.Option.Transient)); CoreGUI.getMessageCenter().notify(message); isValid = false; } } } return isValid; } private static DateTimeItem createDateTimeItem(String name) { final DateTimeItem dateTimeItem = new DateTimeItem(name); dateTimeItem.setEnforceDate(true); dateTimeItem.setCenturyThreshold(99); dateTimeItem.setShowTitle(false); dateTimeItem.setVisible(false); dateTimeItem.setStartDate(new Date()); dateTimeItem.setUseMask(true); dateTimeItem.setShowHint(true); return dateTimeItem; } }