package org.activiti5.engine.impl.calendar; import java.util.Date; import java.util.Map; import java.util.TimeZone; import java.util.concurrent.ConcurrentHashMap; import org.activiti.engine.runtime.ClockReader; import org.activiti5.engine.ActivitiIllegalArgumentException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * An Activiti BusinessCalendar for cycle based schedules that takes into * account a different daylight savings time zone than the one that the server * is configured for. *

* For CRON strings DSTZONE is used as the time zone that the CRON schedule * refers to. Leave it out to use the server time zone. *

* For ISO strings the time zone offset for the date/time specified is part of * the string itself. DSTZONE is used to determine what the offset should be * NOW, which may be different than when the workflow was scheduled if it is * scheduled to run across a DST event. * *

 *   For example:
 *      R/2013-10-01T20:30:00/P1D DSTZONE:US/Eastern
 *      R/2013-10-01T20:30:00/P1D DSTZONE:UTC
 *      R/2013-10-01T20:30:00/P1D DSTZONE:US/Arizona
 *      0 30 20 ? * MON,TUE,WED,THU,FRI * DSTZONE:US/Eastern
 *      0 30 20 ? * MON,TUE,WED,THU,FRI * DSTZONE:UTC
 *      0 30 20 ? * MON,TUE,WED,THU,FRI * DSTZONE:US/Arizona
 * 
* * Removing the DSTZONE key will cause Activiti to use the server's time zone. * This is the original behavior. *

* Schedule strings are versioned. Version 1 strings will use the original * Activiti CycleBusinessCalendar. All new properties are ignored. Version 2 * strings will use the new daylight saving time logic. * *

 *   For example:
 *      R/2013-10-01T20:30:00/P1D VER:2 DSTZONE:US/Eastern
 *      0 30 20 ? * MON,TUE,WED,THU,FRI * VER:1 DSTZONE:US/Arizona
 * 
* * By default (if no VER key is included in the string), it assumes version 2. * This can be changed by modifying the defaultScheduleVersion property. *

* * @author mseiden */ public class AdvancedCycleBusinessCalendar extends CycleBusinessCalendar { private Integer defaultScheduleVersion; private static final Integer DEFAULT_VERSION = 2; private static final Logger logger = LoggerFactory.getLogger(AdvancedCycleBusinessCalendar.class); private static final Map resolvers; static { resolvers = new ConcurrentHashMap(); resolvers.put(1, new AdvancedSchedulerResolverWithoutTimeZone()); resolvers.put(2, new AdvancedSchedulerResolverWithTimeZone()); } public AdvancedCycleBusinessCalendar(ClockReader clockReader) { super(clockReader); } public AdvancedCycleBusinessCalendar(ClockReader clockReader, Integer defaultScheduleVersion) { this(clockReader); this.defaultScheduleVersion = defaultScheduleVersion; } public Integer getDefaultScheduleVersion() { return defaultScheduleVersion == null ? DEFAULT_VERSION : defaultScheduleVersion; } public void setDefaultScheduleVersion(Integer defaultScheduleVersion) { this.defaultScheduleVersion = defaultScheduleVersion; } @Override public Date resolveDuedate(String duedateDescription, int maxIterations) { logger.info("Resolving Due Date: " + duedateDescription); String timeZone = getValueFrom("DSTZONE", duedateDescription); String version = getValueFrom("VER", duedateDescription); // START is a legacy value that is no longer used, but may still exist in // deployed job schedules // Could be used in the future as a start date for a CRON job // String startDate = getValueFrom("START", duedateDescription); duedateDescription = removeValueFrom("VER", removeValueFrom("START", removeValueFrom("DSTZONE", duedateDescription))).trim(); try { logger.info("Base Due Date: " + duedateDescription); Date date = resolvers.get(version == null ? getDefaultScheduleVersion() : Integer.valueOf(version)).resolve(duedateDescription, clockReader, timeZone == null ? clockReader.getCurrentTimeZone() : TimeZone.getTimeZone(timeZone)); logger.info("Calculated Date: " + (date == null ? "Will Not Run Again" : date)); return date; } catch (Exception e) { throw new ActivitiIllegalArgumentException("Cannot parse duration", e); } } private String getValueFrom(String field, String duedateDescription) { int fieldIndex = duedateDescription.indexOf(field + ":"); if (fieldIndex > -1) { int nextWhiteSpace = duedateDescription.indexOf(" ", fieldIndex); fieldIndex += field.length() + 1; if (nextWhiteSpace > -1) { return duedateDescription.substring(fieldIndex, nextWhiteSpace); } else { return duedateDescription.substring(fieldIndex); } } return null; } private String removeValueFrom(String field, String duedateDescription) { int fieldIndex = duedateDescription.indexOf(field + ":"); if (fieldIndex > -1) { int nextWhiteSpace = duedateDescription.indexOf(" ", fieldIndex); if (nextWhiteSpace > -1) { return duedateDescription.replace(duedateDescription.substring(fieldIndex, nextWhiteSpace), ""); } else { return duedateDescription.substring(0, fieldIndex); } } return duedateDescription; } }