/* * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This 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 software 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. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package com.xpn.xwiki.plugin.scheduler; import java.util.Date; import java.util.List; import org.quartz.SchedulerException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.api.Document; import com.xpn.xwiki.api.Object; import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.objects.BaseObject; import com.xpn.xwiki.plugin.PluginApi; /** * A Scheduler plugin to plan execution of Jobs from XWiki with cron expressions. The plugin uses Quartz's scheduling * library. <p> Jobs are represented by {@link com.xpn.xwiki.api.Object} XObjects, instances of the * {@link SchedulerPlugin#XWIKI_JOB_CLASS} XClass. These XObjects do store a job name, the implementation class name of * the job to be executed, the cron expression to precise when the job should be fired, and possibly a groovy script * with the job's program. <p> The plugin offers a {@link GroovyJob} Groovy Job wrapper to execute groovy scripts * (typically for use inside the Wiki), but can also be used with any Java class implementing {@link org.quartz.Job} * * @version $Id: e5c7325b85806541bfb2202a280f603a0de6f2b8 $ */ public class SchedulerPluginApi extends PluginApi<SchedulerPlugin> { /** * Log object to log messages in this class. */ private static final Logger LOGGER = LoggerFactory.getLogger(SchedulerPluginApi.class); public SchedulerPluginApi(SchedulerPlugin plugin, XWikiContext context) { super(plugin, context); } /** * Return the trigger state of the given {@link com.xpn.xwiki.plugin.scheduler.SchedulerPlugin#XWIKI_JOB_CLASS} * XObject job. Possible values are : None (the trigger does not exists yet, or has been deleted), Normal, Blocked, * Complete, Error and Paused * * @param object the XObject job to give the state of * @return a String representing this state */ public String getStatus(Object object) { try { return getJobStatus(object.getXWikiObject()).getValue(); } catch (Exception e) { this.context.put("error", e.getMessage()); return null; } } /** * Return the trigger state as a ${@link JobState}, that holds both the integer trigger's inner value of the state * and a String as a human readable representation of that state */ public JobState getJobStatus(BaseObject object) throws SchedulerException { return getProtectedPlugin().getJobStatus(object, this.context); } public JobState getJobStatus(Object object) throws SchedulerException, SchedulerPluginException { return getProtectedPlugin().getJobStatus(retrieveBaseObject(object), this.context); } /** * This function allow to retrieve a com.xpn.xwiki.objects.BaseObject from a com.xpn.xwiki.api.Object without that * the current user needs programming rights (as in com.xpn.xwiki.api.Object#getXWikiObject(). The function is used * internally by this api class and allows wiki users to call methods from the scheduler without having programming * right. The programming right is only needed at script execution time. * * @return object the unwrapped version of the passed api object */ private BaseObject retrieveBaseObject(Object object) throws SchedulerPluginException { String docName = object.getName(); int objNb = object.getNumber(); try { XWikiDocument jobHolder = this.context.getWiki().getDocument(docName, this.context); BaseObject jobObject = jobHolder.getXObject(SchedulerPlugin.XWIKI_JOB_CLASSREFERENCE, objNb); return jobObject; } catch (XWikiException e) { throw new SchedulerPluginException(SchedulerPluginException.ERROR_SCHEDULERPLUGIN_UNABLE_TO_RETRIEVE_JOB, "Job in document [" + docName + "] with object number [" + objNb + "] could not be retrieved.", e); } } /** * Schedule the given XObject to be executed according to its parameters. Errors are returned in the context map. * Scheduling can be called for example: <code> #if($xwiki.scheduler.scheduleJob($job)!=true) * #error($xcontext.get("error") #else #info("Job scheduled") #end </code> * Where $job is an XObject, instance of the {@link SchedulerPlugin#XWIKI_JOB_CLASS} XClass * * @param object the XObject to be scheduled, an instance of the XClass XWiki.SchedulerJobClass * @return true on success, false on failure */ public boolean scheduleJob(Object object) { try { return scheduleJob(retrieveBaseObject(object)); } catch (Exception e) { // we don't need to push the exception message in the context here // as it should already have been pushed by the throwing exception return false; } } public boolean scheduleJob(BaseObject object) { try { getProtectedPlugin().scheduleJob(object, this.context); return true; } catch (Exception e) { this.context.put("error", e.getMessage()); return false; } } /** * Schedule all {@link com.xpn.xwiki.plugin.scheduler.SchedulerPlugin#XWIKI_JOB_CLASS} XObjects stored inside the * given Wiki document, according to each XObject own parameters. * * @param document the document holding the XObjects Jobs to be scheduled * @return true on success, false on failure. */ public boolean scheduleJobs(Document document) { boolean result = true; try { XWikiDocument doc = this.context.getWiki().getDocument(document.getFullName(), this.context); List<BaseObject> objects = doc.getXObjects(SchedulerPlugin.XWIKI_JOB_CLASSREFERENCE); for (BaseObject object : objects) { result &= scheduleJob(object); } } catch (Exception e) { this.context.put("error", e.getMessage()); return false; } return result; } /** * Pause the given XObject job by pausing all of its current triggers. Can be called the same way as * {@link #scheduleJob(Object)} * * @param object the wrapped XObject Job to be paused * @return true on success, false on failure. */ public boolean pauseJob(Object object) { try { return pauseJob(retrieveBaseObject(object)); } catch (Exception e) { // we don't need to push the exception message in the context here // as it should already have been pushed by the throwing exception return false; } } public boolean pauseJob(BaseObject object) { try { getProtectedPlugin().pauseJob(object, this.context); LOGGER.debug("Pause Job: [{}]", object.getStringValue("jobName")); return true; } catch (XWikiException e) { this.context.put("error", e.getMessage()); return false; } } /** * Resume a XObject job that is in a {@link JobState#STATE_PAUSED} state. Can be called the same way as * {@link #scheduleJob(Object)} * * @param object the wrapped XObject Job to be paused * @return true on success, false on failure. */ public boolean resumeJob(Object object) { try { return resumeJob(retrieveBaseObject(object)); } catch (Exception e) { // we don't need to push the exception message in the context here // as it should already have been pushed by the throwing exception return false; } } public boolean resumeJob(BaseObject object) { try { getProtectedPlugin().resumeJob(object, this.context); LOGGER.debug("Resume Job: [{}]", object.getStringValue("jobName")); return true; } catch (XWikiException e) { this.context.put("error", e.getMessage()); return false; } } /** * Unschedule a XObject job by deleting it from the jobs table. Can be called the same way as * {@link #scheduleJob(Object)} * * @param object the wrapped XObject Job to be paused * @return true on success, false on failure. */ public boolean unscheduleJob(Object object) { try { return unscheduleJob(retrieveBaseObject(object)); } catch (Exception e) { // we don't need to push the exception message in the context here // as it should already have been pushed by the throwing exception return false; } } public boolean unscheduleJob(BaseObject object) { try { getProtectedPlugin().unscheduleJob(object, this.context); LOGGER.debug("Delete Job: [{}]", object.getStringValue("jobName")); return true; } catch (XWikiException e) { this.context.put("error", e.getMessage()); return false; } } /** * Trigger a XObject job (execute it now). * * @param object the wrapped XObject Job to be triggered * @return true on success, false on failure. */ public boolean triggerJob(Object object) { try { return triggerJob(retrieveBaseObject(object)); } catch (Exception e) { // we don't need to push the exception message in the context here // as it should already have been pushed by the throwing exception return false; } } /** * Trigger a BaseObject job (execute it now). * * @param object the BaseObject Job to be triggered * @return true on success, false on failure. */ public boolean triggerJob(BaseObject object) { try { getProtectedPlugin().triggerJob(object, this.context); LOGGER.debug("Trigger Job: [{}]", object.getStringValue("jobName")); return true; } catch (XWikiException e) { this.context.put("error", e.getMessage()); return false; } } /** * Give, for a XObject job in a {@link JobState#STATE_NORMAL} state, the previous date at which the job has been * executed, the fire time is not computed from the CRON expression, this method will return null if the . * * @param object the wrapped XObject for which to give the fire time * @return the date the job has been executed */ public Date getPreviousFireTime(Object object) { try { return getPreviousFireTime(retrieveBaseObject(object)); } catch (Exception e) { // we don't need to push the exception message in the context here // as it should already have been pushed by the throwing exception return null; } } /** * Give, for a BaseObject job in a {@link JobState#STATE_NORMAL} state, the previous date at which the job has been * executed. Note that this method does not compute a date from the CRON expression, it only returns a date value * which is set each time the job is executed. If the job has never been fired this method will return null. * * @param object the BaseObject for which to give the fire time * @return the date the job has been executed */ public Date getPreviousFireTime(BaseObject object) { try { return getProtectedPlugin().getPreviousFireTime(object, this.context); } catch (SchedulerPluginException e) { this.context.put("error", e.getMessage()); return null; } } /** * Give, for a XObject job in a {@link JobState#STATE_NORMAL} state, the next date at which the job will be * executed, according to its cron expression. Errors are returned in the context map. Can be called for example: * <code> #set($firetime = $xwiki.scheduler.getNextFireTime($job)) * #if (!$firetime || $firetime=="") #error($xcontext.get("error") #else #info("Fire time : * $firetime") #end </code> * Where $job is an XObject, instance of the {@link SchedulerPlugin#XWIKI_JOB_CLASS} XClass * * @param object the wrapped XObject for which to give the fire date * @return the date the job will be executed */ public Date getNextFireTime(Object object) { try { return getNextFireTime(retrieveBaseObject(object)); } catch (Exception e) { // we don't need to push the exception message in the context here // as it should already have been pushed by the throwing exception return null; } } public Date getNextFireTime(BaseObject object) { try { return getProtectedPlugin().getNextFireTime(object, this.context); } catch (SchedulerPluginException e) { this.context.put("error", e.getMessage()); return null; } } }