org.opencms.scheduler.CmsScheduleManager Maven / Gradle / Ivy
Show all versions of opencms-core Show documentation
/*
* This library is part of OpenCms -
* the Open Source Content Management System
*
* Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* For further information about Alkacon Software GmbH & Co. KG, please see the
* company website: http://www.alkacon.com
*
* For further information about OpenCms, please see the
* project website: http://www.opencms.org
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.opencms.scheduler;
import org.opencms.file.CmsObject;
import org.opencms.i18n.CmsMessageContainer;
import org.opencms.main.CmsIllegalArgumentException;
import org.opencms.main.CmsLog;
import org.opencms.main.OpenCms;
import org.opencms.security.CmsRole;
import org.opencms.security.CmsRoleViolationException;
import org.opencms.util.CmsStringUtil;
import org.opencms.util.CmsUUID;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.quartz.CronScheduleBuilder;
import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.JobDetailImpl;
import org.quartz.impl.StdSchedulerFactory;
/**
* Manages the OpenCms scheduled jobs.
*
* Please see the documentation of the class {@link org.opencms.scheduler.CmsScheduledJobInfo}
* for a full description of the OpenCms scheduling capabilities.
*
* The OpenCms scheduler implementation internally uses the
* Quartz scheduler from
* the OpenSymphony project.
*
* This manager class implements the org.quartz.Job
interface
* and wraps all calls to the {@link org.opencms.scheduler.I_CmsScheduledJob} implementing
* classes.
*
* @since 6.0.0
*
* @see org.opencms.scheduler.CmsScheduledJobInfo
*/
public class CmsScheduleManager implements Job {
/** Key for the scheduled job description in the job data map. */
public static final String SCHEDULER_JOB_INFO = "org.opencms.scheduler.CmsScheduledJobInfo";
/** The log object for this class. */
private static final Log LOG = CmsLog.getLog(CmsScheduleManager.class);
/** The Admin context used for creation of users for the individual jobs. */
private CmsObject m_adminCms;
/** The list of job entries from the configuration. */
private List m_configuredJobs;
/** The list of scheduled jobs. */
private List m_jobs;
/** The initialized scheduler. */
private Scheduler m_scheduler;
/**
* Default constructor for the scheduler manager,
* used only when a new job is scheduled.
*/
public CmsScheduleManager() {
// important: this constructor is always called when a new job is
// generated, so it _must_ remain empty
}
/**
* Used by the configuration to create a new Scheduler during system startup.
*
* @param configuredJobs the jobs from the configuration
*/
public CmsScheduleManager(List configuredJobs) {
m_configuredJobs = configuredJobs;
int size = 0;
if (m_configuredJobs != null) {
size = m_configuredJobs.size();
}
if (CmsLog.INIT.isInfoEnabled()) {
CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_SCHEDULER_CREATED_1, Integer.valueOf(size)));
}
}
/**
* Implementation of the Quartz job interface.
*
* The architecture is that this scheduler manager generates
* a new (empty) instance of itself for every OpenCms job scheduled with Quartz.
* When the Quartz job is executed, the configured
* implementation of {@link I_CmsScheduledJob} will be called from this method.
*
* @see org.quartz.Job#execute(org.quartz.JobExecutionContext)
*/
public void execute(JobExecutionContext context) {
JobDataMap jobData = context.getJobDetail().getJobDataMap();
CmsScheduledJobInfo jobInfo = (CmsScheduledJobInfo)jobData.get(SCHEDULER_JOB_INFO);
if (jobInfo == null) {
LOG.error(
Messages.get().getBundle().key(
Messages.LOG_INVALID_JOB_1,
((JobDetailImpl)context.getJobDetail()).getFullName()));
// can not continue
return;
}
// update the execution times in job info
jobInfo.setPreviousFireTime(context.getFireTime());
jobInfo.setNextFireTime(context.getNextFireTime());
executeJob(jobInfo);
}
/**
* Given a job ID, this directly executes the corresponding job.
*
* @param jobId the job id
*/
public void executeDirectly(String jobId) {
final CmsScheduledJobInfo jobInfo = (CmsScheduledJobInfo)getJob(jobId).clone();
if (jobInfo == null) {
LOG.error(Messages.get().getBundle().key(Messages.LOG_INVALID_JOB_1, "null"));
return;
}
Thread thread = new Thread() {
/**
* @see java.lang.Thread#run()
*/
@Override
public void run() {
executeJob(jobInfo);
}
};
thread.start();
}
/**
* Returns the currently scheduled job description identified by the given id.
*
* @param id the job id
*
* @return a job or null
if not found
*/
public CmsScheduledJobInfo getJob(String id) {
Iterator it = m_jobs.iterator();
while (it.hasNext()) {
CmsScheduledJobInfo job = it.next();
if (job.getId().equals(id)) {
return job;
}
}
// not found
return null;
}
/**
* Returns the currently scheduled job descriptions in an unmodifiable list.
*
* The objects in the List are of type {@link CmsScheduledJobInfo}
.
*
* @return the currently scheduled job descriptions in an unmodifiable list
*/
public List getJobs() {
return Collections.unmodifiableList(m_jobs);
}
/**
* Initializes the OpenCms scheduler.
*
* @param adminCms an OpenCms context object that must have been initialized with "Admin" permissions
*
* @throws CmsRoleViolationException if the user has insufficient role permissions
*/
public synchronized void initialize(CmsObject adminCms) throws CmsRoleViolationException {
if (OpenCms.getRunLevel() > OpenCms.RUNLEVEL_1_CORE_OBJECT) {
// simple unit tests will have runlevel 1 and no CmsObject
OpenCms.getRoleManager().checkRole(adminCms, CmsRole.WORKPLACE_MANAGER);
}
// the list of job entries
m_jobs = new ArrayList();
// save the admin cms
m_adminCms = adminCms;
// Quartz scheduler settings
Properties properties = new Properties();
properties.put(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME, "OpenCmsScheduler");
properties.put(StdSchedulerFactory.PROP_SCHED_THREAD_NAME, "OpenCms: Scheduler");
properties.put(StdSchedulerFactory.PROP_SCHED_RMI_EXPORT, CmsStringUtil.FALSE);
properties.put(StdSchedulerFactory.PROP_SCHED_RMI_PROXY, CmsStringUtil.FALSE);
properties.put(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, CmsSchedulerThreadPool.class.getName());
properties.put(StdSchedulerFactory.PROP_JOB_STORE_CLASS, "org.quartz.simpl.RAMJobStore");
// this will be required in quartz versions from 1.6, but constants are not supported in earlier versions
properties.put("org.quartz.scheduler.jmx.export", CmsStringUtil.FALSE);
properties.put("org.quartz.scheduler.jmx.proxy", CmsStringUtil.FALSE);
try {
// initialize the Quartz scheduler
SchedulerFactory schedulerFactory = new StdSchedulerFactory(properties);
m_scheduler = schedulerFactory.getScheduler();
} catch (Exception e) {
LOG.error(Messages.get().getBundle().key(Messages.LOG_NO_SCHEDULER_0), e);
// can not continue
m_scheduler = null;
return;
}
if (CmsLog.INIT.isInfoEnabled()) {
CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_SCHEDULER_INITIALIZED_0));
}
if (m_configuredJobs != null) {
// add all jobs from the system configuration
for (int i = 0; i < m_configuredJobs.size(); i++) {
try {
CmsScheduledJobInfo job = m_configuredJobs.get(i);
scheduleJob(adminCms, job);
} catch (CmsSchedulerException e) {
// ignore this job, but keep scheduling the other jobs
// note: the log is has already been written
}
}
}
try {
// start the scheduler
m_scheduler.start();
} catch (Exception e) {
LOG.error(Messages.get().getBundle().key(Messages.LOG_CANNOT_START_SCHEDULER_0), e);
// can not continue
m_scheduler = null;
return;
}
if (CmsLog.INIT.isInfoEnabled()) {
CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_SCHEDULER_STARTED_0));
CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_SCHEDULER_CONFIG_FINISHED_0));
}
}
/**
* Adds a new job to the scheduler.
*
* @param cms an OpenCms context object that must have been initialized with "Admin" permissions
* @param jobInfo the job info describing the job to schedule
*
* @throws CmsRoleViolationException if the user has insufficient role permissions
* @throws CmsSchedulerException if the job could not be scheduled for any reason
*/
public synchronized void scheduleJob(CmsObject cms, CmsScheduledJobInfo jobInfo)
throws CmsRoleViolationException, CmsSchedulerException {
if (OpenCms.getRunLevel() > OpenCms.RUNLEVEL_1_CORE_OBJECT) {
// simple unit tests will have runlevel 1 and no CmsObject
OpenCms.getRoleManager().checkRole(cms, CmsRole.WORKPLACE_MANAGER);
}
if ((jobInfo == null) || (jobInfo.getClassName() == null)) {
// prevent NPE
CmsMessageContainer message = Messages.get().container(Messages.ERR_INVALID_JOB_CONFIGURATION_0);
LOG.error(message.key());
// can not continue
throw new CmsSchedulerException(message);
}
if (m_scheduler == null) {
CmsMessageContainer message = Messages.get().container(Messages.ERR_NO_SCHEDULER_1, jobInfo.getJobName());
LOG.error(message.key());
// can not continue
throw new CmsSchedulerException(message);
}
Class> jobClass;
try {
jobClass = Class.forName(jobInfo.getClassName());
if (!I_CmsScheduledJob.class.isAssignableFrom(jobClass)) {
// class does not implement required interface
CmsMessageContainer message = Messages.get().container(
Messages.ERR_JOB_CLASS_BAD_INTERFACE_2,
jobInfo.getClassName(),
I_CmsScheduledJob.class.getName());
LOG.error(message.key());
if (OpenCms.getRunLevel() > OpenCms.RUNLEVEL_2_INITIALIZING) {
throw new CmsIllegalArgumentException(message);
} else {
jobInfo.setActive(false);
}
}
} catch (ClassNotFoundException e) {
// class does not exist
CmsMessageContainer message = Messages.get().container(
Messages.ERR_JOB_CLASS_NOT_FOUND_1,
jobInfo.getClassName());
LOG.error(message.key());
if (OpenCms.getRunLevel() > OpenCms.RUNLEVEL_2_INITIALIZING) {
throw new CmsIllegalArgumentException(message);
} else {
jobInfo.setActive(false);
}
}
String jobId = jobInfo.getId();
boolean idCreated = false;
if (jobId == null) {
// generate a new job id
CmsUUID jobUUID = new CmsUUID();
jobId = "OpenCmsJob_".concat(jobUUID.toString());
jobInfo.setId(jobId);
idCreated = true;
}
// generate Quartz job trigger
Trigger trigger;
try {
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(jobInfo.getCronExpression());
TriggerBuilder triggerBuilder = TriggerBuilder.newTrigger();
triggerBuilder.withSchedule(scheduleBuilder);
triggerBuilder.withIdentity(jobId, Scheduler.DEFAULT_GROUP);
trigger = triggerBuilder.build();
} catch (Exception e) {
if (idCreated) {
jobInfo.setId(null);
}
CmsMessageContainer message = Messages.get().container(
Messages.ERR_BAD_CRON_EXPRESSION_2,
jobInfo.getJobName(),
jobInfo.getCronExpression());
LOG.error(message.key());
// can not continue
throw new CmsSchedulerException(message);
}
CmsScheduledJobInfo oldJob = null;
if (!idCreated) {
// this job is already scheduled, remove the currently scheduled instance and keep the id
// important: since the new job may have errors, it's required to make sure the old job is only unscheduled
// if the new job info is o.k.
oldJob = unscheduleJob(cms, jobId);
if (oldJob == null) {
CmsMessageContainer message = Messages.get().container(
Messages.ERR_JOB_WITH_ID_DOES_NOT_EXIST_1,
jobId);
LOG.warn(message.key());
// can not continue
throw new CmsSchedulerException(message);
}
// open the job configuration (in case it has been frozen)
jobInfo.setFrozen(false);
}
// only schedule jobs when they are marked as active
if (jobInfo.isActive()) {
// generate Quartz job detail
JobDetailImpl jobDetail = (JobDetailImpl)JobBuilder.newJob(CmsScheduleManager.class).build();
jobDetail.setName(jobInfo.getId());
jobDetail.setGroup(Scheduler.DEFAULT_GROUP);
// add the trigger to the job info
jobInfo.setTrigger(trigger);
// now set the job data
JobDataMap jobData = new JobDataMap();
jobData.put(CmsScheduleManager.SCHEDULER_JOB_INFO, jobInfo);
jobDetail.setJobDataMap(jobData);
// finally add the job to the Quartz scheduler
try {
m_scheduler.scheduleJob(jobDetail, trigger);
if (LOG.isInfoEnabled()) {
LOG.info(
Messages.get().getBundle().key(
Messages.LOG_JOB_SCHEDULED_4,
new Object[] {
Integer.valueOf(m_jobs.size()),
jobInfo.getJobName(),
jobInfo.getClassName(),
jobInfo.getContextInfo().getUserName()}));
Date nextExecution = jobInfo.getExecutionTimeNext();
if (nextExecution != null) {
LOG.info(
Messages.get().getBundle().key(
Messages.LOG_JOB_NEXT_EXECUTION_2,
jobInfo.getJobName(),
nextExecution));
}
}
} catch (Exception e) {
if (LOG.isDebugEnabled()) {
LOG.debug(e.getMessage(), e);
}
if (idCreated) {
jobInfo.setId(null);
}
CmsMessageContainer message = Messages.get().container(
Messages.ERR_COULD_NOT_SCHEDULE_JOB_2,
jobInfo.getJobName(),
jobInfo.getClassName());
if (oldJob != null) {
// make sure an old job is re-scheduled
jobDetail = (JobDetailImpl)JobBuilder.newJob(CmsScheduleManager.class).build();
jobDetail.setName(oldJob.getId());
jobDetail.setGroup(Scheduler.DEFAULT_GROUP);
jobDetail.setJobDataMap(jobData);
try {
m_scheduler.scheduleJob(jobDetail, oldJob.getTrigger());
m_jobs.add(oldJob);
} catch (SchedulerException e2) {
if (LOG.isDebugEnabled()) {
LOG.debug(e2.getMessage(), e2);
}
// unable to re-schedule original job - not much we can do about this...
message = Messages.get().container(
Messages.ERR_COULD_NOT_RESCHEDULE_JOB_2,
jobInfo.getJobName(),
jobInfo.getClassName());
}
}
if (LOG.isWarnEnabled()) {
LOG.warn(message.key());
}
throw new CmsSchedulerException(message);
}
}
// freeze the scheduled job configuration
jobInfo.initConfiguration();
// add the job to the list of configured jobs
m_jobs.add(jobInfo);
}
/**
* Shuts down this instance of the OpenCms scheduler manager.
*/
public synchronized void shutDown() {
m_adminCms = null;
if (CmsLog.INIT.isInfoEnabled()) {
CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_SHUTDOWN_1, this.getClass().getName()));
}
if (m_scheduler != null) {
try {
m_scheduler.shutdown();
} catch (SchedulerException e) {
LOG.error(Messages.get().getBundle().key(Messages.LOG_SHUTDOWN_ERROR_0));
}
}
m_scheduler = null;
}
/**
* Removes a currently scheduled job from the scheduler.
*
* @param cms an OpenCms context object that must have been initialized with "Admin" permissions
* @param jobId the id of the job to unschedule, obtained with {@link CmsScheduledJobInfo#getId()}
*
* @return the {@link CmsScheduledJobInfo}
of the sucessfully unscheduled job,
* or null
if the job could not be unscheduled
*
* @throws CmsRoleViolationException if the user has insufficient role permissions
*/
public synchronized CmsScheduledJobInfo unscheduleJob(CmsObject cms, String jobId)
throws CmsRoleViolationException {
if (OpenCms.getRunLevel() > OpenCms.RUNLEVEL_1_CORE_OBJECT) {
// simple unit tests will have runlevel 1 and no CmsObject
OpenCms.getRoleManager().checkRole(cms, CmsRole.WORKPLACE_MANAGER);
}
CmsScheduledJobInfo jobInfo = null;
if (m_jobs.size() > 0) {
// try to remove the job from the OpenCms list of jobs
for (int i = (m_jobs.size() - 1); i >= 0; i--) {
CmsScheduledJobInfo job = m_jobs.get(i);
if (jobId.equals(job.getId())) {
m_jobs.remove(i);
if (jobInfo != null) {
LOG.error(Messages.get().getBundle().key(Messages.LOG_MULTIPLE_JOBS_FOUND_1, jobId));
}
jobInfo = job;
}
}
}
if ((jobInfo != null) && jobInfo.isActive()) {
// job currently active, remove it from the Quartz scheduler
try {
// try to remove the job from Quartz
m_scheduler.unscheduleJob(new TriggerKey(jobId, Scheduler.DEFAULT_GROUP));
if (LOG.isDebugEnabled()) {
LOG.debug(Messages.get().getBundle().key(Messages.LOG_UNSCHEDULED_JOB_1, jobId));
}
} catch (SchedulerException e) {
if (LOG.isDebugEnabled()) {
LOG.debug(Messages.get().getBundle().key(Messages.LOG_UNSCHEDULING_ERROR_1, jobId));
}
}
}
return jobInfo;
}
/**
* Executes the given job.
*
* @param jobInfo the job info bean
*/
protected void executeJob(CmsScheduledJobInfo jobInfo) {
if (LOG.isDebugEnabled()) {
LOG.debug(Messages.get().getBundle().key(Messages.LOG_JOB_STARTING_1, jobInfo.getJobName()));
}
I_CmsScheduledJob job = jobInfo.getJobInstance();
if (job != null) {
// launch the job
try {
CmsObject cms = null;
// update the request time in the job info to the current time
jobInfo.updateContextRequestTime();
// some simple test cases might run below this runlevel
if (OpenCms.getRunLevel() >= OpenCms.RUNLEVEL_3_SHELL_ACCESS) {
// generate a CmsObject for the job context
// must access the scheduler manager instance from the OpenCms singleton
// to get the initialized CmsObject
cms = OpenCms.initCmsObject(OpenCms.getScheduleManager().getAdminCms(), jobInfo.getContextInfo());
}
String result = job.launch(cms, jobInfo.getParameters());
if (CmsStringUtil.isNotEmpty(result) && LOG.isInfoEnabled()) {
LOG.info(
Messages.get().getBundle().key(Messages.LOG_JOB_EXECUTION_OK_2, jobInfo.getJobName(), result));
}
} catch (Throwable t) {
LOG.error(Messages.get().getBundle().key(Messages.LOG_JOB_EXECUTION_ERROR_1, jobInfo.getJobName()), t);
}
}
if (LOG.isDebugEnabled()) {
LOG.debug(Messages.get().getBundle().key(Messages.LOG_JOB_EXECUTED_1, jobInfo.getJobName()));
Date nextExecution = jobInfo.getExecutionTimeNext();
if (nextExecution != null) {
LOG.info(
Messages.get().getBundle().key(
Messages.LOG_JOB_NEXT_EXECUTION_2,
jobInfo.getJobName(),
nextExecution));
}
}
}
/**
* Returns the {@link CmsObject} this Scheduler Manager was initialized with.
*
* @return the {@link CmsObject} this Scheduler Manager was initialized with
*/
private synchronized CmsObject getAdminCms() {
return m_adminCms;
}
}