001: /*
002: * Copyright 2004-2005 OpenSymphony
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy
006: * of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations
014: * under the License.
015: *
016: */
017:
018: /*
019: * Previously Copyright (c) 2001-2004 James House
020: */
021: package org.quartz;
022:
023: import java.util.HashSet;
024: import java.util.Set;
025:
026: import org.apache.commons.collections.SetUtils;
027: import org.quartz.utils.Key;
028:
029: /**
030: * <p>
031: * Conveys the detail properties of a given <code>Job</code> instance.
032: * </p>
033: *
034: * <p>
035: * Quartz does not store an actual instance of a <code>Job</code> class, but
036: * instead allows you to define an instance of one, through the use of a <code>JobDetail</code>.
037: * </p>
038: *
039: * <p>
040: * <code>Job</code>s have a name and group associated with them, which
041: * should uniquely identify them within a single <code>{@link Scheduler}</code>.
042: * </p>
043: *
044: * <p>
045: * <code>Trigger</code>s are the 'mechanism' by which <code>Job</code>s
046: * are scheduled. Many <code>Trigger</code>s can point to the same <code>Job</code>,
047: * but a single <code>Trigger</code> can only point to one <code>Job</code>.
048: * </p>
049: *
050: * @see Job
051: * @see StatefulJob
052: * @see JobDataMap
053: * @see Trigger
054: *
055: * @author James House
056: * @author Sharada Jambula
057: */
058: public class JobDetail implements Cloneable, java.io.Serializable {
059:
060: /*
061: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
062: *
063: * Data members.
064: *
065: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
066: */
067:
068: private String name;
069:
070: private String group = Scheduler.DEFAULT_GROUP;
071:
072: private String description;
073:
074: private Class jobClass;
075:
076: private JobDataMap jobDataMap;
077:
078: private boolean volatility = false;
079:
080: private boolean durability = false;
081:
082: private boolean shouldRecover = false;
083:
084: private Set jobListeners = SetUtils.orderedSet(new HashSet());
085:
086: private transient Key key = null;
087:
088: /*
089: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
090: *
091: * Constructors.
092: *
093: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
094: */
095:
096: /**
097: * <p>
098: * Create a <code>JobDetail</code> with no specified name or group, and
099: * the default settings of all the other properties.
100: * </p>
101: *
102: * <p>
103: * Note that the {@link #setName(String)},{@link #setGroup(String)}and
104: * {@link #setJobClass(Class)}methods must be called before the job can be
105: * placed into a {@link Scheduler}
106: * </p>
107: */
108: public JobDetail() {
109: // do nothing...
110: }
111:
112: /**
113: * <p>
114: * Create a <code>JobDetail</code> with the given name, and group, and
115: * the default settings of all the other properties.
116: * </p>
117: *
118: * @param group if <code>null</code>, Scheduler.DEFAULT_GROUP will be used.
119: *
120: * @exception IllegalArgumentException
121: * if nameis null or empty, or the group is an empty string.
122: */
123: public JobDetail(String name, String group, Class jobClass) {
124: setName(name);
125: setGroup(group);
126: setJobClass(jobClass);
127: }
128:
129: /**
130: * <p>
131: * Create a <code>JobDetail</code> with the given name, and group, and
132: * the given settings of all the other properties.
133: * </p>
134: *
135: * @param group if <code>null</code>, Scheduler.DEFAULT_GROUP will be used.
136: *
137: * @exception IllegalArgumentException
138: * if nameis null or empty, or the group is an empty string.
139: */
140: public JobDetail(String name, String group, Class jobClass,
141: boolean volatility, boolean durability, boolean recover) {
142: setName(name);
143: setGroup(group);
144: setJobClass(jobClass);
145: setVolatility(volatility);
146: setDurability(durability);
147: setRequestsRecovery(recover);
148: }
149:
150: /*
151: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
152: *
153: * Interface.
154: *
155: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
156: */
157:
158: /**
159: * <p>
160: * Get the name of this <code>Job</code>.
161: * </p>
162: */
163: public String getName() {
164: return name;
165: }
166:
167: /**
168: * <p>
169: * Set the name of this <code>Job</code>.
170: * </p>
171: *
172: * @exception IllegalArgumentException
173: * if name is null or empty.
174: */
175: public void setName(String name) {
176: if (name == null || name.trim().length() == 0) {
177: throw new IllegalArgumentException(
178: "Job name cannot be empty.");
179: }
180:
181: this .name = name;
182: }
183:
184: /**
185: * <p>
186: * Get the group of this <code>Job</code>.
187: * </p>
188: */
189: public String getGroup() {
190: return group;
191: }
192:
193: /**
194: * <p>
195: * Set the group of this <code>Job</code>.
196: * </p>
197: *
198: * @param group if <code>null</code>, Scheduler.DEFAULT_GROUP will be used.
199: *
200: * @exception IllegalArgumentException
201: * if the group is an empty string.
202: */
203: public void setGroup(String group) {
204: if (group != null && group.trim().length() == 0) {
205: throw new IllegalArgumentException(
206: "Group name cannot be empty.");
207: }
208:
209: if (group == null) {
210: group = Scheduler.DEFAULT_GROUP;
211: }
212:
213: this .group = group;
214: }
215:
216: /**
217: * <p>
218: * Returns the 'full name' of the <code>JobDetail</code> in the format
219: * "group.name".
220: * </p>
221: */
222: public String getFullName() {
223: return group + "." + name;
224: }
225:
226: public Key getKey() {
227: if (key == null) {
228: key = new Key(getName(), getGroup());
229: }
230:
231: return key;
232: }
233:
234: /**
235: * <p>
236: * Return the description given to the <code>Job</code> instance by its
237: * creator (if any).
238: * </p>
239: *
240: * @return null if no description was set.
241: */
242: public String getDescription() {
243: return description;
244: }
245:
246: /**
247: * <p>
248: * Set a description for the <code>Job</code> instance - may be useful
249: * for remembering/displaying the purpose of the job, though the
250: * description has no meaning to Quartz.
251: * </p>
252: */
253: public void setDescription(String description) {
254: this .description = description;
255: }
256:
257: /**
258: * <p>
259: * Get the instance of <code>Job</code> that will be executed.
260: * </p>
261: */
262: public Class getJobClass() {
263: return jobClass;
264: }
265:
266: /**
267: * <p>
268: * Set the instance of <code>Job</code> that will be executed.
269: * </p>
270: *
271: * @exception IllegalArgumentException
272: * if jobClass is null or the class is not a <code>Job</code>.
273: */
274: public void setJobClass(Class jobClass) {
275: if (jobClass == null) {
276: throw new IllegalArgumentException(
277: "Job class cannot be null.");
278: }
279:
280: if (!Job.class.isAssignableFrom(jobClass)) {
281: throw new IllegalArgumentException(
282: "Job class must implement the Job interface.");
283: }
284:
285: this .jobClass = jobClass;
286: }
287:
288: /**
289: * <p>
290: * Get the <code>JobDataMap</code> that is associated with the <code>Job</code>.
291: * </p>
292: */
293: public JobDataMap getJobDataMap() {
294: if (jobDataMap == null) {
295: jobDataMap = new JobDataMap();
296: }
297: return jobDataMap;
298: }
299:
300: /**
301: * <p>
302: * Set the <code>JobDataMap</code> to be associated with the <code>Job</code>.
303: * </p>
304: */
305: public void setJobDataMap(JobDataMap jobDataMap) {
306: this .jobDataMap = jobDataMap;
307: }
308:
309: /**
310: * <p>
311: * Validates whether the properties of the <code>JobDetail</code> are
312: * valid for submission into a <code>Scheduler</code>.
313: *
314: * @throws IllegalStateException
315: * if a required property (such as Name, Group, Class) is not
316: * set.
317: */
318: public void validate() throws SchedulerException {
319: if (name == null) {
320: throw new SchedulerException("Job's name cannot be null",
321: SchedulerException.ERR_CLIENT_ERROR);
322: }
323:
324: if (group == null) {
325: throw new SchedulerException("Job's group cannot be null",
326: SchedulerException.ERR_CLIENT_ERROR);
327: }
328:
329: if (jobClass == null) {
330: throw new SchedulerException("Job's class cannot be null",
331: SchedulerException.ERR_CLIENT_ERROR);
332: }
333: }
334:
335: /**
336: * <p>
337: * Set whether or not the <code>Job</code> should be persisted in the
338: * <code>{@link org.quartz.spi.JobStore}</code> for re-use after program
339: * restarts.
340: * </p>
341: *
342: * <p>
343: * If not explicitly set, the default value is <code>false</code>.
344: * </p>
345: */
346: public void setVolatility(boolean volatility) {
347: this .volatility = volatility;
348: }
349:
350: /**
351: * <p>
352: * Set whether or not the <code>Job</code> should remain stored after it
353: * is orphaned (no <code>{@link Trigger}s</code> point to it).
354: * </p>
355: *
356: * <p>
357: * If not explicitly set, the default value is <code>false</code>.
358: * </p>
359: */
360: public void setDurability(boolean durability) {
361: this .durability = durability;
362: }
363:
364: /**
365: * <p>
366: * Set whether or not the the <code>Scheduler</code> should re-execute
367: * the <code>Job</code> if a 'recovery' or 'fail-over' situation is
368: * encountered.
369: * </p>
370: *
371: * <p>
372: * If not explicitly set, the default value is <code>false</code>.
373: * </p>
374: *
375: * @see JobExecutionContext#isRecovering()
376: */
377: public void setRequestsRecovery(boolean shouldRecover) {
378: this .shouldRecover = shouldRecover;
379: }
380:
381: /**
382: * <p>
383: * Whether or not the <code>Job</code> should not be persisted in the
384: * <code>{@link org.quartz.spi.JobStore}</code> for re-use after program
385: * restarts.
386: * </p>
387: *
388: * <p>
389: * If not explicitly set, the default value is <code>false</code>.
390: * </p>
391: *
392: * @return <code>true</code> if the <code>Job</code> should be garbage
393: * collected along with the <code>{@link Scheduler}</code>.
394: */
395: public boolean isVolatile() {
396: return volatility;
397: }
398:
399: /**
400: * <p>
401: * Whether or not the <code>Job</code> should remain stored after it is
402: * orphaned (no <code>{@link Trigger}s</code> point to it).
403: * </p>
404: *
405: * <p>
406: * If not explicitly set, the default value is <code>false</code>.
407: * </p>
408: *
409: * @return <code>true</code> if the Job should remain persisted after
410: * being orphaned.
411: */
412: public boolean isDurable() {
413: return durability;
414: }
415:
416: /**
417: * <p>
418: * Whether or not the <code>Job</code> implements the interface <code>{@link StatefulJob}</code>.
419: * </p>
420: */
421: public boolean isStateful() {
422: if (jobClass == null) {
423: return false;
424: }
425:
426: return (StatefulJob.class.isAssignableFrom(jobClass));
427: }
428:
429: /**
430: * <p>
431: * Instructs the <code>Scheduler</code> whether or not the <code>Job</code>
432: * should be re-executed if a 'recovery' or 'fail-over' situation is
433: * encountered.
434: * </p>
435: *
436: * <p>
437: * If not explicitly set, the default value is <code>false</code>.
438: * </p>
439: *
440: * @see JobExecutionContext#isRecovering()
441: */
442: public boolean requestsRecovery() {
443: return shouldRecover;
444: }
445:
446: /**
447: * <p>
448: * Add the specified name of a <code>{@link JobListener}</code> to the
449: * end of the <code>Job</code>'s list of listeners.
450: * </p>
451: */
452: public void addJobListener(String name) {
453: if (jobListeners.add(name) == false) {
454: throw new IllegalArgumentException("Job listener '" + name
455: + "' is already registered for job detail: "
456: + getFullName());
457: }
458: }
459:
460: /**
461: * <p>
462: * Remove the specified name of a <code>{@link JobListener}</code> from
463: * the <code>Job</code>'s list of listeners.
464: * </p>
465: *
466: * @return true if the given name was found in the list, and removed
467: */
468: public boolean removeJobListener(String name) {
469: return jobListeners.remove(name);
470: }
471:
472: /**
473: * <p>
474: * Returns an array of <code>String</code> s containing the names of all
475: * <code>{@link JobListener}</code>s assigned to the <code>Job</code>,
476: * in the order in which they should be notified.
477: * </p>
478: */
479: public String[] getJobListenerNames() {
480: return (String[]) jobListeners.toArray(new String[jobListeners
481: .size()]);
482: }
483:
484: /**
485: * <p>
486: * Return a simple string representation of this object.
487: * </p>
488: */
489: public String toString() {
490: return "JobDetail '"
491: + getFullName()
492: + "': jobClass: '"
493: + ((getJobClass() == null) ? null : getJobClass()
494: .getName()) + " isStateful: " + isStateful()
495: + " isVolatile: " + isVolatile() + " isDurable: "
496: + isDurable() + " requestsRecovers: "
497: + requestsRecovery();
498: }
499:
500: public Object clone() {
501: JobDetail copy;
502: try {
503: copy = (JobDetail) super .clone();
504: copy.jobListeners = SetUtils.orderedSet(new HashSet(
505: jobListeners));
506: if (jobDataMap != null) {
507: copy.jobDataMap = (JobDataMap) jobDataMap.clone();
508: }
509: } catch (CloneNotSupportedException ex) {
510: throw new IncompatibleClassChangeError("Not Cloneable.");
511: }
512:
513: return copy;
514: }
515: }
|