/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program 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.
*
* Copyright 2005 - 2009 Pentaho Corporation. All rights reserved.
*
*
* Created Aug 15, 2005
* @author wseyler
*/
package org.pentaho.platform.scheduler;
import java.text.DateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.pentaho.platform.api.engine.ComponentException;
import org.pentaho.platform.api.engine.IBackgroundExecution;
import org.pentaho.platform.api.engine.IParameterProvider;
import org.pentaho.platform.api.engine.IPentahoUrlFactory;
import org.pentaho.platform.api.repository.ISchedule;
import org.pentaho.platform.api.scheduler.BackgroundExecutionException;
import org.pentaho.platform.api.scheduler.IJobSchedule;
import org.pentaho.platform.api.util.XmlParseException;
import org.pentaho.platform.engine.core.solution.ActionInfo;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.pentaho.platform.engine.services.WebServiceUtil;
import org.pentaho.platform.engine.services.solution.StandardSettings;
import org.pentaho.platform.scheduler.messages.Messages;
import org.pentaho.platform.uifoundation.component.xml.XmlComponent;
import org.pentaho.platform.util.messages.LocaleHelper;
import org.pentaho.platform.util.xml.dom4j.XmlDom4JHelper;
import org.quartz.CronTrigger;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
/**
* API description:
* Base URL is: http://<servername>:<portnum>/<contextname>/SchedulerAdmin
* or
* http://localhost:8080/pentaho/SchedulerAdmin
*
* deleteJob:
* schedulerAction=deleteJob&jobName=PentahoSystemVersionCheck&jobGroup=DEFAULT
*
* executeJobNow:
* schedulerAction=executeJob&jobName=PentahoSystemVersionCheck&jobGroup=DEFAULT
*
* getJobNames:
* schedulerAction=getJobNames
*
* isSchedulerPaused:
* schedulerAction=isSchedulerPaused
*
* pauseAll:
* schedulerAction=suspendScheduler
*
* pauseJob:
* schedulerAction=pauseJob&jobName=PentahoSystemVersionCheck&jobGroup=DEFAULT
*
* resumeAll:
* schedulerAction=resumeScheduler
*
* resumeJob:
* schedulerAction=resumeJob&jobName=PentahoSystemVersionCheck&jobGroup=DEFAULT
*
* TODO sbarkdull, add API for create and update
*
* NOTE: the term "Job" is somewhat misused in much of this file. Often where you see
* the term "Job", it means "Schedule". A Schedule is a combination of a trigger
* and a Job. A trigger is essentially a time to execute something, a Job is
* essentially the set of things to execute when a trigger fires.
*
* @author wseyler
*
* TODO To change the template for this generated type comment go to Window -
* Preferences - Java - Code Style - Code Templates
*/
public class SchedulerAdminUIComponent extends XmlComponent {
/**
*
*/
private static final long serialVersionUID = 2963902264708970014L;
private static final String JOB = "job"; //$NON-NLS-1$
private static final String JOB_NAME = "jobName"; //$NON-NLS-1$
private static final String RESULT = "schedulerResults"; //$NON-NLS-1$
private static final String ERROR_NODE_NAME = "error"; //$NON-NLS-1$
private static final String MSG_ATTR_NAME = "msg"; //$NON-NLS-1$
private static final String JOB_GROUP = "jobGroup"; //$NON-NLS-1$
public static final String RESUME_SCHEDULER_ACTION_STR = "resumeScheduler"; //$NON-NLS-1$
public static final String SUSPEND_SCHEDULER_ACTION_STR = "suspendScheduler"; //$NON-NLS-1$
public static final String GET_JOB_NAMES_ACTION_STR = "getJobNames"; //$NON-NLS-1$
public static final String GET_IS_SCHEDULER_PAUSED_ACTION_STR = "isSchedulerPaused"; //$NON-NLS-1$
public static final String PAUSE_JOB_ACTION_STR = "pauseJob"; //$NON-NLS-1$
public static final String RESUME_JOB_ACTION_STR = "resumeJob"; //$NON-NLS-1$
public static final String DELETE_JOB_ACTION_STR = "deleteJob"; //$NON-NLS-1$
public static final String SCHEDULER_ACTION_STR = "schedulerAction"; //$NON-NLS-1$
public static final String RUN_JOB_ACTION_STR = "executeJob"; //$NON-NLS-1$
public static final String CREATE_JOB_ACTION_STR = "createJob"; //$NON-NLS-1$
public static final String UPDATE_JOB_ACTION_STR = "updateJob"; //$NON-NLS-1$
private Scheduler sched = null;
private IBackgroundExecution backgroundExecution = null;
private static final Log logger = LogFactory.getLog(SchedulerAdminUIComponent.class);
/**
* @param urlFactory
*/
public SchedulerAdminUIComponent(IPentahoUrlFactory urlFactory, List<String> messages) {
super(urlFactory, messages, null);
try {
sched = QuartzSystemListener.getSchedulerInstance();
backgroundExecution = PentahoSystem.get(IBackgroundExecution.class, getSession());
} catch (Exception e) {
error(Messages.getInstance()
.getString(Messages.getInstance().getErrorString("SchedulerAdminUIComponent.ERROR_0002_NoScheduler") + e.toString())); //$NON-NLS-1$
e.printStackTrace();
}
setXsl("text/html", "SchedulerAdmin.xsl"); //$NON-NLS-1$ //$NON-NLS-2$
}
/*
* (non-Javadoc)
*
* @see org.pentaho.core.system.PentahoBase#getLogger()
*/
public Log getLogger() {
return logger;
}
/*
* (non-Javadoc)
*
* @see org.pentaho.core.ui.component.BaseUIComponent#validate()
*/
public boolean validate() {
return true;
}
/*
* (non-Javadoc)
*
* @see org.pentaho.core.ui.component.BaseUIComponent#getXmlContent()
*/
public Document getXmlContent() {
String schedulerActionStr = getParameter(SCHEDULER_ACTION_STR, GET_JOB_NAMES_ACTION_STR);
setXslProperty("baseUrl", urlFactory.getDisplayUrlBuilder().getUrl()); //$NON-NLS-1$
try {
if (SUSPEND_SCHEDULER_ACTION_STR.equals(schedulerActionStr)) {
return doPauseAll();
} else if (RESUME_SCHEDULER_ACTION_STR.equals(schedulerActionStr)) {
return doResumeAll();
} else if (GET_JOB_NAMES_ACTION_STR.equals(schedulerActionStr)) {
return doGetJobNames();
} else if (GET_IS_SCHEDULER_PAUSED_ACTION_STR.equals(schedulerActionStr)) {
return doIsSchedulerPaused();
} else if (PAUSE_JOB_ACTION_STR.equalsIgnoreCase(schedulerActionStr)) {
return doPauseJob();
} else if (RESUME_JOB_ACTION_STR.equalsIgnoreCase(schedulerActionStr)) {
return doResumeJob();
} else if (DELETE_JOB_ACTION_STR.equalsIgnoreCase(schedulerActionStr)) {
return doDeleteJob();
} else if (RUN_JOB_ACTION_STR.equalsIgnoreCase(schedulerActionStr)) {
return doExecuteJobNow();
} else if (CREATE_JOB_ACTION_STR.equalsIgnoreCase(schedulerActionStr)) {
return doCreateJob();
} else if (UPDATE_JOB_ACTION_STR.equalsIgnoreCase(schedulerActionStr)) {
return doUpdateJob();
} else {
Document document = DocumentHelper.createDocument();
document.setName(SCHEDULER_ACTION_STR);
return document;
// returns a blank document if
// nothing else executed.
/*
* TODO Create some sort of document to display when the default
* action occurs.
*/
}
} catch (ComponentException e) {
// TODO sbarkdull, lame attempt to start to get some error info returned to caller,
// should be much more robust, and clients need to be coded to respond to error xml message
logger.error( e.getMessage() );
return WebServiceUtil.createErrorDocument( e.getMessage() );
}
}
private Document doExecuteJobNow() throws ComponentException {
String jobName = getParameter(JOB_NAME, ""); //$NON-NLS-1$
String groupName = getParameter(JOB_GROUP, ""); //$NON-NLS-1$
Trigger trigger = new SimpleTrigger("Immediate", "DEFAULT"); //$NON-NLS-1$ //$NON-NLS-2$
try {
JobDetail jobDetail = sched.getJobDetail(jobName, groupName);
if (jobDetail == null) {
throw new ComponentException(Messages.getInstance().getErrorString("SchedulerAdminUIComponent.ERROR_0429_FAILED_TO_EXECUTE_NON_EXISTENT_JOB",jobName));
} else {
jobDetail.setGroup("Immediate"); //$NON-NLS-1$
sched.scheduleJob(jobDetail, trigger);
}
} catch (SchedulerException e) {
throw new ComponentException(Messages.getInstance().getErrorString("SchedulerAdminUIComponent.ERROR_0428_FAILED_TO_EXECUTE",jobName), e);
}
return doGetJobNames();
}
private Document doCreateJob() {
IBackgroundExecution helper = PentahoSystem.get(IBackgroundExecution.class, getSession());
String strReturn = null;
try {
strReturn = helper.backgroundExecuteAction(getSession(), (IParameterProvider) getParameterProviders().get(
IParameterProvider.SCOPE_REQUEST));
} catch(BackgroundExecutionException bex) {
return WebServiceUtil.createErrorDocument(bex.getLocalizedMessage());
}
return WebServiceUtil.createStatusDocument("ok");
}
private Document doUpdateJob() throws ComponentException {
String jobName = getParameter("oldJobName", null); //$NON-NLS-1$
String groupName = getParameter("oldJobGroup", null); //$NON-NLS-1$
if (null == jobName || null == groupName) {
throw new ComponentException(Messages.getInstance().getErrorString("SchedulerAdminUIComponent.ERROR_0420_MISSING_PARAMS")); //$NON-NLS-1$
}
try {
sched.deleteJob(jobName, groupName);
} catch (SchedulerException e) {
throw new ComponentException(Messages.getInstance().getErrorString(
"SchedulerAdminUIComponent.ERROR_0421_FAILED_TO_UPDATE", jobName, groupName)); //$NON-NLS-1$
}
/*
JobDetail jd = sched.getJobDetail( jobName, groupName );
Trigger t = sched.getTrigger(jobName, groupName);
sched.unscheduleJob( jobName, groupName );
sched.scheduleJob(jd, t);
*/
IBackgroundExecution helper = PentahoSystem.get(IBackgroundExecution.class, getSession());
String strReturn = null;
try {
strReturn = helper.backgroundExecuteAction(getSession(), (IParameterProvider) getParameterProviders().get(
IParameterProvider.SCOPE_REQUEST));
} catch(BackgroundExecutionException bex) {
return WebServiceUtil.createErrorDocument(bex.getLocalizedMessage());
}
return WebServiceUtil.createStatusDocument("ok");
}
private Document doDeleteJob() throws ComponentException {
String jobName = getParameter(JOB_NAME, ""); //$NON-NLS-1$
String groupName = getParameter(JOB_GROUP, ""); //$NON-NLS-1$
try {
// First delete any content that this schedule may have
JobDetail jobDetail = sched.getJobDetail(jobName, groupName);
String backgroundContentGUID = jobDetail.getJobDataMap().getString(QuartzBackgroundExecutionHelper.BACKGROUND_CONTENT_GUID_STR);
if (backgroundContentGUID != null && backgroundContentGUID.length() > 0) {
backgroundExecution.removeBackgroundExecutedContentForID(backgroundContentGUID, getSession());
}
sched.deleteJob(jobName, groupName);
} catch (SchedulerException e) {
throw new ComponentException(Messages.getInstance().getErrorString(
"SchedulerAdminUIComponent.ERROR_0422_FAILED_TO_DELETE", jobName, groupName), e); //$NON-NLS-1$
}
return doGetJobNames();
}
private Document doResumeJob() throws ComponentException {
String jobName = getParameter(JOB_NAME, ""); //$NON-NLS-1$
String groupName = getParameter(JOB_GROUP, ""); //$NON-NLS-1$
try {
sched.resumeJob(jobName, groupName);
} catch (SchedulerException e) {
throw new ComponentException(Messages.getInstance().getErrorString(
"SchedulerAdminUIComponent.ERROR_0426_FAILED_TO_RESUME", jobName, groupName), e);
}
return doGetJobNames();
}
private Document doPauseJob() throws ComponentException {
String jobName = getParameter(JOB_NAME, ""); //$NON-NLS-1$
String groupName = getParameter(JOB_GROUP, ""); //$NON-NLS-1$
try {
sched.pauseJob(jobName, groupName);
} catch (SchedulerException e) {
throw new ComponentException(Messages.getInstance().getErrorString(
"SchedulerAdminUIComponent.ERROR_0427_FAILED_TO_PAUSE", jobName, groupName), e);
}
return doGetJobNames();
}
/**
* @return
* @throws ComponentException
*/
private Document doIsSchedulerPaused() throws ComponentException {
Document document = DocumentHelper.createDocument();
document.setName(SCHEDULER_ACTION_STR);
Element root = document.addElement(getParameter(SCHEDULER_ACTION_STR, "")); //$NON-NLS-1$
try {
boolean isInStandby = sched.isInStandbyMode();
root
.addAttribute(
RESULT,
isInStandby ? Messages.getInstance().getString("SchedulerAdminUIComponent.USER_isPaused") : Messages.getInstance().getString("SchedulerAdminUIComponent.USER_isRunning")); //$NON-NLS-1$ //$NON-NLS-2$
} catch (SchedulerException e) {
throw new ComponentException(Messages.getInstance().getErrorString("SchedulerAdminUIComponent.ERROR_0425_FAILED_TO_DETERMINE_STATE"), e);
}
return document;
}
/**
* @return
*/
private Document doGetJobNames() {
Document document = DocumentHelper.createDocument();
document.setName(SCHEDULER_ACTION_STR);
Element root = document.addElement(GET_JOB_NAMES_ACTION_STR);
try {
String[] triggerGroups = sched.getTriggerGroupNames();
for (int i = 0; i < triggerGroups.length; i++) {
String[] triggerNames = sched.getTriggerNames(triggerGroups[i]);
for (int j = 0; j < triggerNames.length; j++) {
Element job = root.addElement(JOB);
try {
job
.addAttribute(
"triggerState", Integer.toString(sched.getTriggerState(triggerNames[j], triggerGroups[i]))); //$NON-NLS-1$
Trigger trigger = sched.getTrigger(triggerNames[j], triggerGroups[i]);
job.addAttribute("triggerName", trigger.getName()); //$NON-NLS-1$
job.addAttribute("triggerGroup", trigger.getGroup()); //$NON-NLS-1$
Date date = trigger.getNextFireTime();
job
.addAttribute(
"nextFireTime", (date == null) ? Messages.getInstance().getString("SchedulerAdminUIComponent.USER_NEVER") : date.toString()); //$NON-NLS-1$ //$NON-NLS-2$
date = trigger.getPreviousFireTime();
job
.addAttribute(
"prevFireTime", (date == null) ? Messages.getInstance().getString("SchedulerAdminUIComponent.USER_NEVER") : date.toString()); //$NON-NLS-1$ //$NON-NLS-2$
// get the job info
job.addAttribute(JOB_NAME, trigger.getJobName());
job.addAttribute(JOB_GROUP, trigger.getJobGroup());
JobDetail jobDetail = sched.getJobDetail(trigger.getJobName(), trigger.getJobGroup());
job.addElement("description").addCDATA(jobDetail.getDescription()); //$NON-NLS-1$
DateFormat dateTimeFormatter = getDateTimeFormatter();
Date d = trigger.getStartTime();
if (null != d) {
String startDate = dateTimeFormatter.format(d);
job.addAttribute(StandardSettings.START_DATE_TIME, startDate);
}
d = trigger.getEndTime();
if (null != d) {
String endDate = dateTimeFormatter.format(d);
job.addAttribute(StandardSettings.END_DATE_TIME, endDate);
}
if (trigger instanceof CronTrigger) {
job.addAttribute(StandardSettings.CRON_STRING, ((CronTrigger) trigger).getCronExpression());
} else if (trigger instanceof SimpleTrigger) {
long repeatInSecs = ((SimpleTrigger) trigger).getRepeatInterval();
job.addAttribute(StandardSettings.REPEAT_TIME_MILLISECS, Long.toString(repeatInSecs));
} else {
throw new RuntimeException(Messages.getInstance().getErrorString(
"SchedulerAdminUIComponent.ERROR_0423_UNRECOGNIZED_TRIGGER", trigger.getClass().getName())); //$NON-NLS-1$
}
JobDataMap m = jobDetail.getJobDataMap();
if (null != m.getString(StandardSettings.ACTION)) {
ActionInfo actionInfo = new ActionInfo(m.getString(StandardSettings.SOLUTION), m
.getString(StandardSettings.PATH), m.getString(StandardSettings.ACTION));
job.addAttribute(StandardSettings.ACTIONS_REFS, actionInfo.toString());
}
// job.addAttribute("class",
// jobDetail.getClass().getName()); //$NON-NLS-1$
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
job.addElement("description").addCDATA(e.getMessage()); //$NON-NLS-1$
job.addAttribute("triggerState", "3"); // ERROR //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
} catch (SchedulerException e) {
String msg = Messages.getInstance().getErrorString("SchedulerAdminUIComponent.ERROR_0001_ErrorInScheduler") + e.toString();
error(msg);
root.addAttribute(RESULT, msg);
addErrorElementToDocument(document, msg);
}
return document;
}
/**
* This formatter works with a date/time string with this format:
* May 21, 2008 8:29:21 PM
*
* NOTE: the formatter cannot be shared across threads (since DateFormat implementations
* are not guaranteed to be thread safe) or across sessions (since different
* sessions may have different locales). So create a new one an each call.
* @return
*/
private DateFormat getDateTimeFormatter() {
return DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.MEDIUM, LocaleHelper.getLocale());
}
/**
* @return
* @throws ComponentException
*/
private Document doResumeAll() throws ComponentException {
Document document = DocumentHelper.createDocument();
document.setName(SCHEDULER_ACTION_STR);
Element root = document.addElement(getParameter(SCHEDULER_ACTION_STR, "")); //$NON-NLS-1$
try {
sched.resumeAll();
root.addAttribute(RESULT, Messages.getInstance().getString("SchedulerAdminUIComponent.USER_JobsResumed")); //$NON-NLS-1$
} catch (SchedulerException e) {
throw new ComponentException(Messages.getInstance().getErrorString("SchedulerAdminUIComponent.ERROR_0001_ErrorInScheduler")
+ e.toString());
}
return document;
}
/**
* @return
*/
private Document doPauseAll() throws ComponentException {
Document document = DocumentHelper.createDocument();
document.setName(SCHEDULER_ACTION_STR);
Element root = document.addElement(getParameter(SCHEDULER_ACTION_STR, "")); //$NON-NLS-1$
try {
sched.pauseAll();
root.addAttribute(RESULT, Messages.getInstance().getString("SchedulerAdminUIComponent.USER_JobsSuspended")); //$NON-NLS-1$
} catch (SchedulerException e) {
throw new ComponentException(Messages.getInstance().getErrorString("SchedulerAdminUIComponent.ERROR_0001_ErrorInScheduler")
+ e.toString());
}
return document;
}
private static void addErrorElementToDocument(Document document, String msg) {
Element parentElem = document.getRootElement();
Element error = parentElem.addElement(ERROR_NODE_NAME);
error.addAttribute(MSG_ATTR_NAME, StringEscapeUtils.escapeXml(msg));
}
}