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