AdvancedCycleBusinessCalendar.java 5.2 KB
Newer Older
1 2 3 4 5 6 7
package org.activiti5.engine.impl.calendar;

import java.util.Date;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;

8
import org.activiti.engine.runtime.ClockReader;
9 10 11 12 13
import org.activiti5.engine.ActivitiIllegalArgumentException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
14 15 16
 * 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.
17
 * <p>
18 19
 * 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.
20
 * <p>
21 22 23 24
 * 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.
25 26 27 28 29 30 31 32 33 34 35
 * 
 * <pre>
 *   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
 * </pre>
 * 
36 37
 * Removing the DSTZONE key will cause Activiti to use the server's time zone.
 * This is the original behavior.
38
 * <p>
39 40 41
 * 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.
42 43 44 45 46 47 48
 * 
 * <pre>
 *   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
 * </pre>
 * 
49 50
 * By default (if no VER key is included in the string), it assumes version 2.
 * This can be changed by modifying the defaultScheduleVersion property.
51 52 53 54 55 56
 * <p>
 * 
 * @author mseiden
 */
public class AdvancedCycleBusinessCalendar extends CycleBusinessCalendar {

57
  private Integer defaultScheduleVersion;
58

59
  private static final Integer DEFAULT_VERSION = 2;
60

61
  private static final Logger logger = LoggerFactory.getLogger(AdvancedCycleBusinessCalendar.class);
62

63
  private static final Map<Integer, AdvancedSchedulerResolver> resolvers;
64

65 66 67 68 69
  static {
    resolvers = new ConcurrentHashMap<Integer, AdvancedSchedulerResolver>();
    resolvers.put(1, new AdvancedSchedulerResolverWithoutTimeZone());
    resolvers.put(2, new AdvancedSchedulerResolverWithTimeZone());
  }
70

71 72 73
  public AdvancedCycleBusinessCalendar(ClockReader clockReader) {
    super(clockReader);
  }
74

75 76 77 78
  public AdvancedCycleBusinessCalendar(ClockReader clockReader, Integer defaultScheduleVersion) {
    this(clockReader);
    this.defaultScheduleVersion = defaultScheduleVersion;
  }
79

80 81 82
  public Integer getDefaultScheduleVersion() {
    return defaultScheduleVersion == null ? DEFAULT_VERSION : defaultScheduleVersion;
  }
83

84 85 86
  public void setDefaultScheduleVersion(Integer defaultScheduleVersion) {
    this.defaultScheduleVersion = defaultScheduleVersion;
  }
87

88 89 90
  @Override
  public Date resolveDuedate(String duedateDescription, int maxIterations) {
    logger.info("Resolving Due Date: " + duedateDescription);
91

92 93
    String timeZone = getValueFrom("DSTZONE", duedateDescription);
    String version = getValueFrom("VER", duedateDescription);
94

95
    // START is a legacy value that is no longer used, but may still exist in
96 97 98
    // deployed job schedules
    // Could be used in the future as a start date for a CRON job
    // String startDate = getValueFrom("START", duedateDescription);
99

100
    duedateDescription = removeValueFrom("VER", removeValueFrom("START", removeValueFrom("DSTZONE", duedateDescription))).trim();
101

102 103
    try {
      logger.info("Base Due Date: " + duedateDescription);
104

105
      Date date = resolvers.get(version == null ? getDefaultScheduleVersion() : Integer.valueOf(version)).resolve(duedateDescription, clockReader,
106
              timeZone == null ? clockReader.getCurrentTimeZone() : TimeZone.getTimeZone(timeZone));
107

108
      logger.info("Calculated Date: " + (date == null ? "Will Not Run Again" : date));
109

110
      return date;
111

112 113
    } catch (Exception e) {
      throw new ActivitiIllegalArgumentException("Cannot parse duration", e);
114 115
    }

116
  }
117

118 119
  private String getValueFrom(String field, String duedateDescription) {
    int fieldIndex = duedateDescription.indexOf(field + ":");
120

121 122
    if (fieldIndex > -1) {
      int nextWhiteSpace = duedateDescription.indexOf(" ", fieldIndex);
123

124
      fieldIndex += field.length() + 1;
125

126 127 128 129 130
      if (nextWhiteSpace > -1) {
        return duedateDescription.substring(fieldIndex, nextWhiteSpace);
      } else {
        return duedateDescription.substring(fieldIndex);
      }
131 132
    }

133 134
    return null;
  }
135

136 137
  private String removeValueFrom(String field, String duedateDescription) {
    int fieldIndex = duedateDescription.indexOf(field + ":");
138

139 140
    if (fieldIndex > -1) {
      int nextWhiteSpace = duedateDescription.indexOf(" ", fieldIndex);
141

142 143 144 145 146
      if (nextWhiteSpace > -1) {
        return duedateDescription.replace(duedateDescription.substring(fieldIndex, nextWhiteSpace), "");
      } else {
        return duedateDescription.substring(0, fieldIndex);
      }
147
    }
148 149 150

    return duedateDescription;
  }
151
}