/* * Copyright (c) 2010-2013 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.evolveum.midpoint.task.quartzimpl; import static org.quartz.CronScheduleBuilder.cronScheduleNonvalidatedExpression; import static org.quartz.SimpleScheduleBuilder.simpleSchedule; import java.text.ParseException; import java.util.Date; import com.evolveum.midpoint.task.quartzimpl.execution.JobExecutor; import com.evolveum.midpoint.util.exception.SystemException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import org.quartz.*; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.task.api.TaskExecutionStatus; import com.evolveum.midpoint.xml.ns._public.common.common_3.MisfireActionType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ScheduleType; import javax.xml.datatype.XMLGregorianCalendar; public class TaskQuartzImplUtil { private static final transient Trace LOGGER = TraceManager.getTrace(TaskQuartzImplUtil.class); public static final long SINGLE_TASK_CHECK_INTERVAL = 10000; public static JobKey createJobKeyForTask(Task t) { return new JobKey(t.getOid()); } public static JobKey createJobKeyForTaskOid(String oid) { return new JobKey(oid); } public static TriggerKey createTriggerKeyForTask(Task t) { return new TriggerKey(t.getOid()); } public static TriggerKey createTriggerKeyForTaskOid(String oid) { return new TriggerKey(oid); } public static JobDetail createJobDetailForTask(Task task) { JobDetail job = JobBuilder.newJob(JobExecutor.class) .withIdentity(TaskQuartzImplUtil.createJobKeyForTask(task)) .storeDurably() .requestRecovery() .build(); return job; } public static Trigger createTriggerForTask(Task task) throws ParseException { if (task.getExecutionStatus() != TaskExecutionStatus.RUNNABLE) { return null; // no triggers for such tasks } // special case - recurrent task with no schedule (means "run on demand only") if (task.isCycle() && (task.getSchedule() == null || (task.getSchedule().getInterval() == null && task.getSchedule().getCronLikePattern() == null))) { return null; } TriggerBuilder<Trigger> tb = TriggerBuilder.newTrigger() .withIdentity(createTriggerKeyForTask(task)) .forJob(createJobKeyForTask(task)); if (task.getSchedule() != null) { Date est = task.getSchedule().getEarliestStartTime() != null ? task.getSchedule().getEarliestStartTime().toGregorianCalendar().getTime() : null; Date lst = task.getSchedule().getLatestStartTime() != null ? task.getSchedule().getLatestStartTime().toGregorianCalendar().getTime() : null; // endAt must not be sooner than startAt if (lst != null && est == null) { if (lst.getTime() < System.currentTimeMillis()) { est = lst; // there's no point in setting est to current time } } if (est != null) { tb.startAt(est); } else { tb.startNow(); } if (lst != null) { tb.endAt(lst); // LST is checked also within JobExecutor (needed mainly for tightly-bound recurrent tasks) } if (task.getSchedule().getLatestFinishTime() != null) { tb.endAt(task.getSchedule().getLatestFinishTime().toGregorianCalendar().getTime()); // however, it is the responsibility of task handler to finish no later than this time } } boolean looselyBoundRecurrent; if (task.isCycle() && task.isLooselyBound()) { looselyBoundRecurrent = true; ScheduleType sch = task.getSchedule(); if (sch == null) { return null; //throw new IllegalStateException("Recurrent task " + task + " does not have a schedule."); } ScheduleBuilder sb; if (sch.getInterval() != null) { SimpleScheduleBuilder ssb = simpleSchedule() .withIntervalInSeconds(sch.getInterval().intValue()) .repeatForever(); if (sch.getMisfireAction() == null || sch.getMisfireAction() == MisfireActionType.EXECUTE_IMMEDIATELY) { sb = ssb.withMisfireHandlingInstructionFireNow(); } else if (sch.getMisfireAction() == MisfireActionType.RESCHEDULE) { sb = ssb.withMisfireHandlingInstructionNextWithRemainingCount(); } else { throw new SystemException("Invalid value of misfireAction: " + sch.getMisfireAction() + " for task " + task); } } else if (sch.getCronLikePattern() != null) { CronScheduleBuilder csb = cronScheduleNonvalidatedExpression(sch.getCronLikePattern()); // may throw ParseException if (sch.getMisfireAction() == null || sch.getMisfireAction() == MisfireActionType.EXECUTE_IMMEDIATELY) { sb = csb.withMisfireHandlingInstructionFireAndProceed(); } else if (sch.getMisfireAction() == MisfireActionType.RESCHEDULE) { sb = csb.withMisfireHandlingInstructionDoNothing(); } else { throw new SystemException("Invalid value of misfireAction: " + sch.getMisfireAction() + " for task " + task); } } else { return null; //throw new IllegalStateException("The schedule for task " + task + " is neither fixed nor cron-like one."); } tb.withSchedule(sb); } else { // even non-recurrent tasks will be triggered, to check whether they should not be restarted // (their trigger will be erased when these tasks will be completed) looselyBoundRecurrent = false; // tb.withSchedule(simpleSchedule().withIntervalInMilliseconds(SINGLE_TASK_CHECK_INTERVAL).repeatForever()); } tb.usingJobData("schedule", scheduleFingerprint(task.getSchedule())); tb.usingJobData("looselyBoundRecurrent", looselyBoundRecurrent); tb.usingJobData("handlerUri", task.getHandlerUri()); return tb.build(); } public static Trigger createTriggerNowForTask(Task task) { return TriggerBuilder.newTrigger() .forJob(createJobKeyForTask(task)).startNow() .build(); } public static Trigger createTriggerForTask(Task task, long startAt) { return TriggerBuilder.newTrigger() .forJob(createJobKeyForTask(task)) .startAt(new Date(startAt)) .build(); } public static long xmlGCtoMillis(XMLGregorianCalendar gc) { return gc != null ? gc.toGregorianCalendar().getTimeInMillis() : 0L; } // quick hack, todo: do seriously private static String scheduleFingerprint(ScheduleType scheduleType) { if (scheduleType == null) { return ""; } else { return scheduleType.getInterval() + " $$ " + scheduleType.getCronLikePattern() + " $$ " + xmlGCtoMillis(scheduleType.getEarliestStartTime()) + " $$ " + xmlGCtoMillis(scheduleType.getLatestStartTime()) + " $$ " + xmlGCtoMillis(scheduleType.getLatestFinishTime()) + " $$ " + scheduleType.getMisfireAction() + " $$"; } } // compares scheduling-related data maps of triggers public static boolean triggerDataMapsDiffer(Trigger triggerAsIs, Trigger triggerToBe) { JobDataMap asIs = triggerAsIs.getJobDataMap(); JobDataMap toBe = triggerToBe.getJobDataMap(); boolean scheduleDiffer = !toBe.getString("schedule").equals(asIs.getString("schedule")); boolean lbrDiffer = toBe.getBoolean("looselyBoundRecurrent") != asIs.getBoolean("looselyBoundRecurrent"); String tbh = toBe.getString("handlerUri"); String aih = asIs.getString("handlerUri"); //LOGGER.trace("handlerUri: asIs = " + aih + ", toBe = " + tbh); boolean handlersDiffer = tbh != null ? !tbh.equals(aih) : aih == null; if (LOGGER.isTraceEnabled()) { if (scheduleDiffer) { LOGGER.trace("trigger data maps differ in schedule: triggerAsIs.schedule = " + asIs.getString("schedule") + ", triggerToBe.schedule = " + toBe.getString("schedule")); } if (lbrDiffer) { LOGGER.trace("trigger data maps differ in looselyBoundRecurrent: triggerAsIs = " + asIs.getBoolean("looselyBoundRecurrent") + ", triggerToBe = " + toBe.getBoolean("looselyBoundRecurrent")); } if (handlersDiffer) { LOGGER.trace("trigger data maps differ in handlerUri: triggerAsIs = " + aih + ", triggerToBe = " + tbh); } } return scheduleDiffer || lbrDiffer || handlersDiffer; } public static ParseException validateCronExpression(String cron) { try { cronScheduleNonvalidatedExpression(cron); // may throw ParseException return null; } catch (ParseException pe) { return pe; } } }