| | |
| | | * Copyright 2006-2008 Sun Microsystems, Inc. |
| | | */ |
| | | package org.opends.server.backends.task; |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.Arrays; |
| | | import java.util.Date; |
| | | import java.util.GregorianCalendar; |
| | | import org.opends.messages.Message; |
| | | |
| | | |
| | |
| | | import java.util.Iterator; |
| | | import java.util.List; |
| | | |
| | | import java.util.StringTokenizer; |
| | | import java.util.regex.Pattern; |
| | | import org.opends.server.core.DirectoryServer; |
| | | import org.opends.server.types.Attribute; |
| | | import org.opends.server.types.AttributeType; |
| | |
| | | import static org.opends.server.config.ConfigConstants.*; |
| | | import static org.opends.server.loggers.debug.DebugLogger.*; |
| | | import org.opends.server.loggers.debug.DebugTracer; |
| | | import org.opends.server.types.Attributes; |
| | | import org.opends.server.types.DebugLogLevel; |
| | | import org.opends.server.types.RDN; |
| | | |
| | | import static org.opends.messages.BackendMessages.*; |
| | | import static org.opends.server.util.StaticUtils.*; |
| | | import static org.opends.server.util.ServerConstants.*; |
| | | |
| | | |
| | | |
| | |
| | | // class. |
| | | private String taskClassName; |
| | | |
| | | // Task instance. |
| | | private Task task; |
| | | |
| | | // Task scheduler for this task. |
| | | private TaskScheduler taskScheduler; |
| | | |
| | | // Number of tokens in the task schedule tab. |
| | | private static final int TASKTAB_NUM_TOKENS = 5; |
| | | |
| | | /** |
| | | * Task tab fields. |
| | | */ |
| | | private static enum TaskTab {MINUTE, HOUR, DAY, MONTH, WEEKDAY}; |
| | | |
| | | // Exact match pattern. |
| | | private static final Pattern exactPattern = |
| | | Pattern.compile("\\d+"); |
| | | |
| | | // Range match pattern. |
| | | private static final Pattern rangePattern = |
| | | Pattern.compile("\\d+[-]\\d+"); |
| | | |
| | | // List match pattern. |
| | | private static final Pattern listPattern = |
| | | Pattern.compile("^(\\d+,)(.*)(\\d+)$"); |
| | | |
| | | // Boolean arrays holding task tab slots. |
| | | private boolean[] minutesArray; |
| | | private boolean[] hoursArray; |
| | | private boolean[] daysArray; |
| | | private boolean[] monthArray; |
| | | private boolean[] weekdayArray; |
| | | |
| | | /** |
| | | * Creates a new recurring task based on the information in the provided |
| | |
| | | public RecurringTask(TaskScheduler taskScheduler, Entry recurringTaskEntry) |
| | | throws DirectoryException |
| | | { |
| | | this.recurringTaskEntry = recurringTaskEntry; |
| | | this.taskScheduler = taskScheduler; |
| | | this.recurringTaskEntry = recurringTaskEntry; |
| | | this.recurringTaskEntryDN = recurringTaskEntry.getDN(); |
| | | |
| | | |
| | | // Get the recurring task ID from the entry. If there isn't one, then fail. |
| | | AttributeType attrType = DirectoryServer.getAttributeType( |
| | | ATTR_RECURRING_TASK_ID.toLowerCase()); |
| | |
| | | recurringTaskID = value.getStringValue(); |
| | | |
| | | |
| | | // FIXME -- Need to have some method of getting the scheduling information |
| | | // from the recurring task entry. |
| | | |
| | | |
| | | // Get the class name from the entry. If there isn't one, then fail. |
| | | // Get the schedule for this task. |
| | | attrType = DirectoryServer.getAttributeType( |
| | | ATTR_RECURRING_TASK_CLASS_NAME.toLowerCase()); |
| | | ATTR_RECURRING_TASK_SCHEDULE.toLowerCase()); |
| | | if (attrType == null) |
| | | { |
| | | attrType = DirectoryServer.getDefaultAttributeType( |
| | | ATTR_RECURRING_TASK_CLASS_NAME); |
| | | ATTR_RECURRING_TASK_SCHEDULE); |
| | | } |
| | | |
| | | attrList = recurringTaskEntry.getAttribute(attrType); |
| | | if ((attrList == null) || attrList.isEmpty()) |
| | | { |
| | | Message message = ERR_RECURRINGTASK_NO_CLASS_ATTRIBUTE.get( |
| | | ATTR_RECURRING_TASK_CLASS_NAME); |
| | | Message message = ERR_RECURRINGTASK_NO_SCHEDULE_ATTRIBUTE.get( |
| | | ATTR_RECURRING_TASK_SCHEDULE); |
| | | throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); |
| | | } |
| | | |
| | | if (attrList.size() > 0) |
| | | if (attrList.size() > 1) |
| | | { |
| | | Message message = ERR_RECURRINGTASK_MULTIPLE_CLASS_TYPES.get( |
| | | ATTR_RECURRING_TASK_CLASS_NAME); |
| | | Message message = ERR_RECURRINGTASK_MULTIPLE_SCHEDULE_TYPES.get( |
| | | ATTR_RECURRING_TASK_SCHEDULE); |
| | | throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); |
| | | } |
| | | |
| | | attr = attrList.get(0); |
| | | if (attr.isEmpty()) |
| | | { |
| | | Message message = |
| | | ERR_RECURRINGTASK_NO_CLASS_VALUES.get(ATTR_RECURRING_TASK_CLASS_NAME); |
| | | Message message = ERR_RECURRINGTASK_NO_SCHEDULE_VALUES.get( |
| | | ATTR_RECURRING_TASK_SCHEDULE); |
| | | throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); |
| | | } |
| | | |
| | |
| | | value = iterator.next(); |
| | | if (iterator.hasNext()) |
| | | { |
| | | Message message = ERR_RECURRINGTASK_MULTIPLE_CLASS_VALUES.get( |
| | | ATTR_RECURRING_TASK_CLASS_NAME); |
| | | Message message = ERR_RECURRINGTASK_MULTIPLE_SCHEDULE_VALUES.get( |
| | | ATTR_RECURRING_TASK_SCHEDULE); |
| | | throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); |
| | | } |
| | | |
| | | String taskScheduleTab = value.getStringValue(); |
| | | parseTaskTab(taskScheduleTab); |
| | | |
| | | // Get the class name from the entry. If there isn't one, then fail. |
| | | attrType = DirectoryServer.getAttributeType( |
| | | ATTR_TASK_CLASS.toLowerCase()); |
| | | if (attrType == null) |
| | | { |
| | | attrType = DirectoryServer.getDefaultAttributeType(ATTR_TASK_CLASS); |
| | | } |
| | | |
| | | attrList = recurringTaskEntry.getAttribute(attrType); |
| | | if ((attrList == null) || attrList.isEmpty()) |
| | | { |
| | | Message message = ERR_TASKSCHED_NO_CLASS_ATTRIBUTE.get( |
| | | ATTR_TASK_CLASS); |
| | | throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); |
| | | } |
| | | |
| | | if (attrList.size() > 1) |
| | | { |
| | | Message message = ERR_TASKSCHED_MULTIPLE_CLASS_TYPES.get( |
| | | ATTR_TASK_CLASS); |
| | | throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); |
| | | } |
| | | |
| | | attr = attrList.get(0); |
| | | if (attr.isEmpty()) |
| | | { |
| | | Message message = |
| | | ERR_TASKSCHED_NO_CLASS_VALUES.get(ATTR_TASK_CLASS); |
| | | throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); |
| | | } |
| | | |
| | | iterator = attr.iterator(); |
| | | value = iterator.next(); |
| | | if (iterator.hasNext()) |
| | | { |
| | | Message message = ERR_TASKSCHED_MULTIPLE_CLASS_VALUES.get( |
| | | ATTR_TASK_CLASS); |
| | | throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | Message message = ERR_RECURRINGTASK_CANNOT_LOAD_CLASS. |
| | | get(String.valueOf(taskClassName), ATTR_RECURRING_TASK_CLASS_NAME, |
| | | get(String.valueOf(taskClassName), ATTR_TASK_CLASS, |
| | | getExceptionMessage(e)); |
| | | throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message, |
| | | e); |
| | |
| | | |
| | | |
| | | // Make sure that the specified class can be instantiated as a task. |
| | | Task task; |
| | | try |
| | | { |
| | | task = (Task) taskClass.newInstance(); |
| | |
| | | /** |
| | | * Schedules the next iteration of this recurring task for processing. |
| | | * |
| | | * @return The task that has been scheduled for processing. |
| | | * @return The task that has been scheduled for processing. |
| | | * @throws DirectoryException to indicate an error. |
| | | */ |
| | | public Task scheduleNextIteration() |
| | | public Task scheduleNextIteration() throws DirectoryException |
| | | { |
| | | // NYI |
| | | return null; |
| | | Task nextTask = null; |
| | | Date nextTaskDate = null; |
| | | |
| | | try { |
| | | nextTaskDate = getNextIteration(); |
| | | } catch (IllegalArgumentException e) { |
| | | throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, |
| | | ERR_RECURRINGTASK_INVALID_TOKENS_COMBO.get( |
| | | ATTR_RECURRING_TASK_SCHEDULE)); |
| | | } |
| | | |
| | | SimpleDateFormat dateFormat = new SimpleDateFormat( |
| | | DATE_FORMAT_COMPACT_LOCAL_TIME); |
| | | String nextTaskStartTime = dateFormat.format(nextTaskDate); |
| | | |
| | | try { |
| | | // Make a regular task iteration from this recurring task. |
| | | nextTask = task.getClass().newInstance(); |
| | | Entry nextTaskEntry = recurringTaskEntry.duplicate(false); |
| | | String nextTaskID = task.getTaskID() + " - " + |
| | | nextTaskDate.toString(); |
| | | String nextTaskIDName = NAME_PREFIX_TASK + "id"; |
| | | AttributeType taskIDAttrType = |
| | | DirectoryServer.getAttributeType(nextTaskIDName); |
| | | Attribute nextTaskIDAttr = Attributes.create( |
| | | taskIDAttrType, nextTaskID); |
| | | nextTaskEntry.replaceAttribute(nextTaskIDAttr); |
| | | RDN nextTaskRDN = RDN.decode(nextTaskIDName + "=" + nextTaskID); |
| | | DN nextTaskDN = new DN(nextTaskRDN, |
| | | taskScheduler.getTaskBackend().getScheduledTasksParentDN()); |
| | | nextTaskEntry.setDN(nextTaskDN); |
| | | |
| | | String nextTaskStartTimeName = NAME_PREFIX_TASK + |
| | | "scheduled-start-time"; |
| | | AttributeType taskStartTimeAttrType = |
| | | DirectoryServer.getAttributeType(nextTaskStartTimeName); |
| | | Attribute nextTaskStartTimeAttr = Attributes.create( |
| | | taskStartTimeAttrType, nextTaskStartTime); |
| | | nextTaskEntry.replaceAttribute(nextTaskStartTimeAttr); |
| | | |
| | | nextTask.initializeTaskInternal(taskScheduler, nextTaskEntry); |
| | | nextTask.initializeTask(); |
| | | } catch (Exception e) { |
| | | // Should not happen, debug log it otherwise. |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | } |
| | | |
| | | return nextTask; |
| | | } |
| | | |
| | | /** |
| | | * Parse and validate recurring task schedule. |
| | | * @param taskSchedule recurring task schedule tab in crontab(5) format. |
| | | * @throws DirectoryException to indicate an error. |
| | | */ |
| | | private void parseTaskTab(String taskSchedule) throws DirectoryException |
| | | { |
| | | StringTokenizer st = new StringTokenizer(taskSchedule); |
| | | |
| | | if (st.countTokens() != TASKTAB_NUM_TOKENS) { |
| | | throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, |
| | | ERR_RECURRINGTASK_INVALID_N_TOKENS.get( |
| | | ATTR_RECURRING_TASK_SCHEDULE)); |
| | | } |
| | | |
| | | for (TaskTab taskTabToken : TaskTab.values()) { |
| | | String token = st.nextToken(); |
| | | switch (taskTabToken) { |
| | | case MINUTE: |
| | | try { |
| | | minutesArray = parseTaskTabField(token, 0, 59); |
| | | } catch (IllegalArgumentException e) { |
| | | throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, |
| | | ERR_RECURRINGTASK_INVALID_MINUTE_TOKEN.get( |
| | | ATTR_RECURRING_TASK_SCHEDULE)); |
| | | } |
| | | break; |
| | | case HOUR: |
| | | try { |
| | | hoursArray = parseTaskTabField(token, 0, 23); |
| | | } catch (IllegalArgumentException e) { |
| | | throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, |
| | | ERR_RECURRINGTASK_INVALID_HOUR_TOKEN.get( |
| | | ATTR_RECURRING_TASK_SCHEDULE)); |
| | | } |
| | | break; |
| | | case DAY: |
| | | try { |
| | | daysArray = parseTaskTabField(token, 1, 31); |
| | | } catch (IllegalArgumentException e) { |
| | | throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, |
| | | ERR_RECURRINGTASK_INVALID_DAY_TOKEN.get( |
| | | ATTR_RECURRING_TASK_SCHEDULE)); |
| | | } |
| | | break; |
| | | case MONTH: |
| | | try { |
| | | monthArray = parseTaskTabField(token, 1, 12); |
| | | } catch (IllegalArgumentException e) { |
| | | throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, |
| | | ERR_RECURRINGTASK_INVALID_MONTH_TOKEN.get( |
| | | ATTR_RECURRING_TASK_SCHEDULE)); |
| | | } |
| | | break; |
| | | case WEEKDAY: |
| | | try { |
| | | weekdayArray = parseTaskTabField(token, 0, 6); |
| | | } catch (IllegalArgumentException e) { |
| | | throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, |
| | | ERR_RECURRINGTASK_INVALID_WEEKDAY_TOKEN.get( |
| | | ATTR_RECURRING_TASK_SCHEDULE)); |
| | | } |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Parse and validate recurring task schedule field. |
| | | * @param tabField recurring task schedule field in crontab(5) format. |
| | | * @param minValue minimum value allowed for this field. |
| | | * @param maxValue maximum value allowed for this field. |
| | | * @return boolean schedule slots range set according to |
| | | * the schedule field. |
| | | * @throws IllegalArgumentException if tab field is invalid. |
| | | */ |
| | | private boolean[] parseTaskTabField(String tabField, |
| | | int minValue, int maxValue) throws IllegalArgumentException |
| | | { |
| | | boolean[] valueList = new boolean[maxValue + 1]; |
| | | Arrays.fill(valueList, false); |
| | | |
| | | // Blanket. |
| | | if (tabField.equals("*")) { |
| | | for (int i = minValue; i <= maxValue; i++) { |
| | | valueList[i] = true; |
| | | } |
| | | return valueList; |
| | | } |
| | | |
| | | // Exact. |
| | | if (exactPattern.matcher(tabField).matches()) { |
| | | int value = Integer.parseInt(tabField); |
| | | if ((value >= minValue) && (value <= maxValue)) { |
| | | valueList[value] = true; |
| | | return valueList; |
| | | } |
| | | throw new IllegalArgumentException(); |
| | | } |
| | | |
| | | // Range. |
| | | if (rangePattern.matcher(tabField).matches()) { |
| | | StringTokenizer st = new StringTokenizer(tabField, "-"); |
| | | int startValue = Integer.parseInt(st.nextToken()); |
| | | int endValue = Integer.parseInt(st.nextToken()); |
| | | if ((startValue < endValue) && |
| | | ((startValue >= minValue) && (endValue <= maxValue))) |
| | | { |
| | | for (int i = startValue; i <= endValue; i++) { |
| | | valueList[i] = true; |
| | | } |
| | | return valueList; |
| | | } |
| | | throw new IllegalArgumentException(); |
| | | } |
| | | |
| | | // List. |
| | | if (listPattern.matcher(tabField).matches()) { |
| | | StringTokenizer st = new StringTokenizer(tabField, ","); |
| | | while (st.hasMoreTokens()) { |
| | | int value = Integer.parseInt(st.nextToken()); |
| | | if ((value >= minValue) && (value <= maxValue)) { |
| | | valueList[value] = true; |
| | | } else { |
| | | throw new IllegalArgumentException(); |
| | | } |
| | | } |
| | | return valueList; |
| | | } |
| | | |
| | | throw new IllegalArgumentException(); |
| | | } |
| | | |
| | | /** |
| | | * Get next reccuring slot from the range. |
| | | * @param timesList the range. |
| | | * @param fromNow the current slot. |
| | | * @return next recurring slot in the range. |
| | | */ |
| | | private int getNextTimeSlice(boolean[] timesList, int fromNow) |
| | | { |
| | | for (int i = fromNow; i < timesList.length; i++) { |
| | | if (timesList[i]) { |
| | | return i; |
| | | } |
| | | } |
| | | return -1; |
| | | } |
| | | |
| | | /** |
| | | * Get next task iteration date according to recurring schedule. |
| | | * @return next task iteration date. |
| | | * @throws IllegalArgumentException if recurring schedule is invalid. |
| | | */ |
| | | private Date getNextIteration() throws IllegalArgumentException |
| | | { |
| | | int minute, hour, day, month, weekday; |
| | | GregorianCalendar calendar = new GregorianCalendar(); |
| | | calendar.setFirstDayOfWeek(GregorianCalendar.SUNDAY); |
| | | calendar.add(GregorianCalendar.MINUTE, 1); |
| | | calendar.set(GregorianCalendar.SECOND, 0); |
| | | calendar.setLenient(false); |
| | | |
| | | // Weekday |
| | | for (;;) { |
| | | // Month |
| | | for (;;) { |
| | | // Day |
| | | for (;;) { |
| | | // Hour |
| | | for (;;) { |
| | | // Minute |
| | | for (;;) { |
| | | minute = getNextTimeSlice(minutesArray, |
| | | calendar.get(GregorianCalendar.MINUTE)); |
| | | if (minute == -1) { |
| | | calendar.set(GregorianCalendar.MINUTE, 0); |
| | | calendar.add(GregorianCalendar.HOUR_OF_DAY, 1); |
| | | } else { |
| | | calendar.set(GregorianCalendar.MINUTE, minute); |
| | | break; |
| | | } |
| | | } |
| | | hour = getNextTimeSlice(hoursArray, |
| | | calendar.get(GregorianCalendar.HOUR_OF_DAY)); |
| | | if (hour == -1) { |
| | | calendar.set(GregorianCalendar.HOUR_OF_DAY, 0); |
| | | calendar.add(GregorianCalendar.DAY_OF_MONTH, 1); |
| | | } else { |
| | | calendar.set(GregorianCalendar.HOUR_OF_DAY, hour); |
| | | break; |
| | | } |
| | | } |
| | | day = getNextTimeSlice(daysArray, |
| | | calendar.get(GregorianCalendar.DAY_OF_MONTH)); |
| | | if (day == -1) { |
| | | calendar.set(GregorianCalendar.DAY_OF_MONTH, 1); |
| | | calendar.add(GregorianCalendar.MONTH, 1); |
| | | } else { |
| | | calendar.set(GregorianCalendar.DAY_OF_MONTH, day); |
| | | break; |
| | | } |
| | | } |
| | | month = getNextTimeSlice(monthArray, |
| | | (calendar.get(GregorianCalendar.MONTH) + 1)); |
| | | if (month == -1) { |
| | | calendar.set(GregorianCalendar.MONTH, 0); |
| | | calendar.add(GregorianCalendar.YEAR, 1); |
| | | } else { |
| | | calendar.set(GregorianCalendar.MONTH, (month - 1)); |
| | | break; |
| | | } |
| | | } |
| | | weekday = getNextTimeSlice(weekdayArray, |
| | | (calendar.get(GregorianCalendar.DAY_OF_WEEK) - 1)); |
| | | if ((weekday == -1) || |
| | | (weekday != (calendar.get( |
| | | GregorianCalendar.DAY_OF_WEEK) - 1))) |
| | | { |
| | | calendar.add(GregorianCalendar.DAY_OF_MONTH, 1); |
| | | } else { |
| | | break; |
| | | } |
| | | } |
| | | |
| | | return calendar.getTime(); |
| | | } |
| | | } |
| | | |