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 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 * @param job job
55 * @return -1 if the job could not be found.
56 */
57 int findIndex(final Job job) {
58 int size = jobList.size();
59 boolean found = false;
60
61 int i = 0;
62 for (; i < size; i++) {
63 ScheduledJobEntry se = (ScheduledJobEntry) jobList.get(i);
64 if (se.job == job) {
65 found = true;
66 break;
67 }
68 }
69 if (found) {
70 return i;
71 } else {
72 return -1;
73 }
74 }
75
76 /**
77 * Delete the given job.
78 * @param job job.
79 * @return true if the job could be deleted, and
80 * false if the job could not be found or if the Scheduler is about to
81 * shutdown in which case deletions are not permitted.
82 */
83 public synchronized boolean delete(final Job job) {
84 // if already shutdown in the process of shutdown, there is no
85 // need to remove Jobs as they will never be executed.
86 if (shutdown) {
87 return false;
88 }
89 int i = findIndex(job);
90 if (i != -1) {
91 ScheduledJobEntry se = (ScheduledJobEntry) jobList.remove(i);
92 if (se.job != job) { // this should never happen
93 new IllegalStateException("Internal programming error");
94 }
95 // if the job is the first on the list,
96 // then notify the scheduler thread to schedule a new job
97 if (i == 0) {
98 this.notifyAll();
99 }
100 return true;
101 } else {
102 return false;
103 }
104 }
105
106
107 /**
108 * Schedule a {@link Job} for execution at system time given by
109 * the <code>desiredTime</code> parameter.
110 * @param job job to schedule.
111 * @param desiredTime desired time of execution.
112 */
113 public synchronized void schedule(final Job job,
114 final long desiredTime) {
115 schedule(new ScheduledJobEntry(job, desiredTime));
116 }
117
118 /**
119 * Schedule a {@link Job} for execution at system time given by
120 * the <code>desiredTime</code> parameter.
121 * <p></p>
122 * The job will be rescheduled. It will execute with a frequency determined
123 * by the period parameter.
124 * @param job job to schedule.
125 * @param desiredTime desired time of execution.
126 * @param period repeat period.
127 */
128 public synchronized void schedule(final Job job,
129 final long desiredTime,
130 final long period) {
131 schedule(new ScheduledJobEntry(job, desiredTime, period));
132 }
133
134 /**
135 * Change the period of a job. The original job must exist for its period
136 * to be changed.
137 * <p></p>
138 * The method returns true if the period could be changed, and false
139 * otherwise.
140 * @param job job.
141 * @param newPeriod new repeat period.
142 * @return true if period could be changed.
143 */
144 public synchronized boolean changePeriod(final Job job,
145 final long newPeriod) {
146 if (newPeriod <= 0) {
147 throw new IllegalArgumentException(
148 "Period must be an integer langer than zero");
149 }
150
151 int i = findIndex(job);
152 if (i == -1) {
153 return false;
154 } else {
155 ScheduledJobEntry se = (ScheduledJobEntry) jobList.get(i);
156 se.period = newPeriod;
157 return true;
158 }
159 }
160
161 /**
162 * Schedule a job.
163 * @param newSJE new job entry.
164 */
165 private synchronized void schedule(final ScheduledJobEntry newSJE) {
166 // disallow new jobs after shutdown
167 if (shutdown) {
168 return;
169 }
170 int max = jobList.size();
171 long desiredExecutionTime = newSJE.desiredExecutionTime;
172
173 // find the index i such that timeInMillis < jobList[i]
174 int i = 0;
175 for (; i < max; i++) {
176
177 ScheduledJobEntry sje = (ScheduledJobEntry) jobList.get(i);
178
179 if (desiredExecutionTime < sje.desiredExecutionTime) {
180 break;
181 }
182 }
183 jobList.add(i, newSJE);
184 // if the jobList was empty, then notify the scheduler thread
185 if (i == 0) {
186 this.notifyAll();
187 }
188 }
189
190 /**
191 * Shut down scheduler.
192 */
193 public synchronized void shutdown() {
194 shutdown = true;
195 }
196
197 /**
198 * Run scheduler.
199 */
200 public synchronized void run() {
201 while (!shutdown) {
202 if (jobList.isEmpty()) {
203 linger();
204 } else {
205 ScheduledJobEntry sje = (ScheduledJobEntry) jobList.get(0);
206 long now = System.currentTimeMillis();
207 if (now >= sje.desiredExecutionTime) {
208 executeInABox(sje.job);
209 jobList.remove(0);
210 if (sje.period > 0) {
211 sje.desiredExecutionTime = now + sje.period;
212 schedule(sje);
213 }
214 } else {
215 linger(sje.desiredExecutionTime - now);
216 }
217 }
218 }
219 // clear out the job list to facilitate garbage collection
220 jobList.clear();
221 jobList = null;
222 System.out.println("Leaving scheduler run method");
223 }
224
225 /**
226 * We do not want a single failure to affect the whole scheduler.
227 * @param job job to execute.
228 */
229 void executeInABox(final Job job) {
230 try {
231 job.execute();
232 } catch (Exception e) {
233 System.err.println("The execution of the job threw an exception");
234 e.printStackTrace(System.err);
235 }
236 }
237
238 /**
239 * Wait for notification.
240 */
241 void linger() {
242 try {
243 while (jobList.isEmpty() && !shutdown) {
244 this.wait();
245 }
246 } catch (InterruptedException ie) {
247 shutdown = true;
248 }
249 }
250
251 /**
252 * Wait for notification or time to elapse.
253 * @param timeToLinger time to linger.
254 */
255 void linger(final long timeToLinger) {
256 try {
257 this.wait(timeToLinger);
258 } catch (InterruptedException ie) {
259 shutdown = true;
260 }
261 }
262
263 /**
264 * Represents an entry in job scheduler.
265 */
266 static final class ScheduledJobEntry {
267 /**
268 * Desired execution time.
269 */
270 long desiredExecutionTime;
271 /**
272 * Job to run.
273 */
274 Job job;
275 /**
276 * Repeat period.
277 */
278 long period = 0;
279
280 /**
281 * Create new instance.
282 * @param job job
283 * @param desiredTime desired time.
284 */
285 ScheduledJobEntry(final Job job, final long desiredTime) {
286 this(job, desiredTime, 0);
287 }
288
289 /**
290 * Create new instance.
291 * @param job job
292 * @param desiredTime desired time
293 * @param period repeat period
294 */
295 ScheduledJobEntry(final Job job,
296 final long desiredTime,
297 final long period) {
298 super();
299 this.desiredExecutionTime = desiredTime;
300 this.job = job;
301 this.period = period;
302 }
303 }
304
305 }
306
307