View Javadoc
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