1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.log4j.scheduler;
19
20 import java.util.List;
21 import java.util.Vector;
22
23 /**
24 * A simple but still useful implementation of a Scheduler (in memory only).
25 * <p></p>
26 * This implementation will work very well when the number of scheduled job is
27 * small, say less than 100 jobs. If a larger number of events need to be
28 * scheduled, than a better adapted data structure for the jobList can give
29 * improved performance.
30 *
31 * @author Ceki
32 */
33 public class Scheduler extends Thread {
34
35 /**
36 * Job list.
37 */
38 List<ScheduledJobEntry> jobList;
39 /**
40 * If set true, scheduler has or should shut down.
41 */
42 boolean shutdown = false;
43
44 /**
45 * Create new instance.
46 */
47 public Scheduler() {
48 super();
49 jobList = new Vector<>();
50 }
51
52 /**
53 * Find the index of a given job.
54 *
55 * @param job job
56 * @return -1 if the job could not be found.
57 */
58 int findIndex(final Job job) {
59 int size = jobList.size();
60 boolean found = false;
61
62 int i = 0;
63 for (; i < size; i++) {
64 ScheduledJobEntry se = jobList.get(i);
65 if (se.job == job) {
66 found = true;
67 break;
68 }
69 }
70 if (found) {
71 return i;
72 } else {
73 return -1;
74 }
75 }
76
77 /**
78 * Delete the given job.
79 *
80 * @param job job.
81 * @return true if the job could be deleted, and
82 * false if the job could not be found or if the Scheduler is about to
83 * shutdown in which case deletions are not permitted.
84 */
85 public synchronized boolean delete(final Job job) {
86 // if already shutdown in the process of shutdown, there is no
87 // need to remove Jobs as they will never be executed.
88 if (shutdown) {
89 return false;
90 }
91 int i = findIndex(job);
92 if (i != -1) {
93 ScheduledJobEntry se = jobList.remove(i);
94 if (se.job != job) { // this should never happen
95 new IllegalStateException("Internal programming error");
96 }
97 // if the job is the first on the list,
98 // then notify the scheduler thread to schedule a new job
99 if (i == 0) {
100 this.notifyAll();
101 }
102 return true;
103 } else {
104 return false;
105 }
106 }
107
108
109 /**
110 * Schedule a {@link Job} for execution at system time given by
111 * the <code>desiredTime</code> parameter.
112 *
113 * @param job job to schedule.
114 * @param desiredTime desired time of execution.
115 */
116 public synchronized void schedule(final Job job,
117 final long desiredTime) {
118 schedule(new ScheduledJobEntry(job, desiredTime));
119 }
120
121 /**
122 * Schedule a {@link Job} for execution at system time given by
123 * the <code>desiredTime</code> parameter.
124 * <p></p>
125 * The job will be rescheduled. It will execute with a frequency determined
126 * by the period parameter.
127 *
128 * @param job job to schedule.
129 * @param desiredTime desired time of execution.
130 * @param period repeat period.
131 */
132 public synchronized void schedule(final Job job,
133 final long desiredTime,
134 final long period) {
135 schedule(new ScheduledJobEntry(job, desiredTime, period));
136 }
137
138 /**
139 * Change the period of a job. The original job must exist for its period
140 * to be changed.
141 * <p></p>
142 * The method returns true if the period could be changed, and false
143 * otherwise.
144 *
145 * @param job job.
146 * @param newPeriod new repeat period.
147 * @return true if period could be changed.
148 */
149 public synchronized boolean changePeriod(final Job job,
150 final long newPeriod) {
151 if (newPeriod <= 0) {
152 throw new IllegalArgumentException(
153 "Period must be an integer langer than zero");
154 }
155
156 int i = findIndex(job);
157 if (i == -1) {
158 return false;
159 } else {
160 ScheduledJobEntry se = jobList.get(i);
161 se.period = newPeriod;
162 return true;
163 }
164 }
165
166 /**
167 * Schedule a job.
168 *
169 * @param newSJE new job entry.
170 */
171 private synchronized void schedule(final ScheduledJobEntry newSJE) {
172 // disallow new jobs after shutdown
173 if (shutdown) {
174 return;
175 }
176 int max = jobList.size();
177 long desiredExecutionTime = newSJE.desiredExecutionTime;
178
179 // find the index i such that timeInMillis < jobList[i]
180 int i = 0;
181 for (; i < max; i++) {
182
183 ScheduledJobEntry sje = jobList.get(i);
184
185 if (desiredExecutionTime < sje.desiredExecutionTime) {
186 break;
187 }
188 }
189 jobList.add(i, newSJE);
190 // if the jobList was empty, then notify the scheduler thread
191 if (i == 0) {
192 this.notifyAll();
193 }
194 }
195
196 /**
197 * Shut down scheduler.
198 */
199 public synchronized void shutdown() {
200 shutdown = true;
201 }
202
203 /**
204 * Run scheduler.
205 */
206 public synchronized void run() {
207 while (!shutdown) {
208 if (jobList.isEmpty()) {
209 linger();
210 } else {
211 ScheduledJobEntry sje = jobList.get(0);
212 long now = System.currentTimeMillis();
213 if (now >= sje.desiredExecutionTime) {
214 executeInABox(sje.job);
215 jobList.remove(0);
216 if (sje.period > 0) {
217 sje.desiredExecutionTime = now + sje.period;
218 schedule(sje);
219 }
220 } else {
221 linger(sje.desiredExecutionTime - now);
222 }
223 }
224 }
225 // clear out the job list to facilitate garbage collection
226 jobList.clear();
227 jobList = null;
228 System.out.println("Leaving scheduler run method");
229 }
230
231 /**
232 * We do not want a single failure to affect the whole scheduler.
233 *
234 * @param job job to execute.
235 */
236 void executeInABox(final Job job) {
237 try {
238 job.execute();
239 } catch (Exception e) {
240 System.err.println("The execution of the job threw an exception");
241 e.printStackTrace(System.err);
242 }
243 }
244
245 /**
246 * Wait for notification.
247 */
248 void linger() {
249 try {
250 while (jobList.isEmpty() && !shutdown) {
251 this.wait();
252 }
253 } catch (InterruptedException ie) {
254 shutdown = true;
255 }
256 }
257
258 /**
259 * Wait for notification or time to elapse.
260 *
261 * @param timeToLinger time to linger.
262 */
263 void linger(final long timeToLinger) {
264 try {
265 this.wait(timeToLinger);
266 } catch (InterruptedException ie) {
267 shutdown = true;
268 }
269 }
270
271 /**
272 * Represents an entry in job scheduler.
273 */
274 static final class ScheduledJobEntry {
275 /**
276 * Desired execution time.
277 */
278 long desiredExecutionTime;
279 /**
280 * Job to run.
281 */
282 Job job;
283 /**
284 * Repeat period.
285 */
286 long period = 0;
287
288 /**
289 * Create new instance.
290 *
291 * @param job job
292 * @param desiredTime desired time.
293 */
294 ScheduledJobEntry(final Job job, final long desiredTime) {
295 this(job, desiredTime, 0);
296 }
297
298 /**
299 * Create new instance.
300 *
301 * @param job job
302 * @param desiredTime desired time
303 * @param period repeat period
304 */
305 ScheduledJobEntry(final Job job,
306 final long desiredTime,
307 final long period) {
308 super();
309 this.desiredExecutionTime = desiredTime;
310 this.job = job;
311 this.period = period;
312 }
313 }
314
315 }
316
317