1 /*
2 * Copyright 2004-2005 OpenSymphony
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy
6 * of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 *
16 */
17
18 /*
19 * Previously Copyright (c) 2001-2004 James House
20 */
21 package org.quartz.plugins.history;
22
23 import org.apache.commons.logging.Log;
24 import org.apache.commons.logging.LogFactory;
25 import org.quartz.JobExecutionContext;
26 import org.quartz.JobExecutionException;
27 import org.quartz.Scheduler;
28 import org.quartz.SchedulerException;
29 import org.quartz.Trigger;
30 import org.quartz.JobListener;
31 import org.quartz.spi.SchedulerPlugin;
32
33 import java.text.MessageFormat;
34
35 /**
36 * Logs a history of all job executions (and execution vetos) via the
37 * Jakarta Commons-Logging framework.
38 *
39 * <p>
40 * The logged message is customizable by setting one of the following message
41 * properties to a String that conforms to the syntax of <code>java.util.MessageFormat</code>.
42 * </p>
43 *
44 * <p>
45 * JobToBeFiredMessage - available message data are: <table>
46 * <tr>
47 * <th>Element</th>
48 * <th>Data Type</th>
49 * <th>Description</th>
50 * </tr>
51 * <tr>
52 * <td>0</td>
53 * <td>String</td>
54 * <td>The Job's Name.</td>
55 * </tr>
56 * <tr>
57 * <td>1</td>
58 * <td>String</td>
59 * <td>The Job's Group.</td>
60 * </tr>
61 * <tr>
62 * <td>2</td>
63 * <td>Date</td>
64 * <td>The current time.</td>
65 * </tr>
66 * <tr>
67 * <td>3</td>
68 * <td>String</td>
69 * <td>The Trigger's name.</td>
70 * </tr>
71 * <tr>
72 * <td>4</td>
73 * <td>String</td>
74 * <td>The Triggers's group.</td>
75 * </tr>
76 * <tr>
77 * <td>5</td>
78 * <td>Date</td>
79 * <td>The scheduled fire time.</td>
80 * </tr>
81 * <tr>
82 * <td>6</td>
83 * <td>Date</td>
84 * <td>The next scheduled fire time.</td>
85 * </tr>
86 * <tr>
87 * <td>7</td>
88 * <td>Integer</td>
89 * <td>The re-fire count from the JobExecutionContext.</td>
90 * </tr>
91 * </table>
92 *
93 * The default message text is <i>"Job {1}.{0} fired (by trigger {4}.{3}) at:
94 * {2, date, HH:mm:ss MM/dd/yyyy}"</i>
95 * </p>
96 *
97 *
98 * <p>
99 * JobSuccessMessage - available message data are: <table>
100 * <tr>
101 * <th>Element</th>
102 * <th>Data Type</th>
103 * <th>Description</th>
104 * </tr>
105 * <tr>
106 * <td>0</td>
107 * <td>String</td>
108 * <td>The Job's Name.</td>
109 * </tr>
110 * <tr>
111 * <td>1</td>
112 * <td>String</td>
113 * <td>The Job's Group.</td>
114 * </tr>
115 * <tr>
116 * <td>2</td>
117 * <td>Date</td>
118 * <td>The current time.</td>
119 * </tr>
120 * <tr>
121 * <td>3</td>
122 * <td>String</td>
123 * <td>The Trigger's name.</td>
124 * </tr>
125 * <tr>
126 * <td>4</td>
127 * <td>String</td>
128 * <td>The Triggers's group.</td>
129 * </tr>
130 * <tr>
131 * <td>5</td>
132 * <td>Date</td>
133 * <td>The scheduled fire time.</td>
134 * </tr>
135 * <tr>
136 * <td>6</td>
137 * <td>Date</td>
138 * <td>The next scheduled fire time.</td>
139 * </tr>
140 * <tr>
141 * <td>7</td>
142 * <td>Integer</td>
143 * <td>The re-fire count from the JobExecutionContext.</td>
144 * </tr>
145 * <tr>
146 * <td>8</td>
147 * <td>Object</td>
148 * <td>The string value (toString() having been called) of the result (if any)
149 * that the Job set on the JobExecutionContext, with on it. "NULL" if no
150 * result was set.</td>
151 * </td>
152 * </tr>
153 * </table>
154 *
155 * The default message text is <i>"Job {1}.{0} execution complete at {2, date,
156 * HH:mm:ss MM/dd/yyyy} and reports: {8}"</i>
157 * </p>
158 *
159 * <p>
160 * JobFailedMessage - available message data are: <table>
161 * <tr>
162 * <th>Element</th>
163 * <th>Data Type</th>
164 * <th>Description</th>
165 * </tr>
166 * <tr>
167 * <td>0</td>
168 * <td>String</td>
169 * <td>The Job's Name.</td>
170 * </tr>
171 * <tr>
172 * <td>1</td>
173 * <td>String</td>
174 * <td>The Job's Group.</td>
175 * </tr>
176 * <tr>
177 * <td>2</td>
178 * <td>Date</td>
179 * <td>The current time.</td>
180 * </tr>
181 * <tr>
182 * <td>3</td>
183 * <td>String</td>
184 * <td>The Trigger's name.</td>
185 * </tr>
186 * <tr>
187 * <td>4</td>
188 * <td>String</td>
189 * <td>The Triggers's group.</td>
190 * </tr>
191 * <tr>
192 * <td>5</td>
193 * <td>Date</td>
194 * <td>The scheduled fire time.</td>
195 * </tr>
196 * <tr>
197 * <td>6</td>
198 * <td>Date</td>
199 * <td>The next scheduled fire time.</td>
200 * </tr>
201 * <tr>
202 * <td>7</td>
203 * <td>Integer</td>
204 * <td>The re-fire count from the JobExecutionContext.</td>
205 * </tr>
206 * <tr>
207 * <td>8</td>
208 * <td>String</td>
209 * <td>The message from the thrown JobExecution Exception.
210 * </td>
211 * </tr>
212 * </table>
213 *
214 * The default message text is <i>"Job {1}.{0} execution failed at {2, date,
215 * HH:mm:ss MM/dd/yyyy} and reports: {8}"</i>
216 * </p>
217 *
218 *
219 * <p>
220 * JobWasVetoedMessage - available message data are: <table>
221 * <tr>
222 * <th>Element</th>
223 * <th>Data Type</th>
224 * <th>Description</th>
225 * </tr>
226 * <tr>
227 * <td>0</td>
228 * <td>String</td>
229 * <td>The Job's Name.</td>
230 * </tr>
231 * <tr>
232 * <td>1</td>
233 * <td>String</td>
234 * <td>The Job's Group.</td>
235 * </tr>
236 * <tr>
237 * <td>2</td>
238 * <td>Date</td>
239 * <td>The current time.</td>
240 * </tr>
241 * <tr>
242 * <td>3</td>
243 * <td>String</td>
244 * <td>The Trigger's name.</td>
245 * </tr>
246 * <tr>
247 * <td>4</td>
248 * <td>String</td>
249 * <td>The Triggers's group.</td>
250 * </tr>
251 * <tr>
252 * <td>5</td>
253 * <td>Date</td>
254 * <td>The scheduled fire time.</td>
255 * </tr>
256 * <tr>
257 * <td>6</td>
258 * <td>Date</td>
259 * <td>The next scheduled fire time.</td>
260 * </tr>
261 * <tr>
262 * <td>7</td>
263 * <td>Integer</td>
264 * <td>The re-fire count from the JobExecutionContext.</td>
265 * </tr>
266 * </table>
267 *
268 * The default message text is <i>"Job {1}.{0} was vetoed. It was to be fired
269 * (by trigger {4}.{3}) at: {2, date, HH:mm:ss MM/dd/yyyy}"</i>
270 * </p>
271 *
272 *
273 * @author James House
274 */
275 public class LoggingJobHistoryPlugin implements SchedulerPlugin, JobListener {
276
277 /*
278 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
279 *
280 * Data members.
281 *
282 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
283 */
284
285 private String name;
286
287 private String jobToBeFiredMessage = "Job {1}.{0} fired (by trigger {4}.{3}) at: {2, date, HH:mm:ss MM/dd/yyyy}";
288
289 private String jobSuccessMessage = "Job {1}.{0} execution complete at {2, date, HH:mm:ss MM/dd/yyyy} and reports: {8}";
290
291 private String jobFailedMessage = "Job {1}.{0} execution failed at {2, date, HH:mm:ss MM/dd/yyyy} and reports: {8}";
292
293 private String jobWasVetoedMessage = "Job {1}.{0} was vetoed. It was to be fired (by trigger {4}.{3}) at: {2, date, HH:mm:ss MM/dd/yyyy}";
294
295 private final Log log = LogFactory.getLog(getClass());
296
297 /*
298 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
299 *
300 * Constructors.
301 *
302 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
303 */
304
305 public LoggingJobHistoryPlugin() {
306 }
307
308 /*
309 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
310 *
311 * Interface.
312 *
313 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
314 */
315
316 protected Log getLog() {
317 return log;
318 }
319
320 /**
321 * Get the message that is logged when a Job successfully completes its
322 * execution.
323 */
324 public String getJobSuccessMessage() {
325 return jobSuccessMessage;
326 }
327
328 /**
329 * Get the message that is logged when a Job fails its
330 * execution.
331 */
332 public String getJobFailedMessage() {
333 return jobFailedMessage;
334 }
335
336 /**
337 * Get the message that is logged when a Job is about to execute.
338 */
339 public String getJobToBeFiredMessage() {
340 return jobToBeFiredMessage;
341 }
342
343 /**
344 * Set the message that is logged when a Job successfully completes its
345 * execution.
346 *
347 * @param jobSuccessMessage
348 * String in java.text.MessageFormat syntax.
349 */
350 public void setJobSuccessMessage(String jobSuccessMessage) {
351 this.jobSuccessMessage = jobSuccessMessage;
352 }
353
354 /**
355 * Set the message that is logged when a Job fails its
356 * execution.
357 *
358 * @param jobFailedMessage
359 * String in java.text.MessageFormat syntax.
360 */
361 public void setJobFailedMessage(String jobFailedMessage) {
362 this.jobFailedMessage = jobFailedMessage;
363 }
364
365 /**
366 * Set the message that is logged when a Job is about to execute.
367 *
368 * @param jobToBeFiredMessage
369 * String in java.text.MessageFormat syntax.
370 */
371 public void setJobToBeFiredMessage(String jobToBeFiredMessage) {
372 this.jobToBeFiredMessage = jobToBeFiredMessage;
373 }
374
375 /**
376 * Get the message that is logged when a Job execution is vetoed by a
377 * trigger listener.
378 */
379 public String getJobWasVetoedMessage() {
380 return jobWasVetoedMessage;
381 }
382
383 /**
384 * Set the message that is logged when a Job execution is vetoed by a
385 * trigger listener.
386 *
387 * @param jobWasVetoedMessage
388 * String in java.text.MessageFormat syntax.
389 */
390 public void setJobWasVetoedMessage(String jobWasVetoedMessage) {
391 this.jobWasVetoedMessage = jobWasVetoedMessage;
392 }
393
394 /*
395 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
396 *
397 * SchedulerPlugin Interface.
398 *
399 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
400 */
401
402 /**
403 * <p>
404 * Called during creation of the <code>Scheduler</code> in order to give
405 * the <code>SchedulerPlugin</code> a chance to initialize.
406 * </p>
407 *
408 * @throws SchedulerConfigException
409 * if there is an error initializing.
410 */
411 public void initialize(String name, Scheduler scheduler)
412 throws SchedulerException {
413 this.name = name;
414 scheduler.addGlobalJobListener(this);
415 }
416
417 public void start() {
418 // do nothing...
419 }
420
421 /**
422 * <p>
423 * Called in order to inform the <code>SchedulerPlugin</code> that it
424 * should free up all of it's resources because the scheduler is shutting
425 * down.
426 * </p>
427 */
428 public void shutdown() {
429 // nothing to do...
430 }
431
432 /*
433 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
434 *
435 * JobListener Interface.
436 *
437 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
438 */
439
440 /*
441 * Object[] arguments = { new Integer(7), new
442 * Date(System.currentTimeMillis()), "a disturbance in the Force" };
443 *
444 * String result = MessageFormat.format( "At {1,time} on {1,date}, there
445 * was {2} on planet {0,number,integer}.", arguments);
446 */
447
448 public String getName() {
449 return name;
450 }
451
452 /**
453 * @see org.quartz.JobListener#jobToBeExecuted(JobExecutionContext)
454 */
455 public void jobToBeExecuted(JobExecutionContext context) {
456 if (!getLog().isInfoEnabled()) {
457 return;
458 }
459
460 Trigger trigger = context.getTrigger();
461
462 Object[] args = {
463 context.getJobDetail().getName(),
464 context.getJobDetail().getGroup(), new java.util.Date(),
465 trigger.getName(), trigger.getGroup(),
466 trigger.getPreviousFireTime(), trigger.getNextFireTime(),
467 new Integer(context.getRefireCount())
468 };
469
470 getLog().info(MessageFormat.format(getJobToBeFiredMessage(), args));
471 }
472
473 /**
474 * @see org.quartz.JobListener#jobWasExecuted(JobExecutionContext, JobExecutionException)
475 */
476 public void jobWasExecuted(JobExecutionContext context,
477 JobExecutionException jobException) {
478
479 Trigger trigger = context.getTrigger();
480
481 Object[] args = null;
482
483 if (jobException != null) {
484 if (!getLog().isWarnEnabled()) {
485 return;
486 }
487
488 String errMsg = jobException.getMessage();
489 args =
490 new Object[] {
491 context.getJobDetail().getName(),
492 context.getJobDetail().getGroup(), new java.util.Date(),
493 trigger.getName(), trigger.getGroup(),
494 trigger.getPreviousFireTime(), trigger.getNextFireTime(),
495 new Integer(context.getRefireCount()), errMsg
496 };
497
498 getLog().warn(MessageFormat.format(getJobFailedMessage(), args), jobException);
499 } else {
500 if (!getLog().isInfoEnabled()) {
501 return;
502 }
503
504 String result = String.valueOf(context.getResult());
505 args =
506 new Object[] {
507 context.getJobDetail().getName(),
508 context.getJobDetail().getGroup(), new java.util.Date(),
509 trigger.getName(), trigger.getGroup(),
510 trigger.getPreviousFireTime(), trigger.getNextFireTime(),
511 new Integer(context.getRefireCount()), result
512 };
513
514 getLog().info(MessageFormat.format(getJobSuccessMessage(), args));
515 }
516 }
517
518 /**
519 * @see org.quartz.JobListener#jobExecutionVetoed(org.quartz.JobExecutionContext)
520 */
521 public void jobExecutionVetoed(JobExecutionContext context) {
522
523 if (!getLog().isInfoEnabled()) {
524 return;
525 }
526
527 Trigger trigger = context.getTrigger();
528
529 Object[] args = {
530 context.getJobDetail().getName(),
531 context.getJobDetail().getGroup(), new java.util.Date(),
532 trigger.getName(), trigger.getGroup(),
533 trigger.getPreviousFireTime(), trigger.getNextFireTime(),
534 new Integer(context.getRefireCount())
535 };
536
537 getLog().info(MessageFormat.format(getJobWasVetoedMessage(), args));
538 }
539
540 }
541
542 // EOF