Skip to content

Commit

Permalink
SAK-40327 Better auto provisioning of jobs/triggers. (sakaiproject#5796)
Browse files Browse the repository at this point in the history
* SAK-40327 Added auto provisioning of jobs support.

This allows other services to automatically provision jobs and triggers into the scheduler at startup. This uses the standard XML that quartz supports.

* SAK-40327 event log purge switch to auto provision

Switch the trigger event log to use the newer auto provisioning rather than the spring XML.
  • Loading branch information
buckett authored and ern committed Jul 23, 2018
1 parent 1b9335c commit ad75902
Show file tree
Hide file tree
Showing 10 changed files with 275 additions and 48 deletions.
1 change: 1 addition & 0 deletions jobscheduler/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<module>scheduler-tool</module>
<module>scheduler-test-component</module>
<module>scheduler-test-component-shared</module>
<module>scheduler-utils</module>
</modules>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -136,5 +136,11 @@ public interface SchedulerManager
public void registerBeanJob(String jobName, JobBeanWrapper job);

public JobBeanWrapper getJobBeanWrapper(String beanWrapperId);

/**
* Is the job scheduler auto provisioning jobs and triggers.
* @return true if we're set to globally auto provision
*/
boolean isAutoProvisioning();
}

Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,15 @@ public class SchedulerManagerImpl implements SchedulerManager, SchedulerFactory,
globalJobListeners = new LinkedList<JobListener>();

private LinkedList<SpringInitialJobSchedule>
initialJobSchedule = null;
initialJobSchedule = new LinkedList<>();


// Map from a spring bean ID to a job class.
private HashMap<String,Class<? extends Job>> migration;
// is null before we know.
private Boolean isInitialStartup;

public void init()
public void init()
{
try
{
Expand Down Expand Up @@ -158,37 +160,12 @@ public void init()
}
}

boolean isInitialStartup = isInitialStartup(sqlService);
isInitialStartup = isInitialStartup();
if (isInitialStartup && autoDdl.booleanValue())
{
log.info("Performing initial population of the Quartz tables.");
sqlService.ddl(this.getClass().getClassLoader(), "init_locks2");
}
/*
Determine whether or not to load the jobs defined in the initialJobSchedules list. These jobs will be loaded
under the following conditions:
1) the server configuration property "scheduler.loadjobs" is "true"
2) "scheduler.loadjobs" is "init" and this is the first startup for the scheduler (eg. this is a new Sakai instance)
"scheduler.loadjobs" is set to "init" by default
*/
String
loadJobs = serverConfigurationService.getString(SCHEDULER_LOADJOBS, "init").trim();

List<SpringInitialJobSchedule>
initSchedules = getInitialJobSchedules();

boolean
loadInitSchedules = (initSchedules != null) && (initSchedules.size() > 0) &&
(("init".equalsIgnoreCase(loadJobs) && isInitialStartup) ||
"true".equalsIgnoreCase(loadJobs));

if (loadInitSchedules)
log.debug ("Preconfigured jobs will be loaded");
else
log.debug ("Preconfigured jobs will not be loaded");




// start scheduler and load jobs
SchedulerFactory schedFactory = new StdSchedulerFactory(qrtzProperties);
Expand Down Expand Up @@ -247,7 +224,7 @@ public void init()
scheduler.getListenerManager().addJobListener(jListener);
}

if (loadInitSchedules)
if (isAutoProvisioning())
{
log.debug ("Loading preconfigured jobs");
loadInitialSchedules();
Expand Down Expand Up @@ -405,6 +382,13 @@ private boolean isInitialStartup(SqlService sqlService)
}
}

public boolean isInitialStartup() {
if (isInitialStartup == null) {
isInitialStartup = isInitialStartup(sqlService);
}
return isInitialStartup;
}

/**
* Loads jobs and schedules triggers for preconfigured jobs.
*/
Expand Down Expand Up @@ -745,6 +729,22 @@ public JobBeanWrapper getJobBeanWrapper(String beanWrapperId) {
return getBeanJobs().get(beanWrapperId);
}

@Override
public boolean isAutoProvisioning() {
/*
Determine whether or not to load the jobs defined in the initialJobSchedules list. These jobs will be loaded
under the following conditions:
1) the server configuration property "scheduler.loadjobs" is "true"
2) "scheduler.loadjobs" is "init" and this is the first startup for the scheduler (eg. this is a new Sakai instance)
"scheduler.loadjobs" is set to "init" by default
*/
String loadJobs = serverConfigurationService.getString(SCHEDULER_LOADJOBS, "init").trim();

boolean loadInitSchedules = (("init".equalsIgnoreCase(loadJobs) && isInitialStartup) ||
"true".equalsIgnoreCase(loadJobs));
return loadInitSchedules;
}

public void setJobFactory(JobFactory jobFactory) {
this.jobFactory = jobFactory;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
import org.quartz.Trigger;
import org.sakaiproject.api.app.scheduler.TriggerWrapper;

/**
* @deprecated Use the AutoProvisionJobs class instead.
*/
@Deprecated
public class TriggerWrapperImpl implements TriggerWrapper
{
private Trigger trigger;
Expand Down
22 changes: 20 additions & 2 deletions jobscheduler/scheduler-component/pom.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
Expand Down Expand Up @@ -29,6 +30,23 @@
</dependency>
<dependency>
<groupId>org.sakaiproject.scheduler</groupId>
<artifactId>scheduler-component-shared</artifactId></dependency>
<artifactId>scheduler-component-shared</artifactId>
</dependency>
<dependency>
<groupId>org.sakaiproject.scheduler</groupId>
<artifactId>scheduler-utils</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>

<build>
<resources>
<resource>
<directory>${basedir}/src/resources</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
</project>
52 changes: 52 additions & 0 deletions jobscheduler/scheduler-component/src/resources/event-log-purge.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?xml version='1.0' encoding='utf-8'?>
<job-scheduling-data xmlns="http://www.quartz-scheduler.org/xml/JobSchedulingData"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.quartz-scheduler.org/xml/JobSchedulingData http://www.quartz-scheduler.org/xml/job_scheduling_data_2_0.xsd"
version="2.0">

<processing-directives>
<!-- The UI allows people to change the jobs and we don't want to overwrite those changes -->
<overwrite-existing-data>false</overwrite-existing-data>
<!-- We stop on the first error, this means if you don't like the trigger, just delete it and it won't
make any changes because the job exists -->
<ignore-duplicates>false</ignore-duplicates>
</processing-directives>

<schedule>
<!-- Put all the jobs in the default group -->
<job>
<!-- This is an old style job that uses our wrapper to get the instance from spring -->
<name>Event Log Purge</name>
<description>Purge the trigger events</description>
<job-class>org.sakaiproject.component.app.scheduler.jobs.SpringStatefulJobBeanWrapper</job-class>
<!-- We make the job durable otherwise when the trigger is removed the job goes and it gets re-created -->
<durability>true</durability>
<recover>false</recover>
<job-data-map>
<entry>
<key>org.sakaiproject.api.app.scheduler.JobBeanWrapper.bean</key>
<value>eventPurgeJob</value>
</entry>
<entry>
<key>org.sakaiproject.api.app.scheduler.JobBeanWrapper.jobType</key>
<value>Event Log Purge</value>
</entry>
</job-data-map>
</job>

<trigger>
<cron>
<name>Nightly Log Purge Trigger</name>
<job-name>Event Log Purge</job-name>
<job-data-map>
<entry>
<key>number.days</key>
<value>7</value>
</entry>
</job-data-map>
<!-- Every 5 minutes -->
<cron-expression>0 0 0 * * ? *</cron-expression>
</cron>
</trigger>
</schedule>
</job-scheduling-data>
26 changes: 9 additions & 17 deletions jobscheduler/scheduler-component/src/webapp/WEB-INF/components.xml
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,6 @@
<property name="jobFactory">
<ref bean="org.sakaiproject.component.app.scheduler.AutowiringSpringBeanJobFactory"/>
</property>
<property name="initialJobSchedules">
<list>
<ref bean="eventPurgeScheduling"/>
</list>
</property>
</bean>


Expand Down Expand Up @@ -177,18 +172,6 @@
</property>
</bean>

<bean id="eventPurgeScheduling" class="org.sakaiproject.component.app.scheduler.jobs.SpringInitialJobSchedule">
<property name="jobBeanWrapper"><ref bean="eventPurgeJobWrapper"/></property>
<property name="jobName" value="Event Log Purge"/>
<property name="triggerName" value="Nightly Log Purge Trigger"/>
<property name="cronExpression" value="0 0 0 * * ? *"/>
<property name="configuration">
<map>
<entry key="number.days" value="7"/>
</map>
</property>
</bean>

<!-- Conditions Job -->
<!-- simple job that fires a Datetime event -->
<bean id="datetimeJob"
Expand Down Expand Up @@ -446,6 +429,15 @@
</bean>


<!-- Automatically create event purge job -->
<bean class="org.sakaiproject.scheduler.util.AutoProvisionJobs" init-method="init">
<property name="schedulerManager" ref="org.sakaiproject.api.app.scheduler.SchedulerManager"/>
<property name="files">
<list>
<value>/event-log-purge.xml</value>
</list>
</property>
</bean>

<import resource="backfill-tool.xml"/>
<import resource="cm-components.xml"/>
Expand Down
41 changes: 41 additions & 0 deletions jobscheduler/scheduler-utils/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>scheduler</artifactId>
<groupId>org.sakaiproject.scheduler</groupId>
<version>19-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<groupId>org.sakaiproject.scheduler</groupId>
<artifactId>scheduler-utils</artifactId>
<name>Sakai Job Scheduler Utils </name>
<description>This holds the code for the automatic provsioning of jobs and triggers at startup. It needs to be
in the classloader that holds the XML for configuring the jobs.</description>

<dependencies>
<dependency>
<groupId>org.sakaiproject.kernel</groupId>
<artifactId>sakai-kernel-api</artifactId>
</dependency>
<dependency>
<groupId>org.sakaiproject.scheduler</groupId>
<artifactId>scheduler-api</artifactId>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package org.sakaiproject.scheduler.util;

import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.quartz.ObjectAlreadyExistsException;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.simpl.CascadingClassLoadHelper;
import org.quartz.spi.ClassLoadHelper;
import org.quartz.xml.ValidationException;
import org.quartz.xml.XMLSchedulingDataProcessor;
import org.sakaiproject.api.app.scheduler.SchedulerManager;
import org.xml.sax.SAXException;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathException;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.util.List;

/**
* This allows quartz jobs to be automatically provisioned at startup. It has the advantage over the
* builtin Spring Quartz support of optionally not overwriting any changes that are made. This is in a JAR as
* there's no way to load resources from a component apart from having a class in there.
* Although SchedulerManagerImpl can automatically provision jobs these have to be in the job scheduler and can't
* be in other projects. So in the future this method is preferred.
*
* @see XMLSchedulingDataProcessor
*/
@Slf4j
public class AutoProvisionJobs {

@Setter
private SchedulerManager schedulerManager;

@Setter
private List<String> files;

public void init() throws ParserConfigurationException, XPathException, ParseException, IOException, ValidationException, SchedulerException, SAXException, ClassNotFoundException {

boolean noFiles = files == null || files.isEmpty();
if (noFiles || !schedulerManager.isAutoProvisioning()) {
log.info("Not auto provisioning jobs: "+ ((noFiles)?"no files.":String.join(", ", files)));
return;
}

Scheduler scheduler = schedulerManager.getScheduler();
ClassLoadHelper clh = new CascadingClassLoadHelper();
clh.initialize();

for (String file : files ) {
XMLSchedulingDataProcessor proc = new XMLSchedulingDataProcessor(clh);
InputStream in = getClass().getResourceAsStream(file);
if (in == null) {
throw new IllegalArgumentException("Couldn't find resource on classpath: "+ file);
}
try {
proc.processStreamAndScheduleJobs(in, file, scheduler);
log.info("Successfully provisioned jobs/triggers from :"+ file);
} catch (ObjectAlreadyExistsException e) {
log.info("Not fully processing: "+ file+ " because some parts already exist");
}
}
}

}
Loading

0 comments on commit ad75902

Please sign in to comment.