tim-connector: main changes in scheduler

* CRUD for scheduler
* The UI job_scheduling.zul for CRUD is splits to _editJobScheduling.zul and _listJobScheduling.zul
* Lots of changes in scheduler to make it generic
  - jobs can be scheduled, rescheduled and deleted
  - check if job has a connector and is enabled
  - check if job is allowed to be scheduled
  - do manual is moved to JobConfigurationController
* attributes connectorName and scheduled added to JobSchedulerConfiguration modified
* triggerGroup and triggerName deleted from JobScheduleerConfiguration
* In JobConfigurationDAO findByConnectorName added
* New JobClassNameEnum added to be used as data type for JobClassName in JobSchedulerConfiguration
* Import/Export Jobs read the beans that do the real work from ApplicationContext
* Some minor changes in ConfigurationController
This commit is contained in:
miciele Ghiorghis 2013-03-06 17:49:20 +01:00 committed by Manuel Rego Casasnovas
parent 21c788316a
commit a610afa957
21 changed files with 1072 additions and 452 deletions

View file

@ -33,6 +33,8 @@ public interface IJobSchedulerConfigurationDAO extends
List<JobSchedulerConfiguration> getAll();
List<JobSchedulerConfiguration> findByConnectorName(String connectorName);
JobSchedulerConfiguration findByJobGroupAndJobName(String jobGroup,
String jobName);
}

View file

@ -21,6 +21,7 @@ package org.libreplan.business.common.daos;
import java.util.List;
import org.hibernate.Criteria;
import org.hibernate.criterion.Restrictions;
import org.libreplan.business.common.entities.JobSchedulerConfiguration;
import org.springframework.beans.factory.config.BeanDefinition;
@ -55,4 +56,14 @@ public class JobSchedulerConfigurationDAO extends
.add(Restrictions.eq("jobName", jobName)).uniqueResult();
}
@Override
@Transactional(readOnly = true)
public List<JobSchedulerConfiguration> findByConnectorName(
String connectorName) {
Criteria c = getSession().createCriteria(
JobSchedulerConfiguration.class).add(
Restrictions.eq("connectorName", connectorName));
return ((List<JobSchedulerConfiguration>) c.list());
}
}

View file

@ -112,4 +112,10 @@ public class Connector extends BaseEntity {
}
public boolean isActivated() {
return getPropertiesAsMap()
.get(PredefinedConnectorProperties.ACTIVATED).equalsIgnoreCase(
"Y");
}
}

View file

@ -0,0 +1,52 @@
/*
* This file is part of LibrePlan
*
* Copyright (C) 2013 St. Antoniusziekenhuis
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.libreplan.business.common.entities;
/**
* Defines the job class package and name to be used as data type in
* {@link JobSchedulerConfiguration}
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
public enum JobClassNameEnum {
IMPORT_ROSTER_FROM_TIM_JOB("org.libreplan.importers",
"ImportRosterFromTimJob"), EXPORT_TIMESHEET_TO_TIM_JOB(
"org.libreplan.importers",
"ExportTimesheetToTimJob");
private String packageName;
private String name;
private JobClassNameEnum(String packageName, String name) {
this.packageName = packageName;
this.name = name;
}
public String getPackageName() {
return packageName;
}
public String getName() {
return name;
}
}

View file

@ -21,6 +21,7 @@ package org.libreplan.business.common.entities;
import org.hibernate.validator.NotNull;
import org.libreplan.business.common.BaseEntity;
import org.libreplan.business.common.IHumanIdentifiable;
/**
* JobSchedulerConfiguration entity, represents parameters for the jobs to be
@ -33,7 +34,8 @@ import org.libreplan.business.common.BaseEntity;
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
public class JobSchedulerConfiguration extends BaseEntity {
public class JobSchedulerConfiguration extends BaseEntity implements
IHumanIdentifiable {
public static JobSchedulerConfiguration create() {
return create(new JobSchedulerConfiguration());
@ -49,13 +51,13 @@ public class JobSchedulerConfiguration extends BaseEntity {
private String jobName;
private String triggerGroup;
private String triggerName;
private String cronExpression;
private String jobClassName;
private JobClassNameEnum jobClassName;
private boolean schedule;
private String connectorName;
@NotNull(message = "job group not specified")
public String getJobGroup() {
@ -75,24 +77,6 @@ public class JobSchedulerConfiguration extends BaseEntity {
this.jobName = jobName;
}
@NotNull(message = "trigger group not specified")
public String getTriggerGroup() {
return triggerGroup;
}
public void setTriggerGroup(String triggerGroup) {
this.triggerGroup = triggerGroup;
}
@NotNull(message = "trigger name not specified")
public String getTriggerName() {
return triggerName;
}
public void setTriggerName(String triggerName) {
this.triggerName = triggerName;
}
@NotNull(message = "cron expression not specified")
public String getCronExpression() {
return cronExpression;
@ -103,11 +87,32 @@ public class JobSchedulerConfiguration extends BaseEntity {
}
@NotNull(message = "job class name not specified")
public String getJobClassName() {
public JobClassNameEnum getJobClassName() {
return jobClassName;
}
public void setJobClassName(String jobClassName) {
public void setJobClassName(JobClassNameEnum jobClassName) {
this.jobClassName = jobClassName;
}
public boolean isSchedule() {
return schedule;
}
public void setSchedule(boolean schedule) {
this.schedule = schedule;
}
public String getConnectorName() {
return connectorName;
}
public void setConnectorName(String connectorName) {
this.connectorName = connectorName;
}
@Override
public String getHumanId() {
return jobGroup == null ? "" : jobGroup;
}
}

View file

@ -378,7 +378,6 @@
referencedTableName="connector"
referencesUniqueColumn="false"/>
</changeSet>
<!-- scheduler configuration -->
<changeSet author="miciele" id="create-table-job-scheduler-configuration">
<comment>Create new table job_scheduler_configuration</comment>
@ -395,39 +394,14 @@
<column name="job_name" type="VARCHAR(255)" >
<constraints nullable="false" primaryKey="true" />
</column>
<column name="trigger_group" type="VARCHAR(255)" >
<constraints nullable="false" />
</column>
<column name="trigger_name" type="VARCHAR(255)" >
<constraints nullable="false" />
</column>
<column name="cron_expression" type="VARCHAR(255)" >
<constraints nullable="false" />
</column>
<column name="job_class_name" type="VARCHAR(255)" >
<column name="job_class_name" type="INTEGER" >
<constraints nullable="false" />
</column>
<column name="connector_name" type="VARCHAR(255)" />
<column name="schedule" type="BOOLEAN" />
</createTable>
</changeSet>
<changeSet author="miciele" id="insert-predefined-jobs">
<comment>Insert jobs to be scheduled with default cron-expression</comment>
<insert tableName="job_scheduler_configuration">
<column name="version" value="0"/>
<column name="job_group" value="Tim"/>
<column name="job_name" value="Import roster from Tim" />
<column name="trigger_group" value="TimTrigger" />
<column name="trigger_name" value="Import roster from Tim-trigger" />
<column name="cron_expression" value="0 59 22 ? * *" />
<column name="job_class_name" value="ImportRosterFromTimJob" />
</insert>
<insert tableName="job_scheduler_configuration">
<column name="version" value="0"/>
<column name="job_group" value="Tim"/>
<column name="job_name" value="Export timesheet to Tim" />
<column name="trigger_group" value="TimTrigger" />
<column name="trigger_name" value="Export timesheet to Tim-trigger" />
<column name="cron_expression" value="0 59 23 ? * *" />
<column name="job_class_name" value="ExportTimesheetToTimJob" />
</insert>
</changeSet>
</databaseChangeLog>

View file

@ -14,10 +14,14 @@
<property name="jobGroup" column="job_group" not-null="true" />
<property name="jobName" column="job_name" not-null="true" />
<property name="triggerGroup" column="trigger_group" not-null="true"/>
<property name="triggerName" column="trigger_name" not-null="true"/>
<property name="cronExpression" column="cron_expression" not-null="true"/>
<property name="jobClassName" column="job_class_name" not-null="true"/>
</class>
<property name="cronExpression" column="cron_expression" not-null="true" />
<property name="jobClassName" access="field" column="job_class_name" not-null="true">
<type name="org.hibernate.type.EnumType">
<param name="enumClass">org.libreplan.business.common.entities.JobClassNameEnum</param>
</type>
</property>
<property name="connectorName" column="connector_name" />
<property name="schedule" column="schedule" />
</class>
</hibernate-mapping>

View file

@ -18,36 +18,28 @@
*/
package org.libreplan.importers;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
/**
* A job that exports time sheets to Tim SOAP server
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
@Component
@Scope(BeanDefinition.SCOPE_SINGLETON)
public class ExportTimesheetToTimJob extends QuartzJobBean {
private IExportTimesheetsToTim exportTimesheetsToTim;
public IExportTimesheetsToTim getExportTimesheetsToTim() {
return exportTimesheetsToTim;
}
public void setExportTimesheetsToTim(
IExportTimesheetsToTim exportTimesheetsToTim) {
this.exportTimesheetsToTim = exportTimesheetsToTim;
}
@Override
protected void executeInternal(JobExecutionContext context)
throws JobExecutionException {
ApplicationContext applicationContext = (ApplicationContext) context
.getJobDetail().getJobDataMap().get("applicationContext");
IExportTimesheetsToTim exportTimesheetsToTim = (IExportTimesheetsToTim) applicationContext
.getBean("exportTimesheetsToTim");
exportTimesheetsToTim.exportTimesheets();
}

View file

@ -19,9 +19,8 @@
package org.libreplan.importers;
import java.util.List;
import org.libreplan.business.common.entities.JobSchedulerConfiguration;
import org.quartz.SchedulerException;
import org.springframework.scheduling.quartz.CronTriggerBean;
import org.springframework.scheduling.quartz.JobDetailBean;
@ -31,51 +30,73 @@ import org.springframework.scheduling.quartz.JobDetailBean;
*
* The start and destroy of the scheduler itself is managed by the Spring
* framework. The scheduler starts automatically when the application starts and
* destroyed when the application stops. The sole purpose of this manager is to
* create jobs {@link JobDetailBean} and cron-triggers {@link CronTriggerBean}
* when the scheduler is started. It links the triggers with the jobs and add
* them to the scheduler.
* destroyed when the application stops.
*
* The SchedulerManager reads the jobs to be scheduled and the cron-triggers to
* fire the jobs form the {@link JobSchedulerConfiguration} entity. Hence the
* {@link JobSchedulerConfiguration} entity must exist with predefined jobs and
* valid cron-triggers
* This manager (un)schedules the jobs based on the configuration
* {@link JobSchedulerConfiguration} entity once the scheduler starts.
*
* This manager also supports the rescheduling of jobs.
* <ul>
* <li>Schedule job:create job {@link JobDetailBean} and cron-trigger
* {@link CronTriggerBean}, associated the trigger with the job and add it to
* the scheduler.
* <li>
* <li>Delete job: search the job in the scheduler and if found
* unschedule(delete) the job</li>
* </ul>
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
public interface ISchedulerManager {
/**
* Reads job configuration from the {@link JobSchedulerConfiguration} and
* schedules the jobs as defined in the configuration
* Reads the jobs to be scheduled from the {@link JobSchedulerConfiguration}
* and schedules the jobs based on the cron expression defined for each job
* in the {@link JobSchedulerConfiguration}
*/
void scheduleJobs();
/**
* Reschedule the job.
* Reads the jobs to be scheduled from the specified
* <code>{@link JobSchedulerConfiguration}</code> and (un)schedule it
* accordingly
*
* Reads the job to be rescheduled from the specified parameter
* {@link JobSchedulerConfiguration} and reschedule the job accordingly
* In the specified <code>{@link JobSchedulerConfiguration}</code>
*
* <ul>
* <li><code>{@link JobSchedulerConfiguration#getConnectorName()}</code>
* check if job has a connector and the connector is activated</li>
* <li><code>{@link JobSchedulerConfiguration#isSchedule()}</code> if true
* the job would be scheduled, if not job deleted</li>
* </ul>
*
* @param jobSchedulerConfiguration
* the job scheduler configuration
* configuration for job to be (un)scheduled
* @throws SchedulerException
* if unable to (un)schedule
*/
void rescheduleJob(JobSchedulerConfiguration jobSchedulerConfiguration);
void scheduleOrUnscheduleJob(JobSchedulerConfiguration jobSchedulerConfiguration) throws SchedulerException;
/**
* returns the scheduler info list. Can be useful to display in UI
* Deletes the job from the scheduler for the specified job by
* <code>{@link JobSchedulerConfiguration}</code>, if the job is already in
* the scheduler
*
* @return list of scheduler info
* @param jobSchedulerConfiguration
* configuration for job to be deleted
* @throws SchedulerException
* if unable to delete
*/
List<SchedulerInfo> getSchedulerInfos();
void deleteJob(JobSchedulerConfiguration jobSchedulerConfiguration) throws SchedulerException;
/**
* To manually execute the job specified by <code>jobName</code>
* gets the next fire time for the specified job from
* {@link JobSchedulerConfiguration} if job is already scheduled. This is
* only neede for UI
*
* @param jobName
* the name of the job to be executed
* @param jobSchedulerConfiguration
* configuration to check for next fire time
* @return next fire time or empty string
*/
void doManual(String jobName);
String getNextFireTime(JobSchedulerConfiguration jobSchedulerConfiguration);
}

View file

@ -22,10 +22,10 @@ package org.libreplan.importers;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Scope;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* A job that import rosters from Tim SOAP server
@ -36,21 +36,17 @@ import org.springframework.transaction.annotation.Transactional;
@Scope(BeanDefinition.SCOPE_SINGLETON)
public class ImportRosterFromTimJob extends QuartzJobBean {
private IImportRosterFromTim importRosterFromTim;
public IImportRosterFromTim getImportRosterFromTim() {
return importRosterFromTim;
}
public void setImportRosterFromTim(IImportRosterFromTim importRosterFromTim) {
this.importRosterFromTim = importRosterFromTim;
}
@Override
@Transactional
protected void executeInternal(JobExecutionContext context)
throws JobExecutionException {
ApplicationContext applicationContext = (ApplicationContext) context
.getJobDetail().getJobDataMap().get("applicationContext");
IImportRosterFromTim importRosterFromTim = (IImportRosterFromTim) applicationContext
.getBean("importRosterFromTim");
importRosterFromTim.importRosters();
}

View file

@ -20,26 +20,30 @@
package org.libreplan.importers;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.libreplan.business.common.daos.IJobSchedulerConfigurationDAO;
import org.libreplan.business.common.entities.Connector;
import org.libreplan.business.common.entities.JobClassNameEnum;
import org.libreplan.business.common.entities.JobSchedulerConfiguration;
import org.libreplan.web.common.IConfigurationModel;
import org.quartz.CronExpression;
import org.quartz.CronTrigger;
import org.quartz.JobExecutionContext;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Scope;
import org.springframework.scheduling.quartz.CronTriggerBean;
import org.springframework.scheduling.quartz.JobDetailBean;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* Implementation of scheduler manager
@ -55,15 +59,21 @@ public class SchedulerManager implements ISchedulerManager {
@Autowired
private Scheduler scheduler;
@Autowired
private IImportRosterFromTim importRosterFromTim;
@Autowired
private IExportTimesheetsToTim exportTimesheetsToTim;
@Autowired
private IJobSchedulerConfigurationDAO jobSchedulerConfigurationDAO;
@Autowired
private ApplicationContext applicationContext;
@Autowired
private IConfigurationModel configurationModel;
/**
* suffix for trigger -group and -name
*/
private static final String TRIGGER_SUFFIX = "-TRIGGER";
public Scheduler getScheduler() {
return scheduler;
}
@ -77,40 +87,163 @@ public class SchedulerManager implements ISchedulerManager {
List<JobSchedulerConfiguration> jobSchedulerConfigurations = jobSchedulerConfigurationDAO
.getAll();
for (JobSchedulerConfiguration conf : jobSchedulerConfigurations) {
CronTriggerBean cronTriggerBean = createCronTriggerBean(
conf.getTriggerGroup(), conf.getTriggerName(),
conf.getCronExpression());
if (cronTriggerBean != null) {
cronTriggerBean.setJobName(conf.getJobName());
cronTriggerBean.setJobGroup(conf.getJobGroup());
JobDetailBean jobDetailBean = createJobDetailBean(
conf.getJobName(), conf.getJobGroup(),
conf.getJobClassName());
if (jobDetailBean != null) {
scheduleJob(jobDetailBean, cronTriggerBean);
}
try {
scheduleOrUnscheduleJob(conf);
} catch (SchedulerException e) {
LOG.error("Unable to schedule", e);
}
}
}
@Override
public void scheduleOrUnscheduleJob(
JobSchedulerConfiguration jobSchedulerConfiguration) throws SchedulerException {
if (hasConnector(jobSchedulerConfiguration.getConnectorName())) {
if (isConnectorActivated(jobSchedulerConfiguration
.getConnectorName())) {
if (jobSchedulerConfiguration.isSchedule()) {
scheduleNewJob(jobSchedulerConfiguration);
return;
}
}
deleteJob(jobSchedulerConfiguration);
return;
}
if (!jobSchedulerConfiguration.isSchedule()) {
deleteJob(jobSchedulerConfiguration);
return;
}
scheduleNewJob(jobSchedulerConfiguration);
}
/**
* Creates and returns {@link CronTriggerBean}
* Check if {@link JobSchedulerConfiguration} has a connector
*
* @param triggerGroup
* the trigger group
* @param triggerName
* the trigger name
* @param cronExpression
* the cron expression string
* @param connectorName
* the connector to check for
* @return true if connector is not null or empty
*/
private CronTriggerBean createCronTriggerBean(String triggerGroup,
String triggerName, String cronExpression) {
CronTriggerBean cronTriggerBean = new CronTriggerBean();
cronTriggerBean.setGroup(triggerGroup);
cronTriggerBean.setName(triggerName);
private boolean hasConnector(String connectorName) {
return !StringUtils.isBlank(connectorName);
}
/**
* Check if the specified <code>{@link Connector}</code> is activated
*
* @param connectorName
* the connector to check for activated
* @return true if activated
*/
private boolean isConnectorActivated(String connectorName) {
configurationModel.initConnectorConfiguration();
Connector connector = configurationModel
.getConnectorByName(connectorName);
if (connector == null) {
return false;
}
return connector.isActivated();
}
@Override
public void deleteJob(JobSchedulerConfiguration jobSchedulerConfiguration) throws SchedulerException {
String triggerName = jobSchedulerConfiguration.getJobName()
+ TRIGGER_SUFFIX;
String triggerGroup = jobSchedulerConfiguration.getJobGroup()
+ TRIGGER_SUFFIX;
CronTriggerBean trigger = getTriggerBean(triggerName, triggerGroup);
if (trigger == null) {
LOG.warn("Trigger not found");
return;
}
if (isJobCurrentlyExecuting(triggerName, triggerGroup)) {
LOG.warn("Job is currently executing...");
return;
}
// deleteJob doesn't work using unscheduleJob
this.scheduler.unscheduleJob(trigger.getName(), trigger.getGroup());
}
/**
* Checks if job is currently running for the specified
* <code>triggerName</code> and <code>triggerGroup</code>
*
* @param triggerName
* the triggerName
* @param triggerGroup
* the triggerGroup
* @return true if job is currently running, otherwise false
*/
@SuppressWarnings("unchecked")
private boolean isJobCurrentlyExecuting(String triggerName,
String triggerGroup) {
try {
List<JobExecutionContext> currentExecutingJobs = this.scheduler
.getCurrentlyExecutingJobs();
for (JobExecutionContext jobExecutionContext : currentExecutingJobs) {
String name = jobExecutionContext.getTrigger().getName();
String group = jobExecutionContext.getTrigger().getGroup();
if (triggerName.equals(name) && triggerGroup.equals(group)) {
return true;
}
}
} catch (SchedulerException e) {
LOG.error("Unable to get currently executing jobs", e);
}
return false;
}
/**
* Creates {@link CronTriggerBean} and {@link JobDetailBean} based on the
* specified <code>{@link JobSchedulerConfiguration}</code>. First delete
* job if exist and then schedule it
*
* @param jobSchedulerConfiguration
* where to reade jobs to be scheduled
* @throws SchedulerException
* if unable to delete and/or schedule job
*/
private void scheduleNewJob(
JobSchedulerConfiguration jobSchedulerConfiguration) throws SchedulerException {
CronTriggerBean cronTriggerBean = createCronTriggerBean(jobSchedulerConfiguration);
if (cronTriggerBean == null) {
return;
}
JobDetailBean jobDetailBean = createJobDetailBean(jobSchedulerConfiguration);
if (jobDetailBean == null) {
return;
}
deleteJob(jobSchedulerConfiguration);
this.scheduler.scheduleJob(jobDetailBean, cronTriggerBean);
}
/**
* Creates {@link CronTriggerBean} from the specified
* <code>{@link JobSchedulerConfiguration}</code>
*
* @param jobSchedulerConfiguration
* configuration to create <code>CronTriggerBean</>
* @return the created <code>CronTriggerBean</code> or null if unable to
* create it
*/
private CronTriggerBean createCronTriggerBean(
JobSchedulerConfiguration jobSchedulerConfiguration) {
CronTriggerBean cronTriggerBean = new CronTriggerBean();
cronTriggerBean.setName(jobSchedulerConfiguration.getJobName() + TRIGGER_SUFFIX);
cronTriggerBean.setGroup(jobSchedulerConfiguration.getJobGroup()
+ TRIGGER_SUFFIX);
try {
cronTriggerBean.setCronExpression(new CronExpression(
jobSchedulerConfiguration.getCronExpression()));
cronTriggerBean.setJobName(jobSchedulerConfiguration.getJobName());
cronTriggerBean
.setCronExpression(new CronExpression(cronExpression));
.setJobGroup(jobSchedulerConfiguration.getJobGroup());
return cronTriggerBean;
} catch (ParseException e) {
LOG.error("Unable to parse cron expression", e);
@ -119,56 +252,33 @@ public class SchedulerManager implements ISchedulerManager {
}
/**
* Creates and returns {@link JobDetailBean}
* Creates {@link JobDetailBean} from the specified
* <code>{@link JobSchedulerConfiguration}</code>
*
* @param jobName
* the job name
* @param jobGroup
* the job group
* @param jobClassName
* the job classname
* @param jobSchedulerConfiguration
* configuration to create <code>JobDetailBean</>
* @return the created <code>JobDetailBean</code> or null if unable to it
*/
private JobDetailBean createJobDetailBean(String jobName, String jobGroup,
String jobClassName) {
private JobDetailBean createJobDetailBean(
JobSchedulerConfiguration jobSchedulerConfiguration) {
JobDetailBean jobDetailBean = new JobDetailBean();
Class jobClass = getJobClass(jobClassName);
Class<?> jobClass = getJobClass(jobSchedulerConfiguration
.getJobClassName());
if (jobClass == null) {
LOG.error("JobClass '" + jobClassName + "' not found");
return null;
}
jobDetailBean.setGroup(jobGroup);
jobDetailBean.setName(jobName);
jobDetailBean.setName(jobSchedulerConfiguration.getJobName());
jobDetailBean.setGroup(jobSchedulerConfiguration.getJobGroup());
jobDetailBean.setJobClass(jobClass);
Map<String, Object> jobDataAsMap = new HashMap<String, Object>();
if (jobDetailBean.getJobClass().getSimpleName()
.equals("ImportRosterFromTimJob")) {
jobDataAsMap.put("importRosterFromTim", importRosterFromTim);
} else {
jobDataAsMap.put("exportTimesheetsToTim", exportTimesheetsToTim);
}
jobDataAsMap.put("applicationContext", applicationContext);
jobDetailBean.setJobDataAsMap(jobDataAsMap);
return jobDetailBean;
}
/**
* Schedules the job specified by <code>{@link JobDetailBean}</code> and
* link it with the specified <code>{@link CronTriggerBean}</code>
*
* @param jobDetailBean
* the jobDetailBean
* @param cronTriggerBean
* the cronTriggerBean
*/
private void scheduleJob(JobDetailBean jobDetailBean,
CronTriggerBean cronTriggerBean) {
try {
this.scheduler.scheduleJob(jobDetailBean, cronTriggerBean);
} catch (SchedulerException e) {
LOG.error("unable to schedule job", e);
}
}
/**
* returns jobClass based on <code>jobClassName</code> parameter
@ -176,79 +286,52 @@ public class SchedulerManager implements ISchedulerManager {
* @param jobClassName
* job className
*/
private Class getJobClass(String jobClassName) {
if (jobClassName.equals("ImportRosterFromTimJob")) {
return org.libreplan.importers.ImportRosterFromTimJob.class;
private Class<?> getJobClass(JobClassNameEnum jobClassName) {
try {
return Class.forName(jobClassName.getPackageName() + "."
+ jobClassName.getName());
} catch (ClassNotFoundException e) {
LOG.error("Unable to get class object '" + jobClassName + "'", e);
}
if (jobClassName.equals("ExportTimesheetToTimJob")) {
return org.libreplan.importers.ExportTimesheetToTimJob.class;
}
return null;
}
@Override
public void rescheduleJob(JobSchedulerConfiguration jobSchedulerConfiguration) {
CronTriggerBean cronTriggerBean = createCronTriggerBean(
jobSchedulerConfiguration.getTriggerGroup(),
jobSchedulerConfiguration.getTriggerName(),
jobSchedulerConfiguration.getCronExpression());
cronTriggerBean.setName(jobSchedulerConfiguration.getTriggerName());
cronTriggerBean.setGroup(jobSchedulerConfiguration.getTriggerGroup());
public String getNextFireTime(
JobSchedulerConfiguration jobSchedulerConfiguration) {
try {
cronTriggerBean.setCronExpression(jobSchedulerConfiguration
.getCronExpression());
} catch (ParseException e) {
throw new RuntimeException("Invalid cron expression");
}
cronTriggerBean.setJobName(jobSchedulerConfiguration.getJobName());
cronTriggerBean.setJobGroup(jobSchedulerConfiguration.getJobGroup());
try {
scheduler.rescheduleJob(jobSchedulerConfiguration.getTriggerName(),
jobSchedulerConfiguration.getTriggerGroup(),
cronTriggerBean);
} catch (SchedulerException e) {
throw new RuntimeException("Unable to reschedule the job");
}
}
@Override
@Transactional(readOnly = true)
public List<SchedulerInfo> getSchedulerInfos() {
List<JobSchedulerConfiguration> jobSchedulerConfigurations = jobSchedulerConfigurationDAO
.getAll();
List<SchedulerInfo> results = new ArrayList<SchedulerInfo>();
for (JobSchedulerConfiguration jobSchedulerConfiguration : jobSchedulerConfigurations) {
SchedulerInfo schedulerInfo = new SchedulerInfo();
schedulerInfo
.setJobSchedulerConfiguration(jobSchedulerConfiguration);
try {
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(
jobSchedulerConfiguration.getTriggerName(),
jobSchedulerConfiguration.getTriggerGroup());
if (trigger != null) {
schedulerInfo.setNextFireTime(trigger.getNextFireTime()
.toString());
}
results.add(schedulerInfo);
} catch (SchedulerException e) {
LOG.error("unable to get the trigger");
CronTrigger trigger = (CronTrigger) this.scheduler.getTrigger(
jobSchedulerConfiguration.getJobName() + TRIGGER_SUFFIX,
jobSchedulerConfiguration.getJobGroup()
+ TRIGGER_SUFFIX);
if (trigger != null) {
return trigger.getNextFireTime().toString();
}
} catch (SchedulerException e) {
LOG.error("unable to get the trigger", e);
}
return results;
return "";
}
@Override
public void doManual(String jobName) {
if (jobName.equals("Import roster from Tim")) {
importRosterFromTim.importRosters();
return;
}
if (jobName.equals("Export timesheet to Tim")) {
exportTimesheetsToTim.exportTimesheets();
return;
/**
* gets the {@link CronTriggerBean} for the specified
* <code>triggerName</code> and <code>tirggerGroup</code>
*
* @param triggerName
* the trigger name
* @param triggerGroup
* the trigger group
* @return CronTriggerBean if found, otherwise null
*/
private CronTriggerBean getTriggerBean(String triggerName,
String triggerGroup) {
try {
return (CronTriggerBean) this.scheduler.getTrigger(triggerName,
triggerGroup);
} catch (SchedulerException e) {
LOG.error("Unable to get job trigger", e);
}
return null;
}
}

View file

@ -228,6 +228,12 @@ public class ConfigurationController extends GenericForwardComposer {
configurationModel.confirm();
configurationModel.init();
messages.showMessage(Level.INFO, _("Changes saved"));
if (!configurationModel
.scheduleOrUnscheduleJobs(getSelectedConnector())) {
messages.showMessage(
Level.ERROR,
_("Scheduling or unscheduling of jobs for this connector is not completed"));
}
reloadWindow();
reloadEntitySequences();
reloadConnectors();

View file

@ -96,6 +96,9 @@ public class ConfigurationModel implements IConfigurationModel {
@Autowired
private IConnectorDAO connectorDAO;
@Autowired
private IJobSchedulerModel jobSchedulerModel;
@Override
@Transactional(readOnly = true)
public List<BaseCalendar> getCalendars() {
@ -136,7 +139,9 @@ public class ConfigurationModel implements IConfigurationModel {
}
}
private void initConnectorConfiguration() {
@Override
@Transactional(readOnly = true)
public void initConnectorConfiguration() {
connectors = connectorDAO.getAll();
forceLoadConnectors();
}
@ -748,4 +753,9 @@ public class ConfigurationModel implements IConfigurationModel {
return null;
}
@Override
public boolean scheduleOrUnscheduleJobs(Connector connector) {
return jobSchedulerModel.scheduleOrUnscheduleJobs(connector);
}
}

View file

@ -195,8 +195,12 @@ public interface IConfigurationModel {
void setJiraConnectorTypeOfWorkHours(TypeOfWorkHours typeOfWorkHours);
void initConnectorConfiguration();
List<Connector> getConnectors();
Connector getConnectorByName(String name);
boolean scheduleOrUnscheduleJobs(Connector connector);
}

View file

@ -21,20 +21,120 @@ package org.libreplan.web.common;
import java.util.List;
import org.libreplan.importers.SchedulerInfo;
import org.libreplan.business.common.entities.Connector;
import org.libreplan.business.common.entities.JobSchedulerConfiguration;
import org.libreplan.business.common.entities.PredefinedConnectorProperties;
import org.libreplan.business.common.exceptions.ValidationException;
/**
* Contract for {@link JobSchedulerModel}.
*
* @author Manuel Rego Casasnovas <rego@igalia.com>
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
public interface IJobSchedulerModel {
List<SchedulerInfo> getSchedulerInfos();
/**
* returns all job scheduler configurations
*
* @return list of <code>JobSchedulerConfiguration</code>
*/
List<JobSchedulerConfiguration> getJobSchedulerConfigurations();
void doManual(SchedulerInfo schedulerInfo);
/**
* returns next fire time for the specified job from
* <code>{@link JobSchedulerConfiguration}</code>
*
* @param jobSchedulerConfiguration
* the job scheduler configuration
*/
String getNextFireTime(JobSchedulerConfiguration jobSchedulerConfiguration);
void saveJobConfigurationAndReschedule(String jobGroup, String jobName,
String cronExp);
/**
* Do manual action(replacement of scheduling)
*
* @param jobSchedulerConfiguration
* the job configuration
*/
void doManual(JobSchedulerConfiguration jobSchedulerConfiguration);
/**
* Prepares for create a new {@link JobSchedulerConfiguration}.
*/
void initCreate();
/**
* Prepares for edit {@link JobSchedulerConfiguration}
*
* @param jobSchedulerConfiguration
* object to be edited
*/
void initEdit(JobSchedulerConfiguration jobSchedulerConfiguration);
/**
* Gets the current {@link JobSchedulerConfiguration}.
*
* @return A {@link JobSchedulerConfiguration}
*/
JobSchedulerConfiguration getJobSchedulerConfiguration();
/**
* Saves the current {@link JobSchedulerConfiguration}
*
* @throws ValidationException
* if validation fails
*/
void confirmSave() throws ValidationException;
/**
* Cancels the current {@link JobSchedulerConfiguration}
*/
void cancel();
/**
* Removes the current {@link JobSchedulerConfiguration}
*
* @param jobSchedulerConfiguration
* object to be removed
*/
void remove(JobSchedulerConfiguration jobSchedulerConfiguration);
/**
* returns list of connectors
*/
List<Connector> getConnectors();
/**
* Schedule or unschedule jobs for the specified <code>connector</code>
*
* schedule all jobs of the specified <code>connector</code>'s property
* {@link PredefinedConnectorProperties#ACTIVATED} is 'Y', otherwise
* unschedule the jobs
*
* @param connector
* where to check if property is changed
* @return true if (un)scheduling is successful, false otherwise
*/
boolean scheduleOrUnscheduleJobs(Connector connector);
/**
* schedule or unschedule job for the specified job in
* <code>{@link JobSchedulerConfiguration}</code>
*
* @return true if scheduling is succeeded, false otherwise
*/
boolean scheduleOrUnscheduleJob();
/**
* Delete job specified in <code>{@link JobSchedulerConfiguration}</code>
*
* @param jobSchedulerConfiguration
* configuration for the job to be deleted
* @return true if job is successfully deleted from the scheduler, false
* otherwise
*/
boolean deleteScheduledJob(
JobSchedulerConfiguration jobSchedulerConfiguration);
}

View file

@ -22,20 +22,23 @@ package org.libreplan.web.common;
import static org.libreplan.web.I18nHelper._;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.libreplan.business.common.entities.Connector;
import org.libreplan.business.common.entities.JobClassNameEnum;
import org.libreplan.business.common.entities.JobSchedulerConfiguration;
import org.libreplan.importers.SchedulerInfo;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.common.exceptions.ValidationException;
import org.quartz.CronExpression;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.WrongValueException;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.util.GenericForwardComposer;
import org.zkoss.zul.Button;
import org.zkoss.zul.Grid;
import org.zkoss.zul.Hbox;
@ -50,19 +53,21 @@ import org.zkoss.zul.api.Textbox;
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
public class JobSchedulerController extends GenericForwardComposer {
public class JobSchedulerController extends
BaseCRUDController<JobSchedulerConfiguration> {
private static final Log LOG = LogFactory
.getLog(JobSchedulerController.class);
private Grid jobSchedulerGrid;
private Grid listJobSchedulings;
private Grid cronExpressionGrid;
private Popup cronExpressionInputPopup;
private Label jobGroup;
private Label jobName;
private Textbox cronExpressionTextBox;
private Textbox cronExpressionSeconds;
private Textbox cronExpressionMinutes;
private Textbox cronExpressionHours;
@ -76,89 +81,191 @@ public class JobSchedulerController extends GenericForwardComposer {
@Override
public void doAfterCompose(Component comp) throws Exception {
super.doAfterCompose(comp);
comp.setAttribute("jobSchedulerController", this);
listJobSchedulings = (Grid) listWindow
.getFellowIfAny("listJobSchedulings");
listJobSchedulings.getModel();
initCronExpressionPopup();
}
public List<SchedulerInfo> getSchedulerInfos() {
return jobSchedulerModel.getSchedulerInfos();
/**
* initializes cron expressions for popup
*/
private void initCronExpressionPopup() {
cronExpressionTextBox = (Textbox) editWindow
.getFellow("cronExpressionTextBox");
cronExpressionInputPopup = (Popup) editWindow
.getFellow("cronExpressionInputPopup");
jobGroup = (Label) cronExpressionInputPopup.getFellow("jobGroup");
jobName = (Label) cronExpressionInputPopup.getFellow("jobName");
cronExpressionGrid = (Grid) cronExpressionInputPopup
.getFellow("cronExpressionGrid");
cronExpressionSeconds = (Textbox) cronExpressionGrid
.getFellow("cronExpressionSeconds");
cronExpressionMinutes = (Textbox) cronExpressionGrid
.getFellow("cronExpressionMinutes");
cronExpressionHours = (Textbox) cronExpressionGrid
.getFellow("cronExpressionHours");
cronExpressionDayOfMonth = (Textbox) cronExpressionGrid
.getFellow("cronExpressionDayOfMonth");
cronExpressionMonth = (Textbox) cronExpressionGrid
.getFellow("cronExpressionMonth");
cronExpressionDayOfWeek = (Textbox) cronExpressionGrid
.getFellow("cronExpressionDayOfWeek");
cronExpressionYear = (Textbox) cronExpressionGrid
.getFellow("cronExpressionYear");
}
/**
* returns a list of {@link JobSchedulerConfiguration}
*/
public List<JobSchedulerConfiguration> getJobSchedulerConfigurations() {
return jobSchedulerModel.getJobSchedulerConfigurations();
}
/**
* returns {@link JobSchedulerConfiguration}
*/
public JobSchedulerConfiguration getJobSchedulerConfiguration() {
return jobSchedulerModel.getJobSchedulerConfiguration();
}
/**
* returns all predefined jobs
*/
public JobClassNameEnum[] getJobNames() {
return JobClassNameEnum.values();
}
/**
* return list of connectorNames
*/
public List<String> getConnectorNames() {
List<Connector> connectors = jobSchedulerModel.getConnectors();
List<String> connectorNames = new ArrayList<String>();
for (Connector connector : connectors) {
connectorNames.add(connector.getName());
}
return connectorNames;
}
/**
* renders job scheduling and returns {@link RowRenderer}
*/
public RowRenderer getJobSchedulingRenderer() {
return new RowRenderer() {
@Override
public void render(Row row, Object data) {
SchedulerInfo schedulerInfo = (SchedulerInfo) data;
final JobSchedulerConfiguration jobSchedulerConfiguration = (JobSchedulerConfiguration) data;
row.setValue(data);
Util.appendLabel(row, schedulerInfo
.getJobSchedulerConfiguration().getJobGroup());
Util.appendLabel(row, schedulerInfo
.getJobSchedulerConfiguration().getJobName());
appendCronExpressionAndButton(row, schedulerInfo);
Util.appendLabel(row, schedulerInfo.getNextFireTime());
appendManualStart(row, schedulerInfo);
Util.appendLabel(row, jobSchedulerConfiguration.getJobGroup());
Util.appendLabel(row, jobSchedulerConfiguration.getJobName());
Util.appendLabel(row,
jobSchedulerConfiguration.getCronExpression());
Util.appendLabel(row,
getNextFireTime(jobSchedulerConfiguration));
Hbox hbox = new Hbox();
hbox.appendChild(createManualButton(new EventListener() {
@Override
public void onEvent(Event event) throws Exception {
jobSchedulerModel.doManual(jobSchedulerConfiguration);
}
}));
hbox.appendChild(Util.createEditButton(new EventListener() {
@Override
public void onEvent(Event event) {
goToEditForm(jobSchedulerConfiguration);
}
}));
hbox.appendChild(Util.createRemoveButton(new EventListener() {
@Override
public void onEvent(Event event) {
confirmDelete(jobSchedulerConfiguration);
}
}));
row.appendChild(hbox);
}
};
}
private void appendCronExpressionAndButton(final Row row,
final SchedulerInfo schedulerInfo) {
final Hbox hBox = new Hbox();
hBox.setWidth("170px");
Label label = new Label(schedulerInfo.getJobSchedulerConfiguration()
.getCronExpression());
label.setHflex("1");
hBox.appendChild(label);
Button button = Util.createEditButton(new EventListener() {
@Override
public void onEvent(Event event) throws Exception {
setupCronExpressionPopup(schedulerInfo);
cronExpressionInputPopup.open(hBox);
}
});
hBox.appendChild(button);
row.appendChild(hBox);
/**
* returns the next fire time for the specified job in
* {@link JobSchedulerConfiguration}
*
* @param jobSchedulerConfiguration
* the job scheduler configuration
*/
private String getNextFireTime(
JobSchedulerConfiguration jobSchedulerConfiguration) {
return jobSchedulerModel.getNextFireTime(jobSchedulerConfiguration);
}
/**
* creates and returns a button
*
* @param eventListener
* Event listener for this button
*/
private static Button createManualButton(EventListener eventListener) {
Button button = new Button(_("Manual"));
button.setTooltiptext(_("Manual"));
button.addEventListener(Events.ON_CLICK, eventListener);
return button;
}
private void setupCronExpressionPopup(final SchedulerInfo schedulerInfo) {
JobSchedulerConfiguration jobSchedulerConfiguration = schedulerInfo.getJobSchedulerConfiguration();
jobGroup.setValue(jobSchedulerConfiguration.getJobGroup());
jobName.setValue(jobSchedulerConfiguration.getJobName());
/**
* Opens the <code>cronExpressionInputPopup</code>
*/
public void openPopup() {
setupCronExpressionPopup(getJobSchedulerConfiguration());
cronExpressionInputPopup.open(cronExpressionTextBox, "after_start");
}
String cronExpression = jobSchedulerConfiguration.getCronExpression();
String[] cronExpressionArray = StringUtils.split(cronExpression);
/**
* Sets the cronExpression values for <code>cronExpressionInputPopup</code>
*
* @param jobSchedulerConfiguration
* where to read the values
*/
private void setupCronExpressionPopup(
final JobSchedulerConfiguration jobSchedulerConfiguration) {
if (jobSchedulerConfiguration != null) {
jobGroup.setValue(jobSchedulerConfiguration.getJobGroup());
jobName.setValue(jobSchedulerConfiguration.getJobName());
cronExpressionSeconds.setValue(cronExpressionArray[0]);
cronExpressionMinutes.setValue(cronExpressionArray[1]);
cronExpressionHours.setValue(cronExpressionArray[2]);
cronExpressionDayOfMonth.setValue(cronExpressionArray[3]);
cronExpressionMonth.setValue(cronExpressionArray[4]);
cronExpressionDayOfWeek.setValue(cronExpressionArray[5]);
String cronExpression = jobSchedulerConfiguration
.getCronExpression();
if (cronExpression == null || cronExpression.isEmpty()) {
return;
}
if (cronExpressionArray.length == 7) {
cronExpressionYear.setValue(cronExpressionArray[6]);
String[] cronExpressionArray = StringUtils.split(cronExpression);
cronExpressionSeconds.setValue(cronExpressionArray[0]);
cronExpressionMinutes.setValue(cronExpressionArray[1]);
cronExpressionHours.setValue(cronExpressionArray[2]);
cronExpressionDayOfMonth.setValue(cronExpressionArray[3]);
cronExpressionMonth.setValue(cronExpressionArray[4]);
cronExpressionDayOfWeek.setValue(cronExpressionArray[5]);
if (cronExpressionArray.length == 7) {
cronExpressionYear.setValue(cronExpressionArray[6]);
}
}
}
private void appendManualStart(final Row row,
final SchedulerInfo schedulerInfo) {
final Button rescheduleButton = new Button(_("Manual"));
rescheduleButton.addEventListener(Events.ON_CLICK, new EventListener() {
@Override
public void onEvent(Event event) throws Exception {
jobSchedulerModel.doManual(schedulerInfo);
}
});
row.appendChild(rescheduleButton);
}
public void reschedule() {
/**
* sets the <code>cronExpressionTextBox</code> value from the
* <code>cronExpressionInputPopup</code>
*/
public void updateCronExpression() {
String cronExpression = getCronExpressionString();
try {
// Check cron expression format
@ -169,13 +276,16 @@ public class JobSchedulerController extends GenericForwardComposer {
_("Unable to parse cron expression") + ":\n"
+ e.getMessage());
}
jobSchedulerModel.saveJobConfigurationAndReschedule(
jobGroup.getValue(), jobName.getValue(), cronExpression);
cronExpressionTextBox.setValue(cronExpression);
cronExpressionInputPopup.close();
Util.reloadBindings(jobSchedulerGrid);
Util.saveBindings(cronExpressionTextBox);
}
/**
* Concatenating the cronExpression values
*
* @return cronExpression string
*/
private String getCronExpressionString() {
String cronExpression = "";
cronExpression += StringUtils.trimToEmpty(cronExpressionSeconds.getValue()) + " ";
@ -193,8 +303,61 @@ public class JobSchedulerController extends GenericForwardComposer {
return cronExpression;
}
public void cancel() {
/**
* closes the popup
*/
public void cancelPopup() {
cronExpressionInputPopup.close();
}
@Override
protected String getEntityType() {
return _("Job scheduling");
}
@Override
protected String getPluralEntityType() {
return _("Job scheduling");
}
@Override
protected void initCreate() {
jobSchedulerModel.initCreate();
}
@Override
protected void initEdit(JobSchedulerConfiguration entity) {
jobSchedulerModel.initEdit(entity);
}
@Override
protected void save() throws ValidationException {
jobSchedulerModel.confirmSave();
if (jobSchedulerModel.scheduleOrUnscheduleJob()) {
messagesForUser.showMessage(Level.INFO,
_("Job is scheduled/unscheduled"));
}
}
@Override
protected void cancel() {
jobSchedulerModel.cancel();
}
@Override
protected JobSchedulerConfiguration getEntityBeingEdited() {
return jobSchedulerModel.getJobSchedulerConfiguration();
}
@Override
protected void delete(JobSchedulerConfiguration entity)
throws InstanceNotFoundException {
jobSchedulerModel.remove(entity);
if (jobSchedulerModel.deleteScheduledJob(entity)) {
messagesForUser.showMessage(Level.INFO,
_("Job is deleted from scheduler"));
}
}
}

View file

@ -19,15 +19,20 @@
package org.libreplan.web.common;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.libreplan.business.common.daos.IConnectorDAO;
import org.libreplan.business.common.daos.IJobSchedulerConfigurationDAO;
import org.libreplan.business.common.entities.Connector;
import org.libreplan.business.common.entities.JobClassNameEnum;
import org.libreplan.business.common.entities.JobSchedulerConfiguration;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.common.exceptions.ValidationException;
import org.libreplan.importers.IExportTimesheetsToTim;
import org.libreplan.importers.IImportRosterFromTim;
import org.libreplan.importers.ISchedulerManager;
import org.libreplan.importers.SchedulerInfo;
import org.libreplan.web.common.concurrentdetection.OnConcurrentModification;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
@ -38,60 +43,132 @@ import org.springframework.transaction.annotation.Transactional;
* Model for UI operations related to {@link JobSchedulerConfiguration}.
*
* @author Manuel Rego Casasnovas <rego@igalia.com>
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
@Service
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
@OnConcurrentModification(goToPage = "/common/job_scheduling.zul")
public class JobSchedulerModel implements IJobSchedulerModel {
private JobSchedulerConfiguration jobSchedulerConfiguration;
@Autowired
private ISchedulerManager schedulerManager;
@Autowired
private IJobSchedulerConfigurationDAO jobSchedulerConfigurationDAO;
@Override
public List<SchedulerInfo> getSchedulerInfos() {
List<SchedulerInfo> schedulerInfoList = schedulerManager
.getSchedulerInfos();
Collections.sort(schedulerInfoList, new Comparator<SchedulerInfo>() {
@Autowired
private IConnectorDAO connectorDAO;
@Override
public int compare(SchedulerInfo o1, SchedulerInfo o2) {
int result = o1
.getJobSchedulerConfiguration()
.getJobGroup()
.compareTo(
o2.getJobSchedulerConfiguration().getJobGroup());
if (result == 0) {
result = o1
.getJobSchedulerConfiguration()
.getJobName()
.compareTo(
o2.getJobSchedulerConfiguration()
.getJobName());
}
return result;
}
});
return schedulerInfoList;
@Autowired
private IImportRosterFromTim importRosterFromTim;
@Autowired
private IExportTimesheetsToTim exportTimesheetsToTim;
@Override
@Transactional(readOnly = true)
public List<JobSchedulerConfiguration> getJobSchedulerConfigurations() {
return jobSchedulerConfigurationDAO.getAll();
}
@Override
public void doManual(SchedulerInfo schedulerInfo) {
schedulerManager.doManual(schedulerInfo.getJobSchedulerConfiguration()
.getJobName());
public String getNextFireTime(
JobSchedulerConfiguration jobSchedulerConfiguration) {
return schedulerManager.getNextFireTime(jobSchedulerConfiguration);
}
@Override
public void doManual(JobSchedulerConfiguration jobSchedulerConfiguration) {
String name = jobSchedulerConfiguration.getJobClassName().getName();
if (name.equals(JobClassNameEnum.IMPORT_ROSTER_FROM_TIM_JOB.getName())) {
importRosterFromTim.importRosters();
return;
}
if (name.equals(JobClassNameEnum.EXPORT_TIMESHEET_TO_TIM_JOB.getName())) {
exportTimesheetsToTim.exportTimesheets();
return;
}
}
@Override
public void initCreate() {
this.jobSchedulerConfiguration = JobSchedulerConfiguration.create();
}
@Override
public void initEdit(JobSchedulerConfiguration jobSchedulerConfiguration) {
this.jobSchedulerConfiguration = jobSchedulerConfiguration;
}
@Override
public JobSchedulerConfiguration getJobSchedulerConfiguration() {
return this.jobSchedulerConfiguration;
}
@Override
@Transactional
public void saveJobConfigurationAndReschedule(String jobGroup,
String jobName, String cronExp) {
JobSchedulerConfiguration jobSchedulerConfiguration = jobSchedulerConfigurationDAO
.findByJobGroupAndJobName(jobGroup, jobName);
jobSchedulerConfiguration.setCronExpression(cronExp);
public void confirmSave() throws ValidationException {
jobSchedulerConfigurationDAO.save(jobSchedulerConfiguration);
schedulerManager.rescheduleJob(jobSchedulerConfiguration);
}
@Override
public void cancel() {
jobSchedulerConfiguration = null;
}
@Override
@Transactional
public void remove(JobSchedulerConfiguration jobSchedulerConfiguration) {
try {
jobSchedulerConfigurationDAO.remove(jobSchedulerConfiguration
.getId());
} catch (InstanceNotFoundException e) {
throw new RuntimeException(e);
}
}
@Override
@Transactional(readOnly = true)
public List<Connector> getConnectors() {
return connectorDAO.getAll();
}
@Override
public boolean scheduleOrUnscheduleJobs(Connector connector) {
List<JobSchedulerConfiguration> jobSchedulerConfigurations = jobSchedulerConfigurationDAO
.findByConnectorName(connector.getName());
for (JobSchedulerConfiguration jobSchedulerConfiguration : jobSchedulerConfigurations) {
try {
schedulerManager.scheduleOrUnscheduleJob(jobSchedulerConfiguration);
} catch (SchedulerException e) {
return false;
}
}
return true;
}
@Override
public boolean scheduleOrUnscheduleJob() {
try {
schedulerManager.scheduleOrUnscheduleJob(jobSchedulerConfiguration);
} catch (SchedulerException e) {
throw new RuntimeException("Failed to schedule job", e);
}
return true;
}
@Override
public boolean deleteScheduledJob(
JobSchedulerConfiguration jobSchedulerConfiguration) {
try {
schedulerManager.deleteJob(jobSchedulerConfiguration);
} catch (SchedulerException e) {
throw new RuntimeException("Failed to delete job", e);
}
return true;
}
}

View file

@ -24,7 +24,6 @@ import static org.libreplan.web.I18nHelper._;
import org.apache.commons.logging.LogFactory;
import org.libreplan.business.common.daos.IConnectorDAO;
import org.libreplan.business.common.entities.Connector;
import org.libreplan.business.common.entities.PredefinedConnectorProperties;
import org.libreplan.business.common.entities.PredefinedConnectors;
import org.libreplan.business.orders.entities.OrderSyncInfo;
import org.libreplan.importers.IExportTimesheetsToTim;
@ -108,8 +107,6 @@ public class TimSynchronizationController extends GenericForwardComposer {
if (connector == null) {
return false;
}
return connector.getPropertiesAsMap()
.get(PredefinedConnectorProperties.ACTIVATED)
.equalsIgnoreCase("Y");
return connector.isActivated();
}
}

View file

@ -0,0 +1,167 @@
<!--
This file is part of LibrePlan
Copyright (C) 2013 St. Antoniusziekenhuis
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<window id="${arg.id}">
<caption id="caption" sclass="caption-title" />
<grid id="jobSchedulerGrid" fixedLayout="true">
<columns>
<column width="200px" />
<column/>
</columns>
<rows>
<row>
<label value="${i18n:_('Job group')}" />
<textbox id="jobGroupTextBox" width="400px"
constraint="no empty:${i18n:_('cannot be empty')}"
value="@{controller.jobSchedulerConfiguration.jobGroup}"/>
</row>
<row>
<label value="${i18n:_('Job name')}" />
<textbox id="jobNameTextBox" width="400px"
constraint="no empty:${i18n:_('cannot be empty')}"
value="@{controller.jobSchedulerConfiguration.jobName}"/>
</row>
<row>
<label value="${i18n:_('Cron expression')}" />
<hbox>
<textbox id="cronExpressionTextBox"
value="@{controller.jobSchedulerConfiguration.cronExpression}"
constraint="no empty:${i18n:_('cannot be empty')}"
width="400px" disabled="true"/>
<button sclass="icono" image="/common/img/ico_editar1.png"
hoverImage="/common/img/ico_editar.png"
tooltiptext="${i18n:_('Edit')}"
onClick="controller.openPopup()"/>
</hbox>
</row>
<row>
<label value="${i18n:_('Job class name')}" />
<combobox id="jobCombo" autodrop="true" width="400px"
constraint="no empty:${i18n:_('cannot be empty')}"
model="@{controller.jobNames}"
selectedItem="@{controller.jobSchedulerConfiguration.jobClassName}">
<comboitem
self="@{each=jobNames}"
label="@{jobNames.name}"
value="@{jobNames}" />
</combobox>
</row>
<row>
<label value="${i18n:_('Connector')}" />
<combobox id="connectorCombo" autodrop="true" width="400px"
model="@{controller.connectorNames}"
selectedItem="@{controller.jobSchedulerConfiguration.connectorName}">
<comboitem
self="@{each=connectorNames}"
label="@{connectorNames.name}"
value="@{connectorNames}" />
</combobox>
</row>
<row>
<label value="${i18n:_('Schedule')}" />
<checkbox checked="@{controller.jobSchedulerConfiguration.schedule}" />
</row>
</rows>
</grid>
<!-- Control buttons -->
<button onClick="controller.saveAndExit()"
label="${i18n:_('Save')}"
sclass="save-button global-action" />
<button onClick="controller.saveAndContinue()"
label="${i18n:_('Save and Continue')}"
sclass="save-button global-action" />
<button onClick="controller.cancelForm()"
label="${i18n:_('Cancel')}"
sclass="cancel-button global-action" />
<div>
<groupbox closable="false" >
<caption label="${i18n:_('Cron expression format')}" />
<grid width="500px">
<columns>
<column label="${i18n:_('Field')}" width="150px"/>
<column label="${i18n:_('Allowed values')}" />
</columns>
<rows>
<row>
<label value="${i18n:_('Seconds')}"/>
<label value="0-59"/>
</row>
<row>
<label value="${i18n:_('Minutes')}"/>
<label value="0-59"/>
</row>
<row>
<label value="${i18n:_('Hours')}"/>
<label value="0-23"/>
</row>
<row>
<label value="${i18n:_('Day of month')}"/>
<label value="1-31"/>
</row>
<row>
<label value="${i18n:_('Month')}"/>
<span>1-12 (<label value="${i18n:_('or names')}"/> [JAN-DEC])</span>
</row>
<row>
<label value="${i18n:_('Day of week')}"/>
<span>0-7 (<label value="${i18n:_('0 or 7 is Sunday, or use names')}"/> [SUN-SAT])</span>
</row>
<row>
<label value="${i18n:_('Year (optional)')}"/>
<label value="1970-2099"/>
</row>
</rows>
</grid>
<div>
${i18n:_("For more details on cron expression click")}
<a href="http://www.manpagez.com/man/5/crontab/">${i18n:_('here')}.</a>
</div>
</groupbox>
</div>
<popup id="cronExpressionInputPopup" width="525px">
<label id="jobGroup" value="@{jobSchedulerController.jobGroup}"/>
<label id="jobName" value="@{jobSchedulerController.jobName}"/>
<grid id="cronExpressionGrid">
<columns>
<column label="${i18n:_('Seconds')}"/>
<column label="${i18n:_('Minutes')}" />
<column label="${i18n:_('Hours')}"/>
<column label="${i18n:_('Day of month')}"/>
<column label="${i18n:_('Month')}"/>
<column label="${i18n:_('Day of week')}"/>
<column label="${i18n:_('Year')}"/>
</columns>
<rows>
<row>
<textbox id="cronExpressionSeconds" width="60px" />
<textbox id="cronExpressionMinutes" width="60px" />
<textbox id="cronExpressionHours" width="60px" />
<textbox id="cronExpressionDayOfMonth" width="60px" />
<textbox id="cronExpressionMonth" width="60px" />
<textbox id="cronExpressionDayOfWeek" width="60px" />
<textbox id="cronExpressionYear" width="60px" />
</row>
</rows>
</grid>
<button onClick="controller.updateCronExpression();" label="${i18n:_('OK')}" />
<button onClick="controller.cancelPopup();" label="${i18n:_('Cancel')}" />
</popup>
</window>

View file

@ -0,0 +1,36 @@
<!--
This file is part of LibrePlan
Copyright (C) 2013 St. Antoniusziekenhuis
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<window id="${arg.id}" title="${i18n:_('Job Scheduling List')}">
<grid id="listJobSchedulings"
rowRenderer="@{controller.jobSchedulingRenderer}"
model="@{controller.jobSchedulerConfigurations}"
mold="paging" pageSize="10" fixedLayout="true">
<columns>
<column label="${i18n:_('Job group')}" />
<column label="${i18n:_('Job name')}" />
<column label="${i18n:_('Cron expression')}" />
<column label="${i18n:_('Next fire time')}" />
<column label="${i18n:_('Operations')}" />
</columns>
</grid>
<button label="${i18n:_('Create')}" onClick="controller.goToCreateForm()"
sclass="create-button-global-action"/>
</window>

View file

@ -17,7 +17,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<?page id="exceptionDayTypesList" title="${i18n:_('LibrePlan: Job Scheduling')}" ?>
<?page id="jobSchedulingList" title="${i18n:_('LibrePlan: Job Scheduling')}" ?>
<?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit" ?>
<?init class="org.zkoss.zk.ui.util.Composition" arg0="/common/layout/template.zul"?>
@ -25,100 +25,14 @@
<?link rel="stylesheet" type="text/css" href="/common/css/libreplan.css"?>
<?link rel="stylesheet" type="text/css" href="/common/css/libreplan_zk.css"?>
<?component name="list" inline="true" macroURI="_listStretchesFunctionTemplates.zul"?>
<?component name="edit" inline="true" macroURI="_editStretchesFunctionTemplate.zul"?>
<?component name="list" inline="true" macroURI="_listJobScheduling.zul"?>
<?component name="edit" inline="true" macroURI="_editJobScheduling.zul"?>
<zk>
<window self="@{define(content)}"
apply="org.libreplan.web.common.JobSchedulerController">
<grid id="jobSchedulerGrid"
rowRenderer="@{jobSchedulerController.jobSchedulingRenderer}"
model="@{jobSchedulerController.schedulerInfos}">
<columns>
<column label="${i18n:_('Job group')}" />
<column label="${i18n:_('Job name')}" />
<column label="${i18n:_('Cron expression')}" />
<column label="${i18n:_('Next fire time')}" />
<column label="${i18n:_('Action')}" />
</columns>
</grid>
<div>
<groupbox closable="false" >
<caption label="${i18n:_('Cron expression format')}" />
<grid width="500px">
<columns>
<column label="${i18n:_('Field')}" width="150px"/>
<column label="${i18n:_('Allowed values')}" />
</columns>
<rows>
<row>
<label value="${i18n:_('Seconds')}"/>
<label value="0-59"/>
</row>
<row>
<label value="${i18n:_('Minutes')}"/>
<label value="0-59"/>
</row>
<row>
<label value="${i18n:_('Hours')}"/>
<label value="0-23"/>
</row>
<row>
<label value="${i18n:_('Day of month')}"/>
<label value="1-31"/>
</row>
<row>
<label value="${i18n:_('Month')}"/>
<span>1-12 (<label value="${i18n:_('or names')}"/> [JAN-DEC])</span>
</row>
<row>
<label value="${i18n:_('Day of week')}"/>
<span>0-7 (<label value="${i18n:_('0 or 7 is Sunday, or use names')}"/> [SUN-SAT])</span>
</row>
<row>
<label value="${i18n:_('Year (optional)')}"/>
<label value="1970-2099"/>
</row>
</rows>
</grid>
<div>
${i18n:_("For more details on cron expression click")}
<a href="http://www.manpagez.com/man/5/crontab/">${i18n:_('here')}.</a>
</div>
</groupbox>
</div>
<popup id="cronExpressionInputPopup" width="525px">
<label id="jobGroup" value="@{jobSchedulerController.schedulerInfo}"/>
-
<label id="jobName" value="@{jobSchedulerController.schedulerInfo}"/>
<grid id="cronExpressionGrid">
<columns>
<column label="${i18n:_('Seconds')}"/>
<column label="${i18n:_('Minutes')}" />
<column label="${i18n:_('Hours')}"/>
<column label="${i18n:_('Day of month')}"/>
<column label="${i18n:_('Month')}"/>
<column label="${i18n:_('Day of week')}"/>
<column label="${i18n:_('Year')}"/>
</columns>
<rows>
<row>
<textbox id="cronExpressionSeconds" width="60px" />
<textbox id="cronExpressionMinutes" width="60px" />
<textbox id="cronExpressionHours" width="60px" />
<textbox id="cronExpressionDayOfMonth" width="60px" />
<textbox id="cronExpressionMonth" width="60px" />
<textbox id="cronExpressionDayOfWeek" width="60px" />
<textbox id="cronExpressionYear" width="60px" />
</row>
</rows>
</grid>
<button onClick="jobSchedulerController.reschedule();" label="${i18n:_('OK')}" />
<button onClick="jobSchedulerController.cancel();" label="${i18n:_('Cancel')}" />
</popup>
<vbox id="messagesContainer" />
<list id="listWindow" />
<edit id="editWindow" />
</window>
</zk>