1 package com.atlassian.scheduler.config;
2
3 import com.atlassian.annotations.PublicApi;
4 import com.atlassian.scheduler.SchedulerService;
5 import com.atlassian.scheduler.status.RunOutcome;
6 import com.google.common.base.Predicates;
7
8 import javax.annotation.Nullable;
9 import javax.annotation.concurrent.Immutable;
10 import java.util.Date;
11 import java.util.Objects;
12 import java.util.TimeZone;
13
14 import static com.atlassian.util.concurrent.Assertions.isTrue;
15 import static com.atlassian.util.concurrent.Assertions.notNull;
16 import static com.google.common.collect.Iterables.filter;
17 import static com.google.common.collect.Iterables.size;
18 import static java.util.Arrays.asList;
19
20 /**
21 * Represents a schedule used to run a job at particular times. Instances of this
22 * class are not created directly. Use one of the factory methods — such as
23 * {@link #forInterval(long, Date)} or {@link #forCronExpression(String, TimeZone)} —
24 * to construct them. To recover the information, use {@link #getType()} to identify the
25 * type of schedule information and call the appropriate getter ({@link #getIntervalScheduleInfo()}
26 * for {@link Type#INTERVAL}; {@link #getCronScheduleInfo()} for {@link Type#CRON_EXPRESSION}).
27 */
28 @Immutable
29 @PublicApi
30 public final class Schedule {
31 /**
32 * Creates a new schedule for the given cron expression. The cron expression is not verified immediately,
33 * but invalid expressions will cause {@link SchedulerService#scheduleJobWithGeneratedId(JobConfig)} to fail. The system's
34 * {@link TimeZone#getDefault() default time zone} is assumed.
35 *
36 * @param cronExpression the cron expression to use for the schedule
37 * @return a schedule for running jobs when the given cron expression is satisfied
38 */
39 public static Schedule forCronExpression(String cronExpression) {
40 return forCronExpression(cronExpression, null);
41 }
42
43 /**
44 * Creates a new schedule for the given cron expression. The cron expression is not verified immediately,
45 * but invalid expressions will cause {@link SchedulerService#scheduleJobWithGeneratedId(JobConfig)} to fail.
46 *
47 * @param cronExpression the cron expression to use for the schedule
48 * @param timeZone the time zone within which to apply the rules of the cron expression; may be {@code null}
49 * to use a default time zone that is appropriate for the application
50 * @return a schedule for running jobs when the given cron expression is satisfied
51 */
52 public static Schedule forCronExpression(String cronExpression, @Nullable TimeZone timeZone) {
53 return new Schedule(Type.CRON_EXPRESSION, null, new CronScheduleInfo(cronExpression, timeZone));
54 }
55
56 /**
57 * Creates a new schedule that will run once at the specified time. Jobs that are scheduled to run once
58 * are not guaranteed to remain in the system after they are attempted, regardless of the {@link RunOutcome}.
59 * Jobs scheduled as {@code runOnce} will be purged automatically after they have run; that is, at some
60 * point after they have run (possibly immediately), {@link SchedulerService#getJobDetails(JobId)} will
61 * no longer return the job's information.
62 *
63 * @param runTime when the job should run; may be {@code null} to indicate that the job should run
64 * as soon as possible
65 * @return a schedule for running once at the given time
66 */
67 public static Schedule runOnce(@Nullable Date runTime) {
68 return forInterval(0L, runTime);
69 }
70
71 /**
72 * Creates a new schedule that will run periodically with the given interval.
73 * <p>
74 * <strong>WARNING</strong>: Implementations are not required to honour the time interval at millisecond
75 * precision. The actual resolution that is permitted is implementation-defined. Implementations should
76 * round the requested interval up and persist the rounded value so that the interval reported by
77 * later calls to {@link Schedule#getIntervalScheduleInfo()} will be consistent with the behaviour that the
78 * scheduler actually provides.
79 * </p>
80 *
81 * @param intervalInMillis the minimum time interval (in milliseconds) between runs. If the
82 * value {@code 0L} is specified, then the job will not repeat (this is equivalent to using
83 * {@link #runOnce(Date) runOnce(firstRunTime)}). Negative values are not permitted.
84 * @param firstRunTime when the job should run for the first time; may be {@code null} to indicate that
85 * the job should run as soon as possible
86 * @return a schedule for running once at the given time
87 */
88 public static Schedule forInterval(long intervalInMillis, @Nullable Date firstRunTime) {
89 return new Schedule(Type.INTERVAL,
90 new IntervalScheduleInfo(firstRunTime, intervalInMillis),
91 null);
92 }
93
94
95 private final Type type;
96 private final IntervalScheduleInfo intervalScheduleInfo;
97 private final CronScheduleInfo cronScheduleInfo;
98
99 private Schedule(final Type type, @Nullable final IntervalScheduleInfo intervalScheduleInfo,
100 @Nullable final CronScheduleInfo cronScheduleInfo) {
101 this.type = notNull("type", type);
102 this.intervalScheduleInfo = intervalScheduleInfo;
103 this.cronScheduleInfo = cronScheduleInfo;
104
105 isTrue("Exactly one of the schedule formats must be non-null",
106 countNulls(intervalScheduleInfo, cronScheduleInfo) == 1);
107 }
108
109 private static int countNulls(Object... schedules) {
110 return size(filter(asList(schedules), Predicates.notNull()));
111 }
112
113
114 /**
115 * Returns a representation of the simple settings that were used to create this schedule.
116 *
117 * @return the interval schedule, or {@code null} if that is not this schedule's {@link #getType() type}.
118 */
119 public IntervalScheduleInfo getIntervalScheduleInfo() {
120 return intervalScheduleInfo;
121 }
122
123 /**
124 * Returns a representation of the cron settings that were used to create this schedule.
125 *
126 * @return the cron schedule, or {@code null} if that is not this schedule's {@link #getType() type}.
127 */
128 public CronScheduleInfo getCronScheduleInfo() {
129 return cronScheduleInfo;
130 }
131
132 /**
133 * Returns the {@link Type} of this schedule.
134 *
135 * @return the {@link Type} of this schedule.
136 */
137 public Type getType() {
138 return type;
139 }
140
141
142 @Override
143 public boolean equals(@Nullable final Object o) {
144 if (this == o) {
145 return true;
146 }
147 if (o == null || getClass() != o.getClass()) {
148 return false;
149 }
150
151 final Schedule other = (Schedule) o;
152 return type == other.type
153 && Objects.equals(intervalScheduleInfo, other.intervalScheduleInfo)
154 && Objects.equals(cronScheduleInfo, other.cronScheduleInfo);
155 }
156
157 @Override
158 public int hashCode() {
159 return Objects.hash(type, intervalScheduleInfo, cronScheduleInfo);
160 }
161
162 @Override
163 public String toString() {
164 final StringBuilder sb = new StringBuilder(128).append("Schedule[type=").append(type);
165 switch (type) {
166 case CRON_EXPRESSION:
167 sb.append(",cronScheduleInfo=").append(cronScheduleInfo);
168 break;
169 case INTERVAL:
170 sb.append(",intervalScheduleInfo=").append(intervalScheduleInfo);
171 break;
172 }
173 return sb.append(']').toString();
174 }
175
176
177 public static enum Type {
178 /**
179 * A schedule that uses a cron expression to control when the job runs.
180 *
181 * @see CronScheduleInfo
182 * @see #getCronScheduleInfo()
183 */
184 CRON_EXPRESSION,
185
186 /**
187 * A schedule that uses a interval to control when the job runs.
188 *
189 * @see IntervalScheduleInfo
190 * @see #getIntervalScheduleInfo()
191 */
192 INTERVAL
193 }
194 }
195