KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > quartz > xml > JobSchedulingDataProcessor


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.xml;
22
23 import java.beans.PropertyDescriptor JavaDoc;
24 import java.io.File JavaDoc;
25 import java.io.FileInputStream JavaDoc;
26 import java.io.FileNotFoundException JavaDoc;
27 import java.io.IOException JavaDoc;
28 import java.io.InputStream JavaDoc;
29 import java.net.URL JavaDoc;
30 import java.net.URLDecoder JavaDoc;
31 import java.text.DateFormat JavaDoc;
32 import java.text.ParseException JavaDoc;
33 import java.text.SimpleDateFormat JavaDoc;
34 import java.util.ArrayList JavaDoc;
35 import java.util.Collection JavaDoc;
36 import java.util.Collections JavaDoc;
37 import java.util.Date JavaDoc;
38 import java.util.HashMap JavaDoc;
39 import java.util.Iterator JavaDoc;
40 import java.util.LinkedList JavaDoc;
41 import java.util.List JavaDoc;
42 import java.util.Map JavaDoc;
43 import java.util.TimeZone JavaDoc;
44
45 import javax.xml.parsers.ParserConfigurationException JavaDoc;
46
47 import org.apache.commons.beanutils.ConversionException;
48 import org.apache.commons.beanutils.Converter;
49 import org.apache.commons.beanutils.DynaBean;
50 import org.apache.commons.beanutils.DynaProperty;
51 import org.apache.commons.beanutils.PropertyUtils;
52 import org.apache.commons.digester.BeanPropertySetterRule;
53 import org.apache.commons.digester.Digester;
54 import org.apache.commons.digester.RuleSetBase;
55 import org.apache.commons.logging.Log;
56 import org.apache.commons.logging.LogFactory;
57 import org.quartz.CronTrigger;
58 import org.quartz.JobDataMap;
59 import org.quartz.JobDetail;
60 import org.quartz.JobListener;
61 import org.quartz.ObjectAlreadyExistsException;
62 import org.quartz.Scheduler;
63 import org.quartz.SchedulerException;
64 import org.quartz.SimpleTrigger;
65 import org.quartz.Trigger;
66 import org.xml.sax.InputSource JavaDoc;
67 import org.xml.sax.SAXException JavaDoc;
68 import org.xml.sax.SAXParseException JavaDoc;
69 import org.xml.sax.helpers.DefaultHandler JavaDoc;
70
71
72 /**
73  * Parses an XML file that declares Jobs and their schedules (Triggers).
74  *
75  * The xml document must conform to the format defined in
76  * "job_scheduling_data_1_5.dtd" or "job_scheduling_data_1_5.xsd"
77  *
78  * After creating an instance of this class, you should call one of the <code>processFile()</code>
79  * functions, after which you may call the <code>getScheduledJobs()</code>
80  * function to get a handle to the defined Jobs and Triggers, which can then be
81  * scheduled with the <code>Scheduler</code>. Alternatively, you could call
82  * the <code>processFileAndScheduleJobs()</code> function to do all of this
83  * in one step.
84  *
85  * The same instance can be used again and again, with the list of defined Jobs
86  * being cleared each time you call a <code>processFile</code> method,
87  * however a single instance is not thread-safe.
88  *
89  * @author <a HREF="mailto:bonhamcm@thirdeyeconsulting.com">Chris Bonham</a>
90  * @author James House
91  */

92 public class JobSchedulingDataProcessor extends DefaultHandler JavaDoc {
93     /*
94      * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
95      *
96      * Constants.
97      *
98      * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
99      */

100
101     public static final String JavaDoc QUARTZ_PUBLIC_ID = "-//Quartz Enterprise Job Scheduler//DTD Job Scheduling Data 1.5//EN";
102
103     public static final String JavaDoc QUARTZ_SYSTEM_ID = "http://www.opensymphony.com/quartz/xml/job_scheduling_data_1_5.dtd";
104     
105     public static final String JavaDoc QUARTZ_DTD = "/org/quartz/xml/job_scheduling_data_1_5.dtd";
106
107     public static final String JavaDoc QUARTZ_NS = "http://www.opensymphony.com/quartz/JobSchedulingData";
108     
109     public static final String JavaDoc QUARTZ_SCHEMA = "http://www.opensymphony.com/quartz/xml/job_scheduling_data_1_5.xsd";
110     
111     public static final String JavaDoc QUARTZ_XSD = "/org/quartz/xml/job_scheduling_data_1_5.xsd";
112
113     public static final String JavaDoc QUARTZ_SYSTEM_ID_DIR_PROP = "quartz.system.id.dir";
114
115     public static final String JavaDoc QUARTZ_XML_FILE_NAME = "quartz_jobs.xml";
116
117     public static final String JavaDoc QUARTZ_SYSTEM_ID_PREFIX = "jar:";
118
119     protected static final String JavaDoc TAG_QUARTZ = "quartz";
120     
121     protected static final String JavaDoc TAG_OVERWRITE_EXISTING_JOBS = "overwrite-existing-jobs";
122
123     protected static final String JavaDoc TAG_JOB_LISTENER = "job-listener";
124     
125     protected static final String JavaDoc TAG_CALENDAR = "calendar";
126     
127     protected static final String JavaDoc TAG_CLASS_NAME = "class-name";
128     
129     protected static final String JavaDoc TAG_DESCRIPTION = "description";
130
131     protected static final String JavaDoc TAG_BASE_CALENDAR = "base-calendar";
132     
133     protected static final String JavaDoc TAG_MISFIRE_INSTRUCTION = "misfire-instruction";
134     
135     protected static final String JavaDoc TAG_CALENDAR_NAME = "calendar-name";
136
137     protected static final String JavaDoc TAG_JOB = "job";
138
139     protected static final String JavaDoc TAG_JOB_DETAIL = "job-detail";
140
141     protected static final String JavaDoc TAG_NAME = "name";
142
143     protected static final String JavaDoc TAG_GROUP = "group";
144
145     protected static final String JavaDoc TAG_JOB_CLASS = "job-class";
146
147     protected static final String JavaDoc TAG_JOB_LISTENER_REF = "job-listener-ref";
148     
149     protected static final String JavaDoc TAG_VOLATILITY = "volatility";
150
151     protected static final String JavaDoc TAG_DURABILITY = "durability";
152
153     protected static final String JavaDoc TAG_RECOVER = "recover";
154     
155     protected static final String JavaDoc TAG_JOB_DATA_MAP = "job-data-map";
156     
157     protected static final String JavaDoc TAG_ENTRY = "entry";
158     
159     protected static final String JavaDoc TAG_KEY = "key";
160     
161     protected static final String JavaDoc TAG_ALLOWS_TRANSIENT_DATA = "allows-transient-data";
162     
163     protected static final String JavaDoc TAG_VALUE = "value";
164
165     protected static final String JavaDoc TAG_TRIGGER = "trigger";
166
167     protected static final String JavaDoc TAG_SIMPLE = "simple";
168
169     protected static final String JavaDoc TAG_CRON = "cron";
170
171     protected static final String JavaDoc TAG_JOB_NAME = "job-name";
172
173     protected static final String JavaDoc TAG_JOB_GROUP = "job-group";
174
175     protected static final String JavaDoc TAG_START_TIME = "start-time";
176
177     protected static final String JavaDoc TAG_END_TIME = "end-time";
178
179     protected static final String JavaDoc TAG_REPEAT_COUNT = "repeat-count";
180
181     protected static final String JavaDoc TAG_REPEAT_INTERVAL = "repeat-interval";
182
183     protected static final String JavaDoc TAG_CRON_EXPRESSION = "cron-expression";
184
185     protected static final String JavaDoc TAG_TIME_ZONE = "time-zone";
186
187     /**
188      * XML Schema dateTime datatype format.
189      * <p>
190      * See <a HREF="http://www.w3.org/TR/2001/REC-xmlschema-2-20010502/#dateTime">
191      * http://www.w3.org/TR/2001/REC-xmlschema-2-20010502/#dateTime</a>
192      */

193     protected static final String JavaDoc XSD_DATE_FORMAT = "yyyy-MM-dd'T'hh:mm:ss";
194     
195     /**
196      * Legacy DTD version 1.0 date format.
197      */

198     protected static final String JavaDoc DTD_DATE_FORMAT = "yyyy-MM-dd hh:mm:ss a";
199
200     /*
201      * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
202      *
203      * Data members.
204      *
205      * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
206      */

207
208     protected Map JavaDoc scheduledJobs = new HashMap JavaDoc();
209
210     protected List JavaDoc jobsToSchedule = new LinkedList JavaDoc();
211     protected List JavaDoc calsToSchedule = new LinkedList JavaDoc();
212     protected List JavaDoc listenersToSchedule = new LinkedList JavaDoc();
213     
214     protected Collection JavaDoc validationExceptions = new ArrayList JavaDoc();
215     
216     protected Digester digester;
217     
218     private boolean overWriteExistingJobs = true;
219     
220     private ThreadLocal JavaDoc schedLocal = new ThreadLocal JavaDoc();
221     
222     private final Log log = LogFactory.getLog(getClass());
223
224     /*
225      * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
226      *
227      * Constructors.
228      *
229      * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
230      */

231      
232     /**
233      * Constructor for QuartzMetaDataProcessor.
234      */

235     public JobSchedulingDataProcessor() {
236         this(true, true, true);
237     }
238
239     /**
240      * Constructor for QuartzMetaDataProcessor.
241      *
242      * @param useContextClassLoader whether or not to use the context class loader.
243      * @param validating whether or not to validate XML.
244      * @param validatingSchema whether or not to validate XML schema.
245      */

246     public JobSchedulingDataProcessor(boolean useContextClassLoader, boolean validating, boolean validatingSchema) {
247         initDigester(useContextClassLoader, validating, validatingSchema);
248     }
249
250     /**
251      * Initializes the digester.
252      *
253      * @param useContextClassLoader whether or not to use the context class loader.
254      * @param validating whether or not to validate XML.
255      * @param validatingSchema whether or not to validate XML schema.
256      */

257     protected void initDigester(boolean useContextClassLoader, boolean validating, boolean validatingSchema) {
258         digester = new Digester();
259         digester.setNamespaceAware(true);
260         digester.setUseContextClassLoader(useContextClassLoader);
261         digester.setValidating(validating);
262         initSchemaValidation(validatingSchema);
263         digester.setEntityResolver(this);
264         digester.setErrorHandler(this);
265         
266         if (addCustomDigesterRules(digester)) {
267             addDefaultDigesterRules(digester);
268         }
269     }
270
271     /**
272      * Add the default set of digest rules
273      */

274     protected void addDefaultDigesterRules(Digester digester) {
275         digester.addSetProperties(TAG_QUARTZ, TAG_OVERWRITE_EXISTING_JOBS, "overWriteExistingJobs");
276         digester.addObjectCreate(TAG_QUARTZ + "/" + TAG_JOB_LISTENER, "jobListener","class-name");
277         digester.addCallMethod(TAG_QUARTZ + "/" + TAG_JOB_LISTENER,"setName",1);
278         digester.addCallParam(TAG_QUARTZ + "/" + TAG_JOB_LISTENER,0,"name");
279         digester.addSetNext(TAG_QUARTZ + "/" + TAG_JOB_LISTENER,"addListenerToSchedule");
280         digester.addRuleSet(new CalendarRuleSet(TAG_QUARTZ + "/" + TAG_CALENDAR, "addCalendarToSchedule"));
281         digester.addRuleSet(new CalendarRuleSet("*/" + TAG_BASE_CALENDAR, "setBaseCalendar"));
282         digester.addObjectCreate(TAG_QUARTZ + "/" + TAG_JOB, JobSchedulingBundle.class);
283         digester.addObjectCreate(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL, JobDetail.class);
284         digester.addBeanPropertySetter(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL + "/" + TAG_NAME, "name");
285         digester.addBeanPropertySetter(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL + "/" + TAG_GROUP, "group");
286         digester.addBeanPropertySetter(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL + "/" + TAG_DESCRIPTION, "description");
287         digester.addBeanPropertySetter(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL + "/" + TAG_JOB_CLASS, "jobClass");
288         digester.addCallMethod(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL + "/" + TAG_JOB_LISTENER_REF,"addJobListener",0 );
289         digester.addBeanPropertySetter(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL + "/" + TAG_VOLATILITY, "volatility");
290         digester.addBeanPropertySetter(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL + "/" + TAG_DURABILITY, "durability");
291         digester.addBeanPropertySetter(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL + "/" + TAG_RECOVER, "requestsRecovery");
292         digester.addObjectCreate(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL + "/" + TAG_JOB_DATA_MAP, JobDataMap.class);
293         digester.addCallMethod(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL + "/" + TAG_JOB_DATA_MAP + "/" + TAG_ENTRY, "put", 2, new Class JavaDoc[] { Object JavaDoc.class, Object JavaDoc.class });
294         digester.addCallParam(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL + "/" + TAG_JOB_DATA_MAP + "/" + TAG_ENTRY + "/" + TAG_KEY, 0);
295         digester.addCallParam(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL + "/" + TAG_JOB_DATA_MAP + "/" + TAG_ENTRY + "/" + TAG_VALUE, 1);
296         digester.addSetNext(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL + "/" + TAG_JOB_DATA_MAP, "setJobDataMap");
297         digester.addSetNext(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL, "setJobDetail");
298         digester.addRuleSet(new TriggerRuleSet(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_TRIGGER + "/" + TAG_SIMPLE, SimpleTrigger.class));
299         digester.addBeanPropertySetter(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_TRIGGER + "/" + TAG_SIMPLE + "/" + TAG_REPEAT_COUNT, "repeatCount");
300         digester.addBeanPropertySetter(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_TRIGGER + "/" + TAG_SIMPLE + "/" + TAG_REPEAT_INTERVAL, "repeatInterval");
301         digester.addSetNext(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_TRIGGER + "/" + TAG_SIMPLE, "addTrigger");
302         digester.addRuleSet(new TriggerRuleSet(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_TRIGGER + "/" + TAG_CRON, CronTrigger.class));
303         digester.addBeanPropertySetter(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_TRIGGER + "/" + TAG_CRON + "/" + TAG_CRON_EXPRESSION, "cronExpression");
304         digester.addRule(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_TRIGGER + "/" + TAG_CRON + "/" + TAG_TIME_ZONE, new SimpleConverterRule("timeZone", new TimeZoneConverter(), TimeZone JavaDoc.class));
305         digester.addSetNext(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_TRIGGER + "/" + TAG_CRON, "addTrigger");
306         digester.addSetNext(TAG_QUARTZ + "/" + TAG_JOB, "addJobToSchedule");
307     }
308     
309     /**
310      * Template method provided as a hook for those who wish to extend this
311      * class and add more functionality.
312      *
313      * This method is invoked after the Digester is instantiated, and before
314      * the default set of rules are added.
315      *
316      * @param digester
317      * @return false, if the default rules should NOT be added
318      */

319     protected boolean addCustomDigesterRules(Digester digester) {
320         // do nothing in base impl
321
return true;
322     }
323     
324     /**
325      * Initializes the digester for XML Schema validation.
326      *
327      * @param validatingSchema whether or not to validate XML.
328      */

329     protected void initSchemaValidation(boolean validatingSchema) {
330         if (validatingSchema) {
331             String JavaDoc schemaUri = null;
332             URL JavaDoc url = getClass().getResource(QUARTZ_XSD);
333             if (url != null) {
334                 schemaUri = url.toExternalForm();
335             } else {
336                 schemaUri = QUARTZ_SCHEMA;
337             }
338             digester.setSchema(schemaUri);
339         }
340     }
341
342     protected Log getLog() {
343         return log;
344     }
345
346     /**
347      * Returns whether to use the context class loader.
348      *
349      * @return whether to use the context class loader.
350      */

351     public boolean getUseContextClassLoader() {
352         return digester.getUseContextClassLoader();
353     }
354     
355     /**
356      * Sets whether to use the context class loader.
357      *
358      * @param useContextClassLoader boolean.
359      */

360     public void setUseContextClassLoader(boolean useContextClassLoader) {
361         digester.setUseContextClassLoader(useContextClassLoader);
362     }
363
364     /**
365      * Returns whether to overwrite existing jobs.
366      *
367      * @return whether to overwrite existing jobs.
368      */

369     public boolean getOverWriteExistingJobs() {
370         return overWriteExistingJobs;
371     }
372     
373     /**
374      * Sets whether to overwrite existing jobs.
375      *
376      * @param overWriteExistingJobs boolean.
377      */

378     public void setOverWriteExistingJobs(boolean overWriteExistingJobs) {
379         this.overWriteExistingJobs = overWriteExistingJobs;
380     }
381
382     /*
383      * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
384      *
385      * Interface.
386      *
387      * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
388      */

389
390     /**
391      * Process the xml file in the default location (a file named
392      * "quartz_jobs.xml" in the current working directory).
393      *
394      */

395     public void processFile() throws Exception JavaDoc {
396         processFile(QUARTZ_XML_FILE_NAME);
397     }
398
399     /**
400      * Process the xml file named <code>fileName</code>.
401      *
402      * @param fileName
403      * meta data file name.
404      */

405     public void processFile(String JavaDoc fileName) throws Exception JavaDoc {
406         processFile(fileName, getSystemIdForFileName(fileName));
407     }
408
409     /**
410      * For the given <code>fileName</code>, attempt to expand it to its full path
411      * for use as a system id.
412      *
413      * @see #getURL(String)
414      * @see #processFile()
415      * @see #processFile(String)
416      * @see #processFileAndScheduleJobs(Scheduler, boolean)
417      * @see #processFileAndScheduleJobs(String, Scheduler, boolean)
418      */

419     protected String JavaDoc getSystemIdForFileName(String JavaDoc fileName) {
420         InputStream JavaDoc fileInputStream = null;
421         try {
422             String JavaDoc urlPath = null;
423             
424             File JavaDoc file = new File JavaDoc(fileName); // files in filesystem
425
if (!file.exists()) {
426                 URL JavaDoc url = getURL(fileName);
427                 if (url != null) {
428                     // Required for jdk 1.3 compatibility
429
urlPath = URLDecoder.decode(url.getPath());
430                     try {
431                         fileInputStream = url.openStream();
432                     } catch (IOException JavaDoc ignore) {
433                     }
434                 }
435             } else {
436                 try {
437                     fileInputStream = new FileInputStream JavaDoc(file);
438                 }catch (FileNotFoundException JavaDoc ignore) {
439                 }
440             }
441             
442             if (fileInputStream == null) {
443                 getLog().debug("Unable to resolve '" + fileName + "' to full path, so using it as is for system id.");
444                 return fileName;
445             } else {
446                 return (urlPath != null) ? urlPath : file.getAbsolutePath();
447             }
448         } finally {
449             try {
450                 if (fileInputStream != null) {
451                     fileInputStream.close();
452                 }
453             } catch (IOException JavaDoc ioe) {
454                 getLog().warn("Error closing jobs file: " + fileName, ioe);
455             }
456         }
457     }
458
459     /**
460      * Returns an <code>URL</code> from the fileName as a resource.
461      *
462      * @param fileName
463      * file name.
464      * @return an <code>URL</code> from the fileName as a resource.
465      */

466     protected URL JavaDoc getURL(String JavaDoc fileName) {
467         return Thread.currentThread().getContextClassLoader().getResource(fileName);
468     }
469
470     /**
471      * Process the xmlfile named <code>fileName</code> with the given system
472      * ID.
473      *
474      * @param fileName
475      * meta data file name.
476      * @param systemId
477      * system ID.
478      */

479     public void processFile(String JavaDoc fileName, String JavaDoc systemId)
480         throws ValidationException, ParserConfigurationException JavaDoc,
481             SAXException JavaDoc, IOException JavaDoc, SchedulerException,
482             ClassNotFoundException JavaDoc, ParseException JavaDoc {
483         clearValidationExceptions();
484
485         scheduledJobs.clear();
486         jobsToSchedule.clear();
487         calsToSchedule.clear();
488
489         getLog().info("Parsing XML file: " + fileName +
490                       " with systemId: " + systemId +
491                       " validating: " + digester.getValidating() +
492                       " validating schema: " + digester.getSchema());
493         InputSource JavaDoc is = new InputSource JavaDoc(getInputStream(fileName));
494         is.setSystemId(systemId);
495         digester.push(this);
496         digester.parse(is);
497
498         maybeThrowValidationException();
499     }
500     
501     /**
502      * Process the xmlfile named <code>fileName</code> with the given system
503      * ID.
504      *
505      * @param stream
506      * an input stream containing the xml content.
507      * @param systemId
508      * system ID.
509      */

510     public void processStream(InputStream JavaDoc stream, String JavaDoc systemId)
511         throws ValidationException, ParserConfigurationException JavaDoc,
512             SAXException JavaDoc, IOException JavaDoc, SchedulerException,
513             ClassNotFoundException JavaDoc, ParseException JavaDoc {
514         clearValidationExceptions();
515
516         scheduledJobs.clear();
517         jobsToSchedule.clear();
518         calsToSchedule.clear();
519
520         getLog().info("Parsing XML from stream with systemId: " + systemId +
521                       " validating: " + digester.getValidating() +
522                       " validating schema: " + digester.getSchema());
523         InputSource JavaDoc is = new InputSource JavaDoc(stream);
524         is.setSystemId(systemId);
525         digester.push(this);
526         digester.parse(is);
527
528         maybeThrowValidationException();
529     }
530     
531     /**
532      * Process the xml file in the default location, and schedule all of the
533      * jobs defined within it.
534      *
535      */

536     public void processFileAndScheduleJobs(Scheduler sched,
537             boolean overWriteExistingJobs) throws SchedulerException, Exception JavaDoc {
538         processFileAndScheduleJobs(QUARTZ_XML_FILE_NAME, sched,
539                 overWriteExistingJobs);
540     }
541
542     /**
543      * Process the xml file in the given location, and schedule all of the
544      * jobs defined within it.
545      *
546      * @param fileName
547      * meta data file name.
548      */

549     public void processFileAndScheduleJobs(String JavaDoc fileName, Scheduler sched,
550             boolean overWriteExistingJobs) throws Exception JavaDoc {
551         processFileAndScheduleJobs(fileName, getSystemIdForFileName(fileName), sched, overWriteExistingJobs);
552     }
553     
554     /**
555      * Process the xml file in the given location, and schedule all of the
556      * jobs defined within it.
557      *
558      * @param fileName
559      * meta data file name.
560      */

561     public void processFileAndScheduleJobs(String JavaDoc fileName, String JavaDoc systemId,
562             Scheduler sched, boolean overWriteExistingJobs) throws Exception JavaDoc {
563         schedLocal.set(sched);
564         try {
565             processFile(fileName, systemId);
566             scheduleJobs(getScheduledJobs(), sched, overWriteExistingJobs);
567         } finally {
568             schedLocal.set(null);
569         }
570     }
571
572     /**
573      * Add the Jobs and Triggers defined in the given map of <code>JobSchedulingBundle</code>
574      * s to the given scheduler.
575      *
576      * @param jobBundles
577      * @param sched
578      * @param overWriteExistingJobs
579      * @throws Exception
580      */

581     public void scheduleJobs(Map JavaDoc jobBundles, Scheduler sched,
582             boolean overWriteExistingJobs) throws Exception JavaDoc {
583         getLog().info("Scheduling " + jobsToSchedule.size() + " parsed jobs.");
584
585         Iterator JavaDoc itr = calsToSchedule.iterator();
586         while (itr.hasNext()) {
587             CalendarBundle bndle = (CalendarBundle) itr.next();
588             addCalendar(sched, bndle);
589         }
590
591         itr = jobsToSchedule.iterator();
592         while (itr.hasNext()) {
593             JobSchedulingBundle bndle = (JobSchedulingBundle) itr.next();
594             scheduleJob(bndle, sched, overWriteExistingJobs);
595         }
596         
597         itr = listenersToSchedule.iterator();
598         while (itr.hasNext()) {
599             JobListener listener = (JobListener) itr.next();
600             getLog().info("adding listener "+listener.getName()+" of class "+listener.getClass().getName());
601             sched.addJobListener(listener);
602         }
603         getLog().info(jobBundles.size() + " scheduled jobs.");
604     }
605
606     /**
607      * Returns a <code>Map</code> of scheduled jobs.
608      * <p/>
609      * The key is the job name and the value is a <code>JobSchedulingBundle</code>
610      * containing the <code>JobDetail</code> and <code>Trigger</code>.
611      *
612      * @return a <code>Map</code> of scheduled jobs.
613      */

614     public Map JavaDoc getScheduledJobs() {
615         return Collections.unmodifiableMap(scheduledJobs);
616     }
617
618     /**
619      * Returns a <code>JobSchedulingBundle</code> for the job name.
620      *
621      * @param name
622      * job name.
623      * @return a <code>JobSchedulingBundle</code> for the job name.
624      */

625     public JobSchedulingBundle getScheduledJob(String JavaDoc name) {
626         return (JobSchedulingBundle) getScheduledJobs().get(name);
627     }
628
629     /**
630      * Returns an <code>InputStream</code> from the fileName as a resource.
631      *
632      * @param fileName
633      * file name.
634      * @return an <code>InputStream</code> from the fileName as a resource.
635      */

636     protected InputStream JavaDoc getInputStream(String JavaDoc fileName) {
637         ClassLoader JavaDoc cl = Thread.currentThread().getContextClassLoader();
638
639         InputStream JavaDoc is = cl.getResourceAsStream(fileName);
640
641         return is;
642     }
643     
644     /**
645      * Schedules a given job and trigger (both wrapped by a <code>JobSchedulingBundle</code>).
646      *
647      * @param job
648      * job wrapper.
649      * @exception SchedulerException
650      * if the Job or Trigger cannot be added to the Scheduler, or
651      * there is an internal Scheduler error.
652      */

653     public void scheduleJob(JobSchedulingBundle job)
654         throws SchedulerException {
655         scheduleJob(job, (Scheduler) schedLocal.get(), getOverWriteExistingJobs());
656     }
657
658     
659     public void addJobToSchedule(JobSchedulingBundle job) {
660         jobsToSchedule.add(job);
661     }
662
663     public void addCalendarToSchedule(CalendarBundle cal) {
664         calsToSchedule.add(cal);
665     }
666
667     public void addListenerToSchedule(JobListener listener) {
668         listenersToSchedule.add(listener);
669     }
670     
671     /**
672      * Schedules a given job and trigger (both wrapped by a <code>JobSchedulingBundle</code>).
673      *
674      * @param job
675      * job wrapper.
676      * @param sched
677      * job scheduler.
678      * @param localOverWriteExistingJobs
679      * locally overwrite existing jobs.
680      * @exception SchedulerException
681      * if the Job or Trigger cannot be added to the Scheduler, or
682      * there is an internal Scheduler error.
683      */

684     public void scheduleJob(JobSchedulingBundle job, Scheduler sched, boolean localOverWriteExistingJobs)
685         throws SchedulerException {
686         if ((job != null) && job.isValid()) {
687             JobDetail detail = job.getJobDetail();
688             
689             JobDetail dupeJ = sched.getJobDetail(detail.getName(), detail.getGroup());
690
691             if ((dupeJ != null) && !localOverWriteExistingJobs) {
692                 getLog().info("Not overwriting existing job: " + dupeJ.getFullName());
693                 return;
694             }
695             
696             if (dupeJ != null) {
697                 getLog().info("Replacing job: " + detail.getFullName());
698             } else {
699                 getLog().info("Adding job: " + detail.getFullName());
700             }
701             
702             if (job.getTriggers().size() == 0 && !job.getJobDetail().isDurable()) {
703                 if (dupeJ == null) {
704                     throw new SchedulerException(
705                         "A new job defined without any triggers must be durable: " +
706                         detail.getFullName());
707                 }
708                 
709                 if ((dupeJ.isDurable() &&
710                     (sched.getTriggersOfJob(
711                         detail.getName(), detail.getGroup()).length == 0))) {
712                     throw new SchedulerException(
713                         "Can't make a durable job without triggers non-durable: " +
714                         detail.getFullName());
715                 }
716             }
717             
718             sched.addJob(detail, true);
719             
720             for (Iterator JavaDoc iter = job.getTriggers().iterator(); iter.hasNext(); ) {
721                 Trigger trigger = (Trigger)iter.next();
722
723                 trigger.setJobName(detail.getName());
724                 trigger.setJobGroup(detail.getGroup());
725                 
726                 if(trigger.getStartTime() == null) {
727                     trigger.setStartTime(new Date JavaDoc());
728                 }
729                 
730                 boolean addedTrigger = false;
731                 while (addedTrigger == false) {
732                     Trigger dupeT = sched.getTrigger(trigger.getName(), trigger.getGroup());
733                     if (dupeT != null) {
734                         if (getLog().isDebugEnabled()) {
735                             getLog().debug(
736                                 "Rescheduling job: " + detail.getFullName() + " with updated trigger: " + trigger.getFullName());
737                         }
738                         if(!dupeT.getJobGroup().equals(trigger.getJobGroup()) || !dupeT.getJobName().equals(trigger.getJobName())) {
739                             getLog().warn("Possibly duplicately named triggers in jobs xml file!");
740                         }
741                         sched.rescheduleJob(trigger.getName(), trigger.getGroup(), trigger);
742                     } else {
743                         if (getLog().isDebugEnabled()) {
744                             getLog().debug(
745                                 "Scheduling job: " + detail.getFullName() + " with trigger: " + trigger.getFullName());
746                         }
747     
748                         try {
749                             sched.scheduleJob(trigger);
750                         } catch (ObjectAlreadyExistsException e) {
751                             if (getLog().isDebugEnabled()) {
752                                 getLog().debug(
753                                     "Adding trigger: " + trigger.getFullName() + " for job: " + detail.getFullName() +
754                                     " failed because the trigger already existed. " +
755                                     "This is likely due to a race condition between multiple instances " +
756                                     "in the cluster. Will try to reschedule instead.");
757                             }
758                             continue;
759                         }
760                     }
761                     addedTrigger = true;
762                 }
763             }
764             
765             addScheduledJob(job);
766         }
767     }
768
769     /**
770      * Adds a scheduled job.
771      *
772      * @param job
773      * job wrapper.
774      */

775     protected void addScheduledJob(JobSchedulingBundle job) {
776         scheduledJobs.put(job.getFullName(), job);
777     }
778     
779     /**
780      * Adds a calendar.
781      *
782      * @param calendarBundle calendar bundle.
783      * @throws SchedulerException if the Calendar cannot be added to the Scheduler, or
784      * there is an internal Scheduler error.
785      */

786     public void addCalendar(Scheduler sched, CalendarBundle calendarBundle) throws SchedulerException {
787         sched.addCalendar(
788             calendarBundle.getCalendarName(),
789             calendarBundle.getCalendar(),
790             calendarBundle.getReplace(),
791             true);
792     }
793
794     /**
795      * EntityResolver interface.
796      * <p/>
797      * Allow the application to resolve external entities.
798      * <p/>
799      * Until <code>quartz.dtd</code> has a public ID, it must resolved as a
800      * system ID. Here's the order of resolution (if one fails, continue to the
801      * next).
802      * <ol>
803      * <li>Tries to resolve the <code>systemId</code> with <code>ClassLoader.getResourceAsStream(String)</code>.
804      * </li>
805      * <li>If the <code>systemId</code> starts with <code>QUARTZ_SYSTEM_ID_PREFIX</code>,
806      * then resolve the part after <code>QUARTZ_SYSTEM_ID_PREFIX</code> with
807      * <code>ClassLoader.getResourceAsStream(String)</code>.</li>
808      * <li>Else try to resolve <code>systemId</code> as a URL.
809      * <li>If <code>systemId</code> has a colon in it, create a new <code>URL</code>
810      * </li>
811      * <li>Else resolve <code>systemId</code> as a <code>File</code> and
812      * then call <code>File.toURL()</code>.</li>
813      * </li>
814      * </ol>
815      * <p/>
816      * If the <code>publicId</code> does exist, resolve it as a URL. If the
817      * <code>publicId</code> is the Quartz public ID, then resolve it locally.
818      *
819      * @param publicId
820      * The public identifier of the external entity being referenced,
821      * or null if none was supplied.
822      * @param systemId
823      * The system identifier of the external entity being referenced.
824      * @return An InputSource object describing the new input source, or null
825      * to request that the parser open a regular URI connection to the
826      * system identifier.
827      * @exception SAXException
828      * Any SAX exception, possibly wrapping another exception.
829      * @exception IOException
830      * A Java-specific IO exception, possibly the result of
831      * creating a new InputStream or Reader for the InputSource.
832      */

833     public InputSource JavaDoc resolveEntity(String JavaDoc publicId, String JavaDoc systemId) {
834         InputSource JavaDoc inputSource = null;
835
836         InputStream JavaDoc is = null;
837
838         URL JavaDoc url = null;
839
840         try {
841             if (publicId == null) {
842                 if (systemId != null) {
843                     // resolve Quartz Schema locally
844
if (QUARTZ_SCHEMA.equals(systemId)) {
845                         is = getClass().getResourceAsStream(QUARTZ_XSD);
846                     } else {
847                         is = getInputStream(systemId);
848     
849                         if (is == null) {
850                             int start = systemId.indexOf(QUARTZ_SYSTEM_ID_PREFIX);
851     
852                             if (start > -1) {
853                                 String JavaDoc fileName = systemId
854                                         .substring(QUARTZ_SYSTEM_ID_PREFIX.length());
855                                 is = getInputStream(fileName);
856                             } else {
857                                 if (systemId.indexOf(':') == -1) {
858                                     File JavaDoc file = new java.io.File JavaDoc(systemId);
859                                     url = file.toURL();
860                                 } else {
861                                     url = new URL JavaDoc(systemId);
862                                 }
863     
864                                 is = url.openStream();
865                             }
866                         }
867                     }
868                 }
869             } else {
870                 // resolve Quartz DTD locally
871
if (QUARTZ_PUBLIC_ID.equals(publicId)) {
872                     is = getClass().getResourceAsStream(QUARTZ_DTD);
873                 } else {
874                     url = new URL JavaDoc(systemId);
875                     is = url.openStream();
876                 }
877             }
878         } catch (Exception JavaDoc e) {
879             e.printStackTrace();
880         } finally {
881             if (is != null) {
882                 inputSource = new InputSource JavaDoc(is);
883                 inputSource.setPublicId(publicId);
884                 inputSource.setSystemId(systemId);
885             }
886
887         }
888
889         return inputSource;
890     }
891
892     /**
893      * ErrorHandler interface.
894      *
895      * Receive notification of a warning.
896      *
897      * @param e
898      * The error information encapsulated in a SAX parse exception.
899      * @exception SAXException
900      * Any SAX exception, possibly wrapping another exception.
901      */

902     public void warning(SAXParseException JavaDoc e) throws SAXException JavaDoc {
903         addValidationException(e);
904     }
905
906     /**
907      * ErrorHandler interface.
908      *
909      * Receive notification of a recoverable error.
910      *
911      * @param e
912      * The error information encapsulated in a SAX parse exception.
913      * @exception SAXException
914      * Any SAX exception, possibly wrapping another exception.
915      */

916     public void error(SAXParseException JavaDoc e) throws SAXException JavaDoc {
917         addValidationException(e);
918     }
919
920     /**
921      * ErrorHandler interface.
922      *
923      * Receive notification of a non-recoverable error.
924      *
925      * @param e
926      * The error information encapsulated in a SAX parse exception.
927      * @exception SAXException
928      * Any SAX exception, possibly wrapping another exception.
929      */

930     public void fatalError(SAXParseException JavaDoc e) throws SAXException JavaDoc {
931         addValidationException(e);
932     }
933
934     /**
935      * Adds a detected validation exception.
936      *
937      * @param e
938      * SAX exception.
939      */

940     protected void addValidationException(SAXException JavaDoc e) {
941         validationExceptions.add(e);
942     }
943
944     /**
945      * Resets the the number of detected validation exceptions.
946      */

947     protected void clearValidationExceptions() {
948         validationExceptions.clear();
949     }
950
951     /**
952      * Throws a ValidationException if the number of validationExceptions
953      * detected is greater than zero.
954      *
955      * @exception ValidationException
956      * DTD validation exception.
957      */

958     protected void maybeThrowValidationException() throws ValidationException {
959         if (validationExceptions.size() > 0) {
960             throw new ValidationException(validationExceptions);
961         }
962     }
963     
964     /**
965      * RuleSet for common Calendar tags.
966      *
967      * @author <a HREF="mailto:bonhamcm@thirdeyeconsulting.com">Chris Bonham</a>
968      */

969     public class CalendarRuleSet extends RuleSetBase {
970         protected String JavaDoc prefix;
971         protected String JavaDoc setNextMethodName;
972         
973         public CalendarRuleSet(String JavaDoc prefix, String JavaDoc setNextMethodName) {
974             super();
975             this.prefix = prefix;
976             this.setNextMethodName = setNextMethodName;
977         }
978
979         public void addRuleInstances(Digester digester) {
980             digester.addObjectCreate(prefix, CalendarBundle.class);
981             digester.addSetProperties(prefix, TAG_CLASS_NAME, "className");
982             digester.addBeanPropertySetter(prefix + "/" + TAG_NAME, "calendarName");
983             digester.addBeanPropertySetter(prefix + "/" + TAG_DESCRIPTION, "description");
984             digester.addSetNext(prefix, setNextMethodName);
985         }
986     }
987
988     /**
989      * RuleSet for common Trigger tags.
990      *
991      * @author <a HREF="mailto:bonhamcm@thirdeyeconsulting.com">Chris Bonham</a>
992      */

993     public class TriggerRuleSet extends RuleSetBase {
994         protected String JavaDoc prefix;
995         protected Class JavaDoc clazz;
996
997         public TriggerRuleSet(String JavaDoc prefix, Class JavaDoc clazz) {
998             super();
999             this.prefix = prefix;
1000            if (!Trigger.class.isAssignableFrom(clazz)) {
1001                throw new IllegalArgumentException JavaDoc("Class must be an instance of Trigger");
1002            }
1003            this.clazz = clazz;
1004        }
1005
1006        public void addRuleInstances(Digester digester) {
1007            digester.addObjectCreate(prefix, clazz);
1008            digester.addBeanPropertySetter(prefix + "/" + TAG_NAME, "name");
1009            digester.addBeanPropertySetter(prefix + "/" + TAG_GROUP, "group");
1010            digester.addBeanPropertySetter(prefix + "/" + TAG_DESCRIPTION, "description");
1011            digester.addBeanPropertySetter(prefix + "/" + TAG_VOLATILITY, "volatility");
1012            digester.addRule(prefix + "/" + TAG_MISFIRE_INSTRUCTION, new MisfireInstructionRule("misfireInstruction"));
1013            digester.addBeanPropertySetter(prefix + "/" + TAG_CALENDAR_NAME, "calendarName");
1014            digester.addObjectCreate(prefix + "/" + TAG_JOB_DATA_MAP, JobDataMap.class);
1015            digester.addCallMethod(prefix + "/" + TAG_JOB_DATA_MAP + "/" + TAG_ENTRY, "put", 2, new Class JavaDoc[] { Object JavaDoc.class, Object JavaDoc.class });
1016            digester.addCallParam(prefix + "/" + TAG_JOB_DATA_MAP + "/" + TAG_ENTRY + "/" + TAG_KEY, 0);
1017            digester.addCallParam(prefix + "/" + TAG_JOB_DATA_MAP + "/" + TAG_ENTRY + "/" + TAG_VALUE, 1);
1018            digester.addSetNext(prefix + "/" + TAG_JOB_DATA_MAP, "setJobDataMap");
1019            digester.addBeanPropertySetter(prefix + "/" + TAG_JOB_NAME, "jobName");
1020            digester.addBeanPropertySetter(prefix + "/" + TAG_JOB_GROUP, "jobGroup");
1021            Converter converter = new DateConverter(new String JavaDoc[] { XSD_DATE_FORMAT, DTD_DATE_FORMAT });
1022            digester.addRule(prefix + "/" + TAG_START_TIME, new SimpleConverterRule("startTime", converter, Date JavaDoc.class));
1023            digester.addRule(prefix + "/" + TAG_END_TIME, new SimpleConverterRule("endTime", converter, Date JavaDoc.class));
1024        }
1025    }
1026    
1027    /**
1028     * This rule is needed to fix <a HREF="http://jira.opensymphony.com/browse/QUARTZ-153">QUARTZ-153</a>.
1029     * <p>
1030     * Since the Jakarta Commons BeanUtils 1.6.x <code>ConvertUtils</code> class uses static utility
1031     * methods, the <code>DateConverter</code> and <code>TimeZoneConverter</code> were
1032     * overriding any previously registered converters for <code>java.util.Date</code> and
1033     * <code>java.util.TimeZone</code>.
1034     * <p>
1035     * Jakarta Commons BeanUtils 1.7.x fixes this issue by internally using per-context-classloader
1036     * pseudo-singletons (see <a HREF="http://jakarta.apache.org/commons/beanutils/commons-beanutils-1.7.0/RELEASE-NOTES.txt">
1037     * http://jakarta.apache.org/commons/beanutils/commons-beanutils-1.7.0/RELEASE-NOTES.txt</a>).
1038     * This ensures web applications in the same JVM are using independent converters
1039     * based on their classloaders. However, the environment for QUARTZ-153 started Quartz
1040     * using the <code>QuartzInitializationServlet</code> which started <code>JobInitializationPlugin</code>.
1041     * In this case, the web classloader instances would be the same.
1042     * <p>
1043     * To make sure the converters aren't overridden by the <code>JobSchedulingDataProcessor</code>,
1044     * it's easier to just override <code>BeanPropertySetterRule.end()</code> to convert the
1045     * body text to the specified class using the specified converter.
1046     *
1047     * @author <a HREF="mailto:bonhamcm@thirdeyeconsulting.com">Chris Bonham</a>
1048     */

1049    public class SimpleConverterRule extends BeanPropertySetterRule {
1050        private Converter converter;
1051        private Class JavaDoc clazz;
1052        
1053        /**
1054         * <p>Construct rule that sets the given property from the body text.</p>
1055         *
1056         * @param propertyName name of property to set
1057         * @param converter converter to use
1058         * @param clazz class to convert to
1059         */

1060        public SimpleConverterRule(String JavaDoc propertyName, Converter converter, Class JavaDoc clazz) {
1061            this.propertyName = propertyName;
1062            if (converter == null) {
1063                throw new IllegalArgumentException JavaDoc("Converter must not be null");
1064            }
1065            this.converter = converter;
1066            if (clazz == null) {
1067                throw new IllegalArgumentException JavaDoc("Class must not be null");
1068            }
1069            this.clazz = clazz;
1070        }
1071
1072        /**
1073         * Process the end of this element.
1074         *
1075         * @param namespace the namespace URI of the matching element, or an
1076         * empty string if the parser is not namespace aware or the element has
1077         * no namespace
1078         * @param name the local name if the parser is namespace aware, or just
1079         * the element name otherwise
1080         *
1081         * @exception NoSuchMethodException if the bean does not
1082         * have a writeable property of the specified name
1083         */

1084        public void end(String JavaDoc namespace, String JavaDoc name) throws Exception JavaDoc {
1085
1086            String JavaDoc property = propertyName;
1087
1088            if (property == null) {
1089                // If we don't have a specific property name,
1090
// use the element name.
1091
property = name;
1092            }
1093
1094            // Get a reference to the top object
1095
Object JavaDoc top = this.digester.peek();
1096
1097            // log some debugging information
1098
if (getDigester().getLogger().isDebugEnabled()) {
1099                getDigester().getLogger().debug("[BeanPropertySetterRule]{" + getDigester().getMatch() +
1100                        "} Set " + top.getClass().getName() + " property " +
1101                                   property + " with text " + bodyText);
1102            }
1103
1104            // Force an exception if the property does not exist
1105
// (BeanUtils.setProperty() silently returns in this case)
1106
if (top instanceof DynaBean) {
1107                DynaProperty desc =
1108                    ((DynaBean) top).getDynaClass().getDynaProperty(property);
1109                if (desc == null) {
1110                    throw new NoSuchMethodException JavaDoc ("Bean has no property named " + property);
1111                }
1112            } else /* this is a standard JavaBean */ {
1113                PropertyDescriptor JavaDoc desc =
1114                    PropertyUtils.getPropertyDescriptor(top, property);
1115                if (desc == null) {
1116                    throw new NoSuchMethodException JavaDoc("Bean has no property named " + property);
1117                }
1118            }
1119
1120            // Set the property only using this converter
1121
Object JavaDoc value = converter.convert(clazz, bodyText);
1122            PropertyUtils.setProperty(top, property, value);
1123        }
1124    }
1125    
1126    /**
1127     * This rule translates the trigger misfire instruction constant name into its
1128     * corresponding value.
1129     *
1130     * <p>
1131     * TODO Consider removing this class and using a
1132     * <code>org.apache.commons.digester.Substitutor</code> strategy once
1133     * Jakarta Commons Digester 1.6 is final.
1134     * </p>
1135     *
1136     * @author <a HREF="mailto:bonhamcm@thirdeyeconsulting.com">Chris Bonham</a>
1137     */

1138    public class MisfireInstructionRule extends BeanPropertySetterRule {
1139        /**
1140         * <p>Construct rule that sets the given property from the body text.</p>
1141         *
1142         * @param propertyName name of property to set
1143         */

1144        public MisfireInstructionRule(String JavaDoc propertyName) {
1145            this.propertyName = propertyName;
1146        }
1147
1148        /**
1149         * Process the body text of this element.
1150         *
1151         * @param namespace the namespace URI of the matching element, or an
1152         * empty string if the parser is not namespace aware or the element has
1153         * no namespace
1154         * @param name the local name if the parser is namespace aware, or just
1155         * the element name otherwise
1156         * @param text The text of the body of this element
1157         */

1158        public void body(String JavaDoc namespace, String JavaDoc name, String JavaDoc text)
1159            throws Exception JavaDoc {
1160            super.body(namespace, name, text);
1161            this.bodyText = getConstantValue(bodyText);
1162        }
1163
1164        /**
1165         * Returns the value for the constant name.
1166         * If the constant can't be found or any exceptions occur,
1167         * return 0.
1168         *
1169         * @param constantName constant name.
1170         * @return the value for the constant name.
1171         */

1172        private String JavaDoc getConstantValue(String JavaDoc constantName) {
1173            String JavaDoc value = "0";
1174
1175            Object JavaDoc top = this.digester.peek();
1176            if (top != null) {
1177                Class JavaDoc clazz = top.getClass();
1178                try {
1179                    java.lang.reflect.Field JavaDoc field = clazz.getField(constantName);
1180                    Object JavaDoc fieldValue = field.get(top);
1181                    if (fieldValue != null) {
1182                        value = fieldValue.toString();
1183                    }
1184                } catch (Exception JavaDoc e) {
1185                    // ignore
1186
}
1187            }
1188
1189            return value;
1190        }
1191    }
1192
1193    /**
1194     * <p>Standard {@link Converter} implementation that converts an incoming
1195     * String into a <code>java.util.Date</code> object, optionally using a
1196     * default value or throwing a {@link ConversionException} if a conversion
1197     * error occurs.</p>
1198     */

1199    public final class DateConverter implements Converter {
1200
1201        // ----------------------------------------------------------- Constructors
1202

1203        /**
1204         * Create a {@link Converter} that will throw a {@link ConversionException}
1205         * if a conversion error occurs.
1206         */

1207        public DateConverter() {
1208            this.defaultValue = null;
1209            this.useDefault = false;
1210        }
1211
1212        /**
1213         * Create a {@link Converter} that will return the specified default value
1214         * if a conversion error occurs.
1215         *
1216         * @param defaultValue The default value to be returned
1217         */

1218        public DateConverter(Object JavaDoc defaultValue) {
1219            this.defaultValue = defaultValue;
1220            this.useDefault = true;
1221        }
1222
1223        public DateConverter(String JavaDoc[] formats) {
1224            this();
1225            
1226            int len = formats.length;
1227            dateFormats = new DateFormat JavaDoc[len];
1228            for (int i = 0; i < len; i++) {
1229                dateFormats[i] = new SimpleDateFormat JavaDoc(formats[i]);
1230            }
1231        }
1232
1233        // ----------------------------------------------------- Instance Variables
1234

1235        /**
1236         * The default value specified to our Constructor, if any.
1237         */

1238        private Object JavaDoc defaultValue = null;
1239
1240        /**
1241         * Should we return the default value on conversion errors?
1242         */

1243        private boolean useDefault = true;
1244
1245        private DateFormat JavaDoc[] dateFormats;
1246
1247        // --------------------------------------------------------- Public Methods
1248

1249        /**
1250         * Convert the specified input object into an output object of the
1251         * specified type.
1252         *
1253         * @param type Data type to which this value should be converted
1254         * @param value The input value to be converted
1255         *
1256         * @exception ConversionException if conversion cannot be performed
1257         * successfully
1258         */

1259        public Object JavaDoc convert(Class JavaDoc type, Object JavaDoc value) {
1260
1261            if (value == null) {
1262                if (useDefault) {
1263                    return (defaultValue);
1264                } else {
1265                    return (null);
1266                }
1267            }
1268
1269            if (String JavaDoc.class.equals(type)) {
1270                if ((value instanceof Date JavaDoc) && (dateFormats != null)) {
1271                    return (dateFormats[0].format((Date JavaDoc) value));
1272                } else {
1273                    return (value.toString());
1274                }
1275            }
1276
1277            if (value instanceof Date JavaDoc) {
1278                return (value);
1279            }
1280
1281            try {
1282                if (Date JavaDoc.class.isAssignableFrom(type) && dateFormats != null) {
1283                    return parseDate(value);
1284                } else {
1285                    return (value.toString());
1286                }
1287            } catch (Exception JavaDoc e) {
1288                if (useDefault) {
1289                    return (defaultValue);
1290                } else {
1291                    throw new ConversionException(e);
1292                }
1293            }
1294        }
1295        
1296        protected Date JavaDoc parseDate(Object JavaDoc value) throws ParseException JavaDoc {
1297            Date JavaDoc date = null;
1298
1299            int len = dateFormats.length;
1300            for (int i = 0; i < len; i++) {
1301
1302                try {
1303                    date = (dateFormats[i].parse(value.toString()));
1304                    break;
1305                } catch (ParseException JavaDoc e) {
1306                    // if this is the last format, throw the exception
1307
if (i == (len - 1)) {
1308                        throw e;
1309                    }
1310                }
1311            }
1312
1313            return date;
1314        }
1315    }
1316
1317    /**
1318     * <p>Standard {@link Converter} implementation that converts an incoming
1319     * String into a <code>java.util.TimeZone</code> object throwing a
1320     * {@link ConversionException} if a conversion error occurs.</p>
1321     */

1322    public final class TimeZoneConverter implements Converter {
1323        // ----------------------------------------------------------- Constructors
1324

1325        /**
1326         * Create a {@link Converter} that will throw a {@link ConversionException}
1327         * if a conversion error occurs.
1328         */

1329        public TimeZoneConverter() {
1330        }
1331
1332        // --------------------------------------------------------- Public Methods
1333

1334        /**
1335         * Convert the specified input object into an output object of the
1336         * specified type.
1337         *
1338         * @param type Data type to which this value should be converted
1339         * @param value The input value to be converted
1340         *
1341         * @exception ConversionException if conversion cannot be performed
1342         * successfully
1343         */

1344        public Object JavaDoc convert(Class JavaDoc type, Object JavaDoc value) {
1345
1346            if (value == null) {
1347                return (null);
1348            }
1349
1350            if (value instanceof TimeZone JavaDoc) {
1351                return (value);
1352            }
1353
1354            try {
1355                if (String JavaDoc.class.equals(value.getClass())) {
1356                    return (TimeZone.getTimeZone((String JavaDoc) value));
1357                } else {
1358                    return (value.toString());
1359                }
1360            } catch (Exception JavaDoc e) {
1361                throw new ConversionException(e);
1362            }
1363        }
1364    }
1365}
1366
Popular Tags