001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software GmbH & Co. KG, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.scheduler;
029
030import org.opencms.configuration.CmsParameterConfiguration;
031import org.opencms.configuration.I_CmsConfigurationParameterHandler;
032import org.opencms.i18n.CmsMessageContainer;
033import org.opencms.main.CmsContextInfo;
034import org.opencms.main.CmsIllegalArgumentException;
035import org.opencms.main.CmsLog;
036import org.opencms.main.CmsRuntimeException;
037import org.opencms.main.OpenCms;
038import org.opencms.util.CmsStringUtil;
039
040import java.io.Serializable;
041import java.util.Collections;
042import java.util.Date;
043import java.util.SortedMap;
044import java.util.TreeMap;
045
046import javax.validation.Valid;
047
048import org.apache.commons.logging.Log;
049
050import org.quartz.CronExpression;
051import org.quartz.Trigger;
052
053/**
054 * Describes a scheduled job for the OpenCms scheduler.<p>
055 *
056 * <p>
057 * The time the scheduled job is executed is defined with Unix 'cron-like' definitions.
058 * </p>
059 *
060 * <p>
061 * For those unfamiliar with "cron", this means being able to create a firing
062 * schedule such as: "At 8:00am every Monday through Friday" or "At 1:30am
063 * every last Friday of the month".
064 * </p>
065 *
066 * <p>
067 * A "Cron-Expression" is a string comprised of 6 or 7 fields separated by
068 * white space. The 6 mandatory and 1 optional fields are as follows: <br>
069 *
070 * <table cellspacing="8">
071 * <tr>
072 * <th align="left">Field Name</th>
073 * <th align="left">&nbsp;</th>
074 * <th align="left">Allowed Values</th>
075 * <th align="left">&nbsp;</th>
076 * <th align="left">Allowed Special Characters</th>
077 * </tr>
078 * <tr>
079 * <td align="left"><code>Seconds</code></td>
080 * <td align="left">&nbsp;</td>
081 * <td align="left"><code>0-59</code></td>
082 * <td align="left">&nbsp;</td>
083 * <td align="left"><code>, - * /</code></td>
084 * </tr>
085 * <tr>
086 * <td align="left"><code>Minutes</code></td>
087 * <td align="left">&nbsp;</td>
088 * <td align="left"><code>0-59</code></td>
089 * <td align="left">&nbsp;</td>
090 * <td align="left"><code>, - * /</code></td>
091 * </tr>
092 * <tr>
093 * <td align="left"><code>Hours</code></td>
094 * <td align="left">&nbsp;</td>
095 * <td align="left"><code>0-23</code></td>
096 * <td align="left">&nbsp;</td>
097 * <td align="left"><code>, - * /</code></td>
098 * </tr>
099 * <tr>
100 * <td align="left"><code>Day-of-month</code></td>
101 * <td align="left">&nbsp;</td>
102 * <td align="left"><code>1-31</code></td>
103 * <td align="left">&nbsp;</td>
104 * <td align="left"><code>, - * ? / L C</code></td>
105 * </tr>
106 * <tr>
107 * <td align="left"><code>Month</code></td>
108 * <td align="left">&nbsp;</td>
109 * <td align="left"><code>1-12 or JAN-DEC</code></td>
110 * <td align="left">&nbsp;</td>
111 * <td align="left"><code>, - * /</code></td>
112 * </tr>
113 * <tr>
114 * <td align="left"><code>Day-of-Week</code></td>
115 * <td align="left">&nbsp;</td>
116 * <td align="left"><code>1-7 or SUN-SAT</code></td>
117 * <td align="left">&nbsp;</td>
118 * <td align="left"><code>, - * ? / L C #</code></td>
119 * </tr>
120 * <tr>
121 * <td align="left"><code>Year (Optional)</code></td>
122 * <td align="left">&nbsp;</td>
123 * <td align="left"><code>empty, 1970-2099</code></td>
124 * <td align="left">&nbsp;</td>
125 * <td align="left"><code>, - * /</code></td>
126 * </tr>
127 * </table>
128 * </p>
129 *
130 * <p>
131 * The '*' character is used to specify all values. For example, "*" in the
132 * minute field means "every minute".
133 * </p>
134 *
135 * <p>
136 * The '?' character is allowed for the day-of-month and day-of-week fields. It
137 * is used to specify 'no specific value'. This is useful when you need to
138 * specify something in one of the two fields, but not the other. See the
139 * examples below for clarification.
140 * </p>
141 *
142 * <p>
143 * The '-' character is used to specify ranges For example "10-12" in the hour
144 * field means "the hours 10, 11 and 12".
145 * </p>
146 *
147 * <p>
148 * The ',' character is used to specify additional values. For example
149 * "MON,WED,FRI" in the day-of-week field means "the days Monday, Wednesday,
150 * and Friday".
151 * </p>
152 *
153 * <p>
154 * The '/' character is used to specify increments. For example "0/15" in the
155 * seconds field means "the seconds 0, 15, 30, and 45". And "5/15" in the
156 * seconds field means "the seconds 5, 20, 35, and 50". You can also specify
157 * '/' after the '*' character - in this case '*' is equivalent to having '0'
158 * before the '/'.
159 * </p>
160 *
161 * <p>
162 * The 'L' character is allowed for the day-of-month and day-of-week fields.
163 * This character is short-hand for "last", but it has different meaning in
164 * each of the two fields. For example, the value "L" in the day-of-month field
165 * means "the last day of the month" - day 31 for January, day 28 for February
166 * on non-leap years. If used in the day-of-week field by itself, it simply
167 * means "7" or "SAT". But if used in the day-of-week field after another
168 * value, it means "the last xxx day of the month" - for example "6L" means
169 * "the last friday of the month". When using the 'L' option, it is important
170 * not to specify lists, or ranges of values, as you'll get confusing results.
171 * </p>
172 *
173 * <p>
174 * The 'W' character is allowed for the day-of-month field.  This character
175 * is used to specify the weekday (Monday-Friday) nearest the given day.  As an
176 * example, if you were to specify "15W" as the value for the day-of-month
177 * field, the meaning is: "the nearest weekday to the 15th of the month".  So
178 * if the 15th is a Saturday, the trigger will fire on Friday the 14th.  If the
179 * 15th is a Sunday, the trigger will fire on Monday the 16th.  If the 15th is
180 * a Tuesday, then it will fire on Tuesday the 15th.  However if you specify
181 * "1W" as the value for day-of-month, and the 1st is a Saturday, the trigger
182 * will fire on Monday the 3rd, as it will not 'jump' over the boundary of a
183 * month's days.  The 'W' character can only be specified when the day-of-month
184 * is a single day, not a range or list of days.
185 * </p>
186 *
187 * <p>
188 * The 'L' and 'W' characters can also be combined for the day-of-month
189 * expression to yield 'LW', which translates to "last weekday of the month".
190 * </p>
191 *
192 * <p>
193 * The '#' character is allowed for the day-of-week field. This character is
194 * used to specify "the nth" day of the month. For example, the value of
195 * "6#3" in the day-of-week field means the third Friday of the month (day 6 =
196 * Friday and "#3" = the 3rd one in the month). Other examples: "2#1" = the
197 * first Monday of the month and "4#5" = the fifth Wednesday of the month. Note
198 * that if you specify "#5" and there is not 5 of the given day-of-week in the
199 * month, then no firing will occur that month.
200 * </p>
201 *
202 * <p>
203 * The 'C' character is allowed for the day-of-month and day-of-week fields.
204 * This character is short-hand for "calendar". This means values are
205 * calculated against the associated calendar, if any. If no calendar is
206 * associated, then it is equivalent to having an all-inclusive calendar. A
207 * value of "5C" in the day-of-month field means "the first day included by the
208 * calendar on or after the 5th". A value of "1C" in the day-of-week field
209 * means "the first day included by the calendar on or after sunday".
210 * </p>
211 *
212 * <p>
213 * The legal characters and the names of months and days of the week are not
214 * case sensitive.
215 * </p>
216 *
217 * <p>
218 * Here are some full examples: <br><table cellspacing="8">
219 * <tr>
220 * <th align="left">Expression</th>
221 * <th align="left">&nbsp;</th>
222 * <th align="left">Meaning</th>
223 * </tr>
224 * <tr>
225 * <td align="left"><code>"0 0 12 * * ?"</code></td>
226 * <td align="left">&nbsp;</td>
227 * <td align="left"><code>Fire at 12pm (noon) every day</code></td>
228 * </tr>
229 * <tr>
230 * <td align="left"><code>"0 15 10 ? * *"</code></td>
231 * <td align="left">&nbsp;</td>
232 * <td align="left"><code>Fire at 10:15am every day</code></td>
233 * </tr>
234 * <tr>
235 * <td align="left"><code>"0 15 10 * * ?"</code></td>
236 * <td align="left">&nbsp;</td>
237 * <td align="left"><code>Fire at 10:15am every day</code></td>
238 * </tr>
239 * <tr>
240 * <td align="left"><code>"0 15 10 * * ? *"</code></td>
241 * <td align="left">&nbsp;</td>
242 * <td align="left"><code>Fire at 10:15am every day</code></td>
243 * </tr>
244 * <tr>
245 * <td align="left"><code>"0 15 10 * * ? 2005"</code></td>
246 * <td align="left">&nbsp;</td>
247 * <td align="left"><code>Fire at 10:15am every day during the year 2005</code>
248 * </td>
249 * </tr>
250 * <tr>
251 * <td align="left"><code>"0 * 14 * * ?"</code></td>
252 * <td align="left">&nbsp;</td>
253 * <td align="left"><code>Fire every minute starting at 2pm and ending at 2:59pm, every day</code>
254 * </td>
255 * </tr>
256 * <tr>
257 * <td align="left"><code>"0 0/5 14 * * ?"</code></td>
258 * <td align="left">&nbsp;</td>
259 * <td align="left"><code>Fire every 5 minutes starting at 2pm and ending at 2:55pm, every day</code>
260 * </td>
261 * </tr>
262 * <tr>
263 * <td align="left"><code>"0 0/5 14,18 * * ?"</code></td>
264 * <td align="left">&nbsp;</td>
265 * <td align="left"><code>Fire every 5 minutes starting at 2pm and ending at 2:55pm, AND fire every 5 minutes starting at 6pm and ending at 6:55pm, every day</code>
266 * </td>
267 * </tr>
268 * <tr>
269 * <td align="left"><code>"0 0-5 14 * * ?"</code></td>
270 * <td align="left">&nbsp;</td>
271 * <td align="left"><code>Fire every minute starting at 2pm and ending at 2:05pm, every day</code>
272 * </td>
273 * </tr>
274 * <tr>
275 * <td align="left"><code>"0 10,44 14 ? 3 WED"</code></td>
276 * <td align="left">&nbsp;</td>
277 * <td align="left"><code>Fire at 2:10pm and at 2:44pm every Wednesday in the month of March.</code>
278 * </td>
279 * </tr>
280 * <tr>
281 * <td align="left"><code>"0 15 10 ? * MON-FRI"</code></td>
282 * <td align="left">&nbsp;</td>
283 * <td align="left"><code>Fire at 10:15am every Monday, Tuesday, Wednesday, Thursday and Friday</code>
284 * </td>
285 * </tr>
286 * <tr>
287 * <td align="left"><code>"0 15 10 15 * ?"</code></td>
288 * <td align="left">&nbsp;</td>
289 * <td align="left"><code>Fire at 10:15am on the 15th day of every month</code>
290 * </td>
291 * </tr>
292 * <tr>
293 * <td align="left"><code>"0 15 10 L * ?"</code></td>
294 * <td align="left">&nbsp;</td>
295 * <td align="left"><code>Fire at 10:15am on the last day of every month</code>
296 * </td>
297 * </tr>
298 * <tr>
299 * <td align="left"><code>"0 15 10 ? * 6L"</code></td>
300 * <td align="left">&nbsp;</td>
301 * <td align="left"><code>Fire at 10:15am on the last Friday of every month</code>
302 * </td>
303 * </tr>
304 * <tr>
305 * <td align="left"><code>"0 15 10 ? * 6L"</code></td>
306 * <td align="left">&nbsp;</td>
307 * <td align="left"><code>Fire at 10:15am on the last Friday of every month</code>
308 * </td>
309 * </tr>
310 * <tr>
311 * <td align="left"><code>"0 15 10 ? * 6L 2002-2005"</code></td>
312 * <td align="left">&nbsp;</td>
313 * <td align="left"><code>Fire at 10:15am on every last friday of every month during the years 2002, 2003, 2004 and 2005</code>
314 * </td>
315 * </tr>
316 * <tr>
317 * <td align="left"><code>"0 15 10 ? * 6#3"</code></td>
318 * <td align="left">&nbsp;</td>
319 * <td align="left"><code>Fire at 10:15am on the third Friday of every month</code>
320 * </td>
321 * </tr>
322 * </table>
323 * </p>
324 *
325 * <p>
326 * Pay attention to the effects of '?' and '*' in the day-of-week and
327 * day-of-month fields!
328 * </p>
329 *
330 * <p>
331 * <b>NOTES:</b>
332 * <ul>
333 * <li>Support for the features described for the 'C' character is not
334 * complete.</li>
335 * <li>Support for specifying both a day-of-week and a day-of-month value is
336 * not complete (you'll need to use the '?' character in on of these fields).
337 * </li>
338 * <li>Be careful when setting fire times between mid-night and 1:00 AM -
339 * "daylight savings" can cause a skip or a repeat depending on whether the
340 * time moves back or jumps forward.</li>
341 * </ul>
342 * </p>
343 *
344 *
345 * @since 6.0.0
346 */
347public class CmsScheduledJobInfo implements I_CmsConfigurationParameterHandler, Serializable {
348
349    /** The log object for this class. */
350    private static final Log LOG = CmsLog.getLog(CmsScheduledJobInfo.class);
351
352    /** The serial version id. */
353    private static final long serialVersionUID = 7621446065755519582L;
354
355    /** Indicates if this job is currently active in the scheduler or not. */
356    private boolean m_active;
357
358    /** The name of the class to schedule. */
359    private String m_className;
360
361    /** The context information for the user to execute the job with. */
362    private CmsContextInfo m_context;
363
364    /** The cron expression for this scheduler job. */
365    private String m_cronExpression;
366
367    /** Indicates if the configuration of this job is finalized (frozen). */
368    private boolean m_frozen;
369
370    /** The id of this job. */
371    private String m_id;
372
373    /** Instance object of the scheduled job (only required when instance is re-used). */
374    private transient I_CmsScheduledJob m_jobInstance;
375
376    /** The name of the job (for information purposes). */
377    private String m_jobName;
378
379    /** Stores the next execution time. */
380    private Date m_nextFireTime;
381
382    /** The parameters used for this job entry. */
383    private SortedMap<String, String> m_parameters;
384
385    /** Stores the last job execution time. */
386    private Date m_previousFireTime;
387
388    /** Indicates if the job instance should be re-used if the job is run. */
389    private boolean m_reuseInstance;
390
391    /** The (cron) trigger used for scheduling this job. */
392    private Trigger m_trigger;
393
394    /**
395     * Default constructor.<p>
396     */
397    public CmsScheduledJobInfo() {
398
399        m_reuseInstance = false;
400        m_frozen = false;
401        // parameters are stored in a tree map
402        m_parameters = new TreeMap<String, String>();
403        // a job is active by default
404        m_active = true;
405    }
406
407    /**
408     * Constructor for creating a new job with all required parameters.<p>
409     *
410     * @param id the id of the job of <code>null</code> if a new id should be automatically generated
411     * @param jobName the display name of the job
412     * @param className the class name of the job, must be an instance of <code>{@link I_CmsScheduledJob}</code>
413     * @param context the OpenCms user context information to use when executing the job
414     * @param cronExpression the cron expression for scheduling the job
415     * @param reuseInstance indicates if the job class should be re-used
416     * @param active indicates if the job should be active in the scheduler
417     * @param parameters the job parameters
418     */
419    public CmsScheduledJobInfo(
420        String id,
421        String jobName,
422        String className,
423        CmsContextInfo context,
424        String cronExpression,
425        boolean reuseInstance,
426        boolean active,
427        SortedMap<String, String> parameters) {
428
429        m_frozen = false;
430        setId(id);
431        if (CmsStringUtil.isNotEmpty(jobName)) {
432            // job name is optional, if not present class name will be used
433            setJobName(jobName);
434        }
435        setClassName(className);
436        setContextInfo(context);
437        setCronExpression(cronExpression);
438        setReuseInstance(reuseInstance);
439        setActive(active);
440        setParameters(parameters);
441    }
442
443    /**
444     * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#addConfigurationParameter(java.lang.String, java.lang.String)
445     */
446    public void addConfigurationParameter(String paramName, String paramValue) {
447
448        checkFrozen();
449        // add the configured parameter
450        m_parameters.put(paramName, paramValue);
451        if (LOG.isDebugEnabled()) {
452            LOG.debug(
453                org.opencms.configuration.Messages.get().getBundle().key(
454                    org.opencms.configuration.Messages.LOG_ADD_CONFIG_PARAM_3,
455                    paramName,
456                    paramValue,
457                    this));
458        }
459    }
460
461    /**
462     * Clears the id of the job.<p>
463     *
464     * This is useful if you want to create a copy of a job without keeping the job id.
465     * Use <code>{@link CmsScheduledJobInfo#clone()}</code> first to create the copy,
466     * and then clear the id of the clone.<p>
467     */
468    public void clearId() {
469
470        setId(null);
471    }
472
473    /**
474     * Creates a clone of this scheduled job.<p>
475     *
476     * The clone will not be active in the scheduler until it is scheduled
477     * with <code>{@link CmsScheduleManager#scheduleJob(org.opencms.file.CmsObject, CmsScheduledJobInfo)}</code>.
478     * The job id returned by <code>{@link #getId()}</code> will be the same.
479     * The <code>{@link #isActive()}</code> flag will be set to false.
480     * The clones job instance class will be the same
481     * if the <code>{@link #isReuseInstance()}</code> flag is set.<p>
482     *
483     * @see java.lang.Object#clone()
484     */
485    @Override
486    public CmsScheduledJobInfo clone() {
487
488        CmsScheduledJobInfo result = new CmsScheduledJobInfo();
489
490        result.m_id = m_id;
491        result.m_active = false;
492        result.m_frozen = false;
493        result.m_className = m_className;
494        if (isReuseInstance()) {
495            result.m_jobInstance = m_jobInstance;
496        }
497        result.m_reuseInstance = m_reuseInstance;
498        result.m_context = (CmsContextInfo)m_context.clone();
499        result.m_cronExpression = m_cronExpression;
500        result.m_jobName = m_jobName;
501        result.m_parameters = new TreeMap<String, String>(m_parameters);
502        result.m_trigger = null;
503
504        return result;
505    }
506
507    /**
508     * Returns the name of the class to schedule.<p>
509     *
510     * @return the name of the class to schedule
511     */
512    public String getClassName() {
513
514        return m_className;
515    }
516
517    /**
518     * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#getConfiguration()
519     */
520    public CmsParameterConfiguration getConfiguration() {
521
522        // this configuration does not support parameters
523        if (LOG.isDebugEnabled()) {
524            LOG.debug(
525                org.opencms.configuration.Messages.get().getBundle().key(
526                    org.opencms.configuration.Messages.LOG_GET_CONFIGURATION_1,
527                    this));
528        }
529
530        return new CmsParameterConfiguration(getParameters());
531    }
532
533    /**
534     * Returns the context information for the user executing this job.<p>
535     *
536     * Please note: The context time returned by {@link org.opencms.file.CmsRequestContext#getRequestTime()}
537     * will be set to the time when this job was created.
538     * This can be relevant in case you want to perform VFS operations, because it will
539     * affect how resources are processed that have date released / date expired attributes set.<p>
540     *
541     * @return the context information for the user executing this job
542     */
543    @Valid
544    public CmsContextInfo getContextInfo() {
545
546        return m_context;
547    }
548
549    /**
550     * Returns the cron expression for this job entry.<p>
551     *
552     * @return the cron expression for this job entry
553     */
554    public String getCronExpression() {
555
556        return m_cronExpression;
557    }
558
559    /**
560     * Returns the next time at which this job will be executed, after the given time.<p>
561     *
562     * If this job will not be executed after the given time, <code>null</code> will be returned..<p>
563     *
564     * @param date the after which the next execution time should be calculated
565     * @return the next time at which this job will be executed, after the given time
566     */
567    public Date getExecutionTimeAfter(Date date) {
568
569        if (!m_active || (m_trigger == null)) {
570            // if the job is not active, no time can be calculated
571            return null;
572        }
573
574        return m_trigger.getFireTimeAfter(date);
575    }
576
577    /**
578     * Returns the next time at which this job will be executed.<p>
579     *
580     * If the job will not execute again, <code>null</code> will be returned.<p>
581     *
582     * @return the next time at which this job will be executed
583     */
584    public Date getExecutionTimeNext() {
585
586        if (!m_active || (m_trigger == null)) {
587            // if the job is not active, no time can be calculated
588            return null;
589        }
590        if (m_nextFireTime == null) {
591            // in case next time is not set, check if the trigger supplies a valid next fire time
592            Date next = m_trigger.getNextFireTime();
593            if (System.currentTimeMillis() < next.getTime()) {
594                m_nextFireTime = next;
595            }
596        }
597        return m_nextFireTime;
598    }
599
600    /**
601     * Returns the previous time at which this job will be executed.<p>
602     *
603     * If this job has not yet been executed, <code>null</code> will be returned.
604     *
605     * @return the previous time at which this job will be executed
606     */
607    public Date getExecutionTimePrevious() {
608
609        if (!m_active || (m_trigger == null)) {
610            // if the job is not active, no time can be calculated
611            return null;
612        }
613
614        return m_previousFireTime;
615    }
616
617    /**
618     * Returns the internal id of this job in the scheduler.<p>
619     *
620     * Can be used to remove this job from the scheduler with
621     * <code>{@link CmsScheduleManager#unscheduleJob(org.opencms.file.CmsObject, String)}</code>.<p>
622     *
623     * @return the internal id of this job in the scheduler
624     */
625    public String getId() {
626
627        return m_id;
628    }
629
630    /**
631     * Returns an instance of the configured job class.<p>
632     *
633     * If any error occurs during class invocaion, the error
634     * is written to the OpenCms log and <code>null</code> is returned.<p>
635     *
636     * @return an instance of the configured job class, or null if an error occurred
637     */
638    public synchronized I_CmsScheduledJob getJobInstance() {
639
640        if (m_jobInstance != null) {
641
642            if (LOG.isDebugEnabled()) {
643                LOG.debug(
644                    Messages.get().getBundle().key(
645                        Messages.LOG_REUSING_INSTANCE_1,
646                        m_jobInstance.getClass().getName()));
647            }
648
649            // job instance already initialized
650            return m_jobInstance;
651        }
652
653        I_CmsScheduledJob job = null;
654
655        try {
656            // create an instance of the OpenCms job class
657            job = (I_CmsScheduledJob)Class.forName(getClassName()).newInstance();
658        } catch (ClassNotFoundException e) {
659            LOG.error(Messages.get().getBundle().key(Messages.LOG_CLASS_NOT_FOUND_1, getClassName()), e);
660        } catch (IllegalAccessException e) {
661            LOG.error(Messages.get().getBundle().key(Messages.LOG_ILLEGAL_ACCESS_0), e);
662        } catch (InstantiationException e) {
663            LOG.error(Messages.get().getBundle().key(Messages.LOG_INSTANCE_GENERATION_0), e);
664        } catch (ClassCastException e) {
665            LOG.error(Messages.get().getBundle().key(Messages.LOG_BAD_INTERFACE_0), e);
666        }
667
668        if (m_reuseInstance) {
669            // job instance must be re-used
670            m_jobInstance = job;
671        }
672
673        if (LOG.isDebugEnabled()) {
674            LOG.debug(Messages.get().getBundle().key(Messages.LOG_JOB_CREATED_1, getClassName()));
675        }
676
677        // this should not flood the log files: if class name is wrong or jar files missing this will
678        // most likely persist until restart.
679        if (job == null) {
680            setActive(false);
681        }
682        return job;
683    }
684
685    /**
686     * Returns the job name.<p>
687     *
688     * @return the job name
689     */
690    public String getJobName() {
691
692        return m_jobName;
693    }
694
695    /**
696     * Returns the parameters.<p>
697     *
698     * @return the parameters
699     */
700    public SortedMap<String, String> getParameters() {
701
702        return m_parameters;
703    }
704
705    /**
706     * Finalizes (freezes) the configuration of this scheduler job entry.<p>
707     *
708     * After this job entry has been frozen, any attempt to change the
709     * configuration of this entry with one of the "set..." methods
710     * will lead to a <code>RuntimeException</code>.<p>
711     *
712     * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#initConfiguration()
713     */
714    public void initConfiguration() {
715
716        // simple default configuration does not need to be initialized
717        if (LOG.isDebugEnabled()) {
718            LOG.debug(
719                org.opencms.configuration.Messages.get().getBundle().key(
720                    org.opencms.configuration.Messages.LOG_INIT_CONFIGURATION_1,
721                    this));
722        }
723        setFrozen(true);
724    }
725
726    /**
727     * Returns <code>true</code> if this job is currently active in the scheduler.<p>
728     *
729     * @return <code>true</code> if this job is currently active in the scheduler
730     */
731    public boolean isActive() {
732
733        return m_active;
734    }
735
736    /**
737     * Returns true if the job instance class is reused for this job.<p>
738     *
739     * @return true if the job instance class is reused for this job
740     */
741    public boolean isReuseInstance() {
742
743        return m_reuseInstance;
744    }
745
746    /**
747     * Sets the active state of this job.<p>
748     *
749     * @param active the active state to set
750     */
751    public void setActive(boolean active) {
752
753        checkFrozen();
754        m_active = active;
755    }
756
757    /**
758     * Sets the name of the class to schedule.<p>
759     *
760     * @param className the class name to set
761     */
762    public void setClassName(String className) {
763
764        checkFrozen();
765        if (className != null) {
766            // remove leading or trailing white space
767            className = className.trim();
768        }
769        if (!CmsStringUtil.isValidJavaClassName(className)) {
770            CmsMessageContainer message = Messages.get().container(Messages.ERR_BAD_JOB_CLASS_NAME_1, className);
771            if (OpenCms.getRunLevel() > OpenCms.RUNLEVEL_2_INITIALIZING) {
772                throw new CmsIllegalArgumentException(message);
773            } else {
774                LOG.warn(message.key());
775            }
776        } else {
777            Class<?> jobClass;
778            try {
779                jobClass = Class.forName(className);
780                if (!I_CmsScheduledJob.class.isAssignableFrom(jobClass)) {
781                    CmsMessageContainer message = Messages.get().container(
782                        Messages.ERR_JOB_CLASS_BAD_INTERFACE_2,
783                        className,
784                        I_CmsScheduledJob.class.getName());
785
786                    if (OpenCms.getRunLevel() > OpenCms.RUNLEVEL_2_INITIALIZING) {
787                        throw new CmsIllegalArgumentException(message);
788                    } else {
789                        LOG.warn(message.key());
790                    }
791
792                }
793            } catch (ClassNotFoundException e) {
794                CmsMessageContainer message = Messages.get().container(Messages.ERR_JOB_CLASS_NOT_FOUND_1, className);
795                if (OpenCms.getRunLevel() > OpenCms.RUNLEVEL_2_INITIALIZING) {
796                    throw new CmsIllegalArgumentException(message);
797                } else {
798                    LOG.warn(message.key());
799                }
800
801            }
802        }
803        m_className = className;
804        if (getJobName() == null) {
805            // initialize job name with class name as default
806            setJobName(className);
807        }
808
809    }
810
811    /**
812     * Sets the context information for the user executing the job.<p>
813     *
814     * This will also "freeze" the context information that is set.<p>
815     *
816     * @param contextInfo the context information for the user executing the job
817     *
818     * @see CmsContextInfo#freeze()
819     */
820    public void setContextInfo(CmsContextInfo contextInfo) {
821
822        checkFrozen();
823        if (contextInfo == null) {
824            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_CONTEXT_INFO_0));
825        }
826        m_context = contextInfo;
827    }
828
829    /**
830     * Sets the cron expression for this job entry.<p>
831     *
832     * @param cronExpression the cron expression to set
833     */
834    @SuppressWarnings("unused")
835    public void setCronExpression(String cronExpression) {
836
837        checkFrozen();
838
839        try {
840            // check if the cron expression is valid
841            new CronExpression(cronExpression);
842        } catch (Exception e) {
843            throw new CmsIllegalArgumentException(
844                Messages.get().container(Messages.ERR_BAD_CRON_EXPRESSION_2, getJobName(), cronExpression));
845        }
846
847        m_cronExpression = cronExpression;
848    }
849
850    /**
851     * Sets the job name.<p>
852     *
853     * @param jobName the job name to set
854     */
855    public void setJobName(String jobName) {
856
857        checkFrozen();
858        if (CmsStringUtil.isEmpty(jobName) || !jobName.trim().equals(jobName)) {
859            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_JOB_NAME_1, jobName));
860
861        }
862        m_jobName = jobName;
863    }
864
865    /**
866     * Sets the job parameters.<p>
867     *
868     * @param parameters the parameters to set
869     */
870    public void setParameters(SortedMap<String, String> parameters) {
871
872        checkFrozen();
873        if (parameters == null) {
874            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_JOB_PARAMS_0));
875        }
876        // make sure the parameters are a sorted map
877        m_parameters = new TreeMap<String, String>(parameters);
878    }
879
880    /**
881     * Controls if the job instance class is reused for this job,
882     * of if a new instance is generated every time the job is run.<p>
883     *
884     * @param reuseInstance must be true if the job instance class is to be reused
885     */
886    public void setReuseInstance(boolean reuseInstance) {
887
888        checkFrozen();
889        m_reuseInstance = reuseInstance;
890    }
891
892    /**
893     * Checks if this job info configuration is frozen.<p>
894     *
895     * @throws CmsRuntimeException in case the configuration is already frozen
896     */
897    protected void checkFrozen() throws CmsRuntimeException {
898
899        if (m_frozen) {
900            throw new CmsRuntimeException(Messages.get().container(Messages.ERR_JOB_INFO_FROZEN_1, getJobName()));
901        }
902    }
903
904    /**
905     * Returns the Quartz trigger used for scheduling this job.<p>
906     *
907     * This is an internal operation that should only by performed by the
908     * <code>{@link CmsScheduleManager}</code>, never by using this API directly.<p>
909     *
910     * @return the Quartz trigger used for scheduling this job
911     */
912    protected Trigger getTrigger() {
913
914        return m_trigger;
915    }
916
917    /**
918     * Sets the "frozen" state of this job.<p>
919     *
920     * This is an internal operation to be used only by the <code>{@link CmsScheduleManager}</code>.<p>
921     *
922     * @param frozen the "frozen" state to set
923     */
924    protected synchronized void setFrozen(boolean frozen) {
925
926        if (frozen && !m_frozen) {
927            // "freeze" the job configuration
928            m_parameters = Collections.unmodifiableSortedMap(m_parameters);
929            m_context.freeze();
930            m_frozen = true;
931        } else if (!frozen && m_frozen) {
932            // "unfreeze" the job configuration
933            m_parameters = new TreeMap<String, String>(m_parameters);
934            m_frozen = false;
935        }
936    }
937
938    /**
939     * Sets the is used for scheduling this job.<p>
940     *
941     * This is an internal operation that should only by performed by the
942     * <code>{@link CmsScheduleManager}</code>, never by using this API directly.<p>
943     *
944     * @param id the id to set
945     */
946    protected void setId(String id) {
947
948        checkFrozen();
949        m_id = id;
950    }
951
952    /**
953     * Sets the next execution time.<p>
954     *
955     * @param nextFire the next execution time
956     */
957    protected void setNextFireTime(Date nextFire) {
958
959        m_nextFireTime = nextFire;
960    }
961
962    /**
963     * Sets the previous execution time.<p>
964     *
965     * @param fireTime the previous execution time
966     */
967    protected void setPreviousFireTime(Date fireTime) {
968
969        m_previousFireTime = fireTime;
970    }
971
972    /**
973     * Sets the Quartz trigger used for scheduling this job.<p>
974     *
975     * This is an internal operation that should only by performed by the
976     * <code>{@link CmsScheduleManager}</code>, never by using this API directly.<p>
977     *
978     * @param trigger the Quartz trigger to set
979     */
980    protected void setTrigger(Trigger trigger) {
981
982        checkFrozen();
983        m_trigger = trigger;
984    }
985
986    /**
987     * Updates the request time in the internal context information of the user with the current system time.<p>
988     *
989     * This is required before executing the job, otherwise the context information request time would be the time
990     * the context object was initially created.<p>
991     */
992    protected void updateContextRequestTime() {
993
994        CmsContextInfo context = (CmsContextInfo)m_context.clone();
995        context.setRequestTime(System.currentTimeMillis());
996        context.freeze();
997        m_context = context;
998    }
999}