KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > quartz > plugins > xml > JobInitializationPlugin


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.xml;
22
23 import java.io.File JavaDoc;
24 import java.io.FileNotFoundException JavaDoc;
25 import java.io.IOException JavaDoc;
26 import java.io.InputStream JavaDoc;
27 import java.net.URL JavaDoc;
28 import java.net.URLDecoder JavaDoc;
29 import java.util.Date JavaDoc;
30 import java.util.HashMap JavaDoc;
31 import java.util.HashSet JavaDoc;
32 import java.util.Iterator JavaDoc;
33 import java.util.Map JavaDoc;
34 import java.util.Set JavaDoc;
35 import java.util.StringTokenizer JavaDoc;
36
37 import javax.transaction.UserTransaction JavaDoc;
38
39 import org.quartz.JobDetail;
40 import org.quartz.Scheduler;
41 import org.quartz.SchedulerException;
42 import org.quartz.SimpleTrigger;
43 import org.quartz.jobs.FileScanJob;
44 import org.quartz.jobs.FileScanListener;
45 import org.quartz.plugins.SchedulerPluginWithUserTransactionSupport;
46 import org.quartz.simpl.CascadingClassLoadHelper;
47 import org.quartz.spi.ClassLoadHelper;
48 import org.quartz.xml.JobSchedulingDataProcessor;
49
50 /**
51  * This plugin loads XML file(s) to add jobs and schedule them with triggers
52  * as the scheduler is initialized, and can optionally periodically scan the
53  * file for changes.
54  *
55  * <p>
56  * The periodically scanning of files for changes is not currently supported in a
57  * clustered environment.
58  * </p>
59  *
60  * <p>
61  * If using the JobInitializationPlugin with JobStoreCMT, be sure to set the
62  * plugin property <em>wrapInUserTransaction</em> to true. Also, if have a
63  * positive <em>scanInterval</em> be sure to set
64  * <em>org.quartz.scheduler.wrapJobExecutionInUserTransaction</em> to true.
65  * </p>
66  *
67  * @author James House
68  * @author Pierre Awaragi
69  */

70 public class JobInitializationPlugin
71     extends SchedulerPluginWithUserTransactionSupport
72     implements FileScanListener {
73
74     /*
75      * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
76      *
77      * Data members.
78      *
79      * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
80      */

81     private static final int MAX_JOB_TRIGGER_NAME_LEN = 80;
82     private static final String JavaDoc JOB_INITIALIZATION_PLUGIN_NAME = "JobInitializationPlugin";
83     private static final String JavaDoc FILE_NAME_DELIMITERS = ",";
84     
85     private boolean overWriteExistingJobs = false;
86
87     private boolean failOnFileNotFound = true;
88
89     private String JavaDoc fileNames = JobSchedulingDataProcessor.QUARTZ_XML_FILE_NAME;
90
91     // Populated by initialization
92
private Map JavaDoc jobFiles = new HashMap JavaDoc();
93
94     private boolean useContextClassLoader = true;
95     
96     private boolean validating = false;
97     
98     private boolean validatingSchema = true;
99
100     private long scanInterval = 0;
101     
102     boolean started = false;
103     
104     protected ClassLoadHelper classLoadHelper = null;
105
106     private Set JavaDoc jobTriggerNameSet = new HashSet JavaDoc();
107     
108     /*
109      * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
110      *
111      * Constructors.
112      *
113      * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
114      */

115
116     public JobInitializationPlugin() {
117     }
118
119     /*
120      * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
121      *
122      * Interface.
123      *
124      * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
125      */

126
127     /**
128      * The file name (and path) to the XML file that should be read.
129      * @deprecated Use fileNames with just one file.
130      */

131     public String JavaDoc getFileName() {
132         return fileNames;
133     }
134
135     /**
136      * The file name (and path) to the XML file that should be read.
137      * @deprecated Use fileNames with just one file.
138      */

139     public void setFileName(String JavaDoc fileName) {
140         getLog().warn("The \"filename\" plugin property is deprecated. Please use \"filenames\" in the future.");
141         this.fileNames = fileName;
142     }
143
144     /**
145      * Comma separated list of file names (with paths) to the XML files that should be read.
146      */

147     public String JavaDoc getFileNames() {
148         return fileNames;
149     }
150
151     /**
152      * The file name (and path) to the XML file that should be read.
153      */

154     public void setFileNames(String JavaDoc fileNames) {
155         this.fileNames = fileNames;
156     }
157     
158     /**
159      * Whether or not jobs defined in the XML file should be overwrite existing
160      * jobs with the same name.
161      */

162     public boolean isOverWriteExistingJobs() {
163         return overWriteExistingJobs;
164     }
165
166     /**
167      * Whether or not jobs defined in the XML file should be overwrite existing
168      * jobs with the same name.
169      *
170      * @param overWriteExistingJobs
171      */

172     public void setOverWriteExistingJobs(boolean overWriteExistingJobs) {
173         this.overWriteExistingJobs = overWriteExistingJobs;
174     }
175
176     /**
177      * The interval (in seconds) at which to scan for changes to the file.
178      * If the file has been changed, it is re-loaded and parsed. The default
179      * value for the interval is 0, which disables scanning.
180      *
181      * @return Returns the scanInterval.
182      */

183     public long getScanInterval() {
184         return scanInterval / 1000;
185     }
186
187     /**
188      * The interval (in seconds) at which to scan for changes to the file.
189      * If the file has been changed, it is re-loaded and parsed. The default
190      * value for the interval is 0, which disables scanning.
191      *
192      * @param scanInterval The scanInterval to set.
193      */

194     public void setScanInterval(long scanInterval) {
195         this.scanInterval = scanInterval * 1000;
196     }
197     
198     /**
199      * Whether or not initialization of the plugin should fail (throw an
200      * exception) if the file cannot be found. Default is <code>true</code>.
201      */

202     public boolean isFailOnFileNotFound() {
203         return failOnFileNotFound;
204     }
205
206     /**
207      * Whether or not initialization of the plugin should fail (throw an
208      * exception) if the file cannot be found. Default is <code>true</code>.
209      */

210     public void setFailOnFileNotFound(boolean failOnFileNotFound) {
211         this.failOnFileNotFound = failOnFileNotFound;
212     }
213     
214     /**
215      * Whether or not the context class loader should be used. Default is <code>true</code>.
216      */

217     public boolean isUseContextClassLoader() {
218         return useContextClassLoader;
219     }
220
221     /**
222      * Whether or not context class loader should be used. Default is <code>true</code>.
223      */

224     public void setUseContextClassLoader(boolean useContextClassLoader) {
225         this.useContextClassLoader = useContextClassLoader;
226     }
227     
228     /**
229      * Whether or not the XML should be validated. Default is <code>false</code>.
230      */

231     public boolean isValidating() {
232         return validating;
233     }
234
235     /**
236      * Whether or not the XML should be validated. Default is <code>false</code>.
237      */

238     public void setValidating(boolean validating) {
239         this.validating = validating;
240     }
241     
242     /**
243      * Whether or not the XML schema should be validated. Default is <code>true</code>.
244      */

245     public boolean isValidatingSchema() {
246         return validatingSchema;
247     }
248
249     /**
250      * Whether or not the XML schema should be validated. Default is <code>true</code>.
251      */

252     public void setValidatingSchema(boolean validatingSchema) {
253         this.validatingSchema = validatingSchema;
254     }
255
256     /*
257      * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
258      *
259      * SchedulerPlugin Interface.
260      *
261      * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
262      */

263
264     /**
265      * <p>
266      * Called during creation of the <code>Scheduler</code> in order to give
267      * the <code>SchedulerPlugin</code> a chance to initialize.
268      * </p>
269      *
270      * @throws org.quartz.SchedulerConfigException
271      * if there is an error initializing.
272      */

273     public void initialize(String JavaDoc name, final Scheduler scheduler)
274         throws SchedulerException {
275         super.initialize(name, scheduler);
276         
277         classLoadHelper = new CascadingClassLoadHelper();
278         classLoadHelper.initialize();
279         
280         getLog().info("Registering Quartz Job Initialization Plug-in.");
281         
282         // Create JobFile objects
283
StringTokenizer JavaDoc stok = new StringTokenizer JavaDoc(fileNames, FILE_NAME_DELIMITERS);
284         while (stok.hasMoreTokens()) {
285             JobFile jobFile = new JobFile(stok.nextToken());
286             jobFiles.put(jobFile.getFilePath(), jobFile);
287         }
288     }
289
290     
291     public void start(UserTransaction JavaDoc userTransaction) {
292         try {
293             if (jobFiles.isEmpty() == false) {
294                 
295                 if (scanInterval > 0) {
296                     getScheduler().getContext().put(JOB_INITIALIZATION_PLUGIN_NAME + '_' + getName(), this);
297                 }
298                 
299                 Iterator JavaDoc iterator = jobFiles.values().iterator();
300                 while (iterator.hasNext()) {
301                     JobFile jobFile = (JobFile)iterator.next();
302                 
303                     if (scanInterval > 0) {
304                         String JavaDoc jobTriggerName = buildJobTriggerName(jobFile.getFileBasename());
305                         
306                         SimpleTrigger trig = new SimpleTrigger(
307                                 jobTriggerName,
308                                 JOB_INITIALIZATION_PLUGIN_NAME,
309                                 new Date JavaDoc(), null,
310                                 SimpleTrigger.REPEAT_INDEFINITELY, scanInterval);
311                         trig.setVolatility(true);
312                         
313                         JobDetail job = new JobDetail(
314                                 jobTriggerName,
315                                 JOB_INITIALIZATION_PLUGIN_NAME,
316                                 FileScanJob.class);
317                         job.setVolatility(true);
318                         job.getJobDataMap().put(FileScanJob.FILE_NAME, jobFile.getFilePath());
319                         job.getJobDataMap().put(FileScanJob.FILE_SCAN_LISTENER_NAME, JOB_INITIALIZATION_PLUGIN_NAME + '_' + getName());
320                         
321                         getScheduler().scheduleJob(job, trig);
322                     }
323                     
324                     processFile(jobFile);
325                 }
326             }
327         } catch(SchedulerException se) {
328             getLog().error("Error starting background-task for watching jobs file.", se);
329         } finally {
330             started = true;
331         }
332     }
333     
334     /**
335      * Helper method for generating unique job/trigger name for the
336      * file scanning jobs (one per FileJob). The unique names are saved
337      * in jobTriggerNameSet.
338      */

339     private String JavaDoc buildJobTriggerName(
340             String JavaDoc fileBasename) {
341         // Name w/o collisions will be prefix + _ + filename (with '.' of filename replaced with '_')
342
// For example: JobInitializationPlugin_jobInitializer_myjobs_xml
343
String JavaDoc jobTriggerName = JOB_INITIALIZATION_PLUGIN_NAME + '_' + getName() + '_' + fileBasename.replace('.', '_');
344         
345         // If name is too long (DB column is 80 chars), then truncate to max length
346
if (jobTriggerName.length() > MAX_JOB_TRIGGER_NAME_LEN) {
347             jobTriggerName = jobTriggerName.substring(0, MAX_JOB_TRIGGER_NAME_LEN);
348         }
349         
350         // Make sure this name is unique in case the same file name under different
351
// directories is being checked, or had a naming collision due to length truncation.
352
// If there is a conflict, keep incrementing a _# suffix on the name (being sure
353
// not to get too long), until we find a unique name.
354
int currentIndex = 1;
355         while (jobTriggerNameSet.add(jobTriggerName) == false) {
356             // If not our first time through, then strip off old numeric suffix
357
if (currentIndex > 1) {
358                 jobTriggerName = jobTriggerName.substring(0, jobTriggerName.lastIndexOf('_'));
359             }
360
361             String JavaDoc numericSuffix = "_" + currentIndex++;
362
363             // If the numeric suffix would make the name too long, then make room for it.
364
if (jobTriggerName.length() > (MAX_JOB_TRIGGER_NAME_LEN - numericSuffix.length())) {
365                 jobTriggerName = jobTriggerName.substring(0, (MAX_JOB_TRIGGER_NAME_LEN - numericSuffix.length()));
366             }
367
368             jobTriggerName += numericSuffix;
369         }
370         
371         return jobTriggerName;
372     }
373     
374     /**
375      * Overriden to ignore <em>wrapInUserTransaction</em> because shutdown()
376      * does not interact with the <code>Scheduler</code>.
377      */

378     public void shutdown() {
379         // Since we have nothing to do, override base shutdown so don't
380
// get extranious UserTransactions.
381
}
382
383     private void processFile(JobFile jobFile) {
384         if ((jobFile == null) || (jobFile.getFileFound() == false)) {
385             return;
386         }
387
388         JobSchedulingDataProcessor processor =
389             new JobSchedulingDataProcessor(isUseContextClassLoader(), isValidating(), isValidatingSchema());
390
391         try {
392             processor.processFileAndScheduleJobs(
393                     jobFile.getFilePath(),
394                     jobFile.getFilePath(), // systemId
395
getScheduler(),
396                     isOverWriteExistingJobs());
397         } catch (Exception JavaDoc e) {
398             getLog().error("Error scheduling jobs: " + e.getMessage(), e);
399         }
400     }
401     
402     public void processFile(String JavaDoc filePath) {
403         processFile((JobFile)jobFiles.get(filePath));
404     }
405
406     /**
407      * @see org.quartz.jobs.FileScanListener#fileUpdated(java.lang.String)
408      */

409     public void fileUpdated(String JavaDoc fileName) {
410         if (started) {
411             processFile(fileName);
412         }
413     }
414     
415     class JobFile {
416         private String JavaDoc fileName;
417
418         // These are set by initialize()
419
private String JavaDoc filePath;
420         private String JavaDoc fileBasename;
421         private boolean fileFound;
422
423         protected JobFile(String JavaDoc fileName) throws SchedulerException {
424             this.fileName = fileName;
425             initialize();
426         }
427         
428         protected String JavaDoc getFileName() {
429             return fileName;
430         }
431         
432         protected boolean getFileFound() {
433             return fileFound;
434         }
435
436         protected String JavaDoc getFilePath() {
437             return filePath;
438         }
439         
440         protected String JavaDoc getFileBasename() {
441             return fileBasename;
442         }
443                 
444         private void initialize() throws SchedulerException {
445             InputStream JavaDoc f = null;
446             try {
447                 String JavaDoc furl = null;
448                 
449                 File JavaDoc file = new File JavaDoc(getFileName()); // files in filesystem
450
if (!file.exists()) {
451                     URL JavaDoc url = classLoadHelper.getResource(getFileName());
452                     if(url != null) {
453     // we need jdk 1.3 compatibility, so we abandon this code...
454
// try {
455
// furl = URLDecoder.decode(url.getPath(), "UTF-8");
456
// } catch (UnsupportedEncodingException e) {
457
// furl = url.getPath();
458
// }
459
furl = URLDecoder.decode(url.getPath());
460                         file = new File JavaDoc(furl);
461                         try {
462                             f = url.openStream();
463                         } catch (IOException JavaDoc ignor) {
464                             // Swallow the exception
465
}
466                     }
467                 } else {
468                     try {
469                         f = new java.io.FileInputStream JavaDoc(file);
470                     }catch (FileNotFoundException JavaDoc e) {
471                         // ignore
472
}
473                 }
474                 
475                 if (f == null) {
476                     if (isFailOnFileNotFound()) {
477                         throw new SchedulerException(
478                             "File named '" + getFileName() + "' does not exist.");
479                     } else {
480                         getLog().warn("File named '" + getFileName() + "' does not exist.");
481                     }
482                 } else {
483                     fileFound = true;
484                     filePath = (furl != null) ? furl : file.getAbsolutePath();
485                     fileBasename = file.getName();
486                 }
487             } finally {
488                 try {
489                     if (f != null) {
490                         f.close();
491                     }
492                 } catch (IOException JavaDoc ioe) {
493                     getLog().warn("Error closing jobs file " + getFileName(), ioe);
494                 }
495             }
496         }
497     }
498 }
499
500 // EOF
501
Popular Tags