Skip to content

Commit

Permalink
SAK-31584 Allow auto wiring of Quartz Jobs. (sakaiproject#3105)
Browse files Browse the repository at this point in the history
This means you can just register a class with the job scheduler and then quartz will create an instance when it needs to and have spring autowire any dependencies.
  • Loading branch information
buckett authored and ottenhoff committed Jul 31, 2016
1 parent ad83c1d commit 65af0ef
Show file tree
Hide file tree
Showing 17 changed files with 334 additions and 54 deletions.
5 changes: 5 additions & 0 deletions jobscheduler/scheduler-api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
</dependency>
<dependency>
<groupId>org.sakaiproject.kernel</groupId>
<artifactId>sakai-kernel-private</artifactId>
<version>12-SNAPSHOT</version>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,31 @@
**********************************************************************************/
package org.sakaiproject.api.app.scheduler;

import org.quartz.Job;

/**
* Created by IntelliJ IDEA.
* User: John Ellis
* Date: Dec 1, 2005
* Time: 12:35:51 PM
* To change this template use File | Settings | File Templates.
* This is used to represent a job that can be run through the Sakai Scheduler tool.
*/
public interface JobBeanWrapper {

public static final String SPRING_BEAN_NAME = "org.sakaiproject.api.app.scheduler.JobBeanWrapper.bean";
public static final String JOB_TYPE = "org.sakaiproject.api.app.scheduler.JobBeanWrapper.jobType";
String SPRING_BEAN_NAME = "org.sakaiproject.api.app.scheduler.JobBeanWrapper.bean";
String JOB_TYPE = "org.sakaiproject.api.app.scheduler.JobBeanWrapper.jobType";

public String getBeanId();
/**
* @return The Spring Bean ID to retrieve from the application context.
*/
String getBeanId();

public Class getJobClass();
/**
* This is the class that will get registered with Quartz to be run.
* @return A Class that implements the Job interface.
*/
Class<? extends Job> getJobClass();

public String getJobType();
/**
* This is the name that is displayed in the interface for the job.
* @return A summary of the job.
*/
String getJobType();

}
17 changes: 17 additions & 0 deletions jobscheduler/scheduler-component-shared/README.beanjobs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Autowiring Quartz Jobs
======================

To implement a quartz job now all you need todo is create a class that
implements the org.quartz.Job interface and register this class with the
scheuduler manager. The class doesn't need to be a spring bean as it
can be autowired with any services it needs. The data from the job map
is also available to the autowiring.

See: org.springframework.scheduling.quartz.SpringBeanJobFactory
See: org.sakaiproject.component.app.scheduler.jobs.AutowiredTestJob

@Inject doesn't work unless the annotation is available when the main
spring application context is setup. However @Autowired is always available
so can be used.


13 changes: 8 additions & 5 deletions jobscheduler/scheduler-component-shared/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
Expand Down Expand Up @@ -88,11 +96,6 @@
<artifactId>javassist</artifactId>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.sakaiproject.component.app.scheduler;

import org.quartz.Job;
import org.sakaiproject.api.app.scheduler.JobBeanWrapper;

/**
* This is a JobBeanWrapper that will just be registered with the Job Scheduler.
* This class isn't actually used when running the job as quartz will directly create an instance of the class.
*/
public class AutowiredJobBeanWrapper implements JobBeanWrapper {

private String jobType;
private Class<? extends Job> aClass;

public AutowiredJobBeanWrapper(Class<? extends Job> aClass, String jobType) {
this.aClass = aClass;
this.jobType = jobType;
}

@Override
public String getBeanId() {
// We don't need a bean ID as a new instance will be created and will get autowired.
return null;
}

@Override
public Class<? extends Job> getJobClass() {
return aClass;
}

@Override
public String getJobType() {
return jobType;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.sakaiproject.component.app.scheduler;

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;

/**
* This JobFactory autowires automatically the created quartz bean with spring @Autowired dependencies.
*
* @author jelies (thanks to Brian Matthews)
* @link http://webcache.googleusercontent.com/search?q=cache:FH-N1i--sDgJ:blog.btmatthews.com/2011/09/24/inject-application-context-dependencies-in-quartz-job-beans/+&cd=7&hl=en&ct=clnk&gl=es)
*/
public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements
ApplicationContextAware {

private transient AutowireCapableBeanFactory beanFactory;

@Override
public void setApplicationContext(final ApplicationContext context) {
beanFactory = context.getAutowireCapableBeanFactory();
}

@Override
protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
final Object job = super.createJobInstance(bundle);
beanFactory.autowireBean(job);
return job;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,41 +31,29 @@
import org.quartz.utils.ConnectionProvider;
import org.sakaiproject.component.cover.ComponentManager;

/**
* This looks up the DataSource from Spring so that it will use the main Sakai connection.
*/
public class ConnectionProviderDelegate implements ConnectionProvider
{
private final Logger LOG = LoggerFactory.getLogger(ConnectionProviderDelegate.class);
private DataSource ds;

private static final Logger LOG = LoggerFactory.getLogger(ConnectionProviderDelegate.class);
private static DataSource ds;


/**
* @see org.quartz.utils.ConnectionProvider#getConnection()
*/
@Override
public Connection getConnection() throws SQLException {

if (LOG.isDebugEnabled()){
LOG.debug("quartz getConnection()");
}

if (ds == null){
ds = (DataSource) ComponentManager.get("javax.sql.DataSource");
}
return ds.getConnection();
}

/**
* @see org.quartz.utils.ConnectionProvider#shutdown()
*/
@Override
public void shutdown() throws SQLException {
}

/** (non-Javadoc)
* @see org.quartz.utils.ConnectionProvider#initialize()
*/
@Override
public void initialize() throws SQLException {
ds = ComponentManager.get(javax.sql.DataSource.class);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.sakaiproject.component.app.scheduler;

import org.sakaiproject.api.app.scheduler.JobBeanWrapper;
import org.sakaiproject.api.app.scheduler.SchedulerManager;

import java.util.List;

/**
* Just registers JobBeanWrappers with the Schedule Manager, this is needed because the autowired
* jobs don't have to register themselves.
*/
public class JobBeanWrapperRegistrar {

private SchedulerManager schedulerManager;
private List<JobBeanWrapper> jobBeans;

public void setSchedulerManager(SchedulerManager schedulerManager) {
this.schedulerManager = schedulerManager;
}

public void setJobBeans(List<JobBeanWrapper> jobBeans) {
this.jobBeans = jobBeans;
}

public void init() {
jobBeans.stream().forEach(wrapper -> schedulerManager.registerBeanJob(wrapper.getJobType(), wrapper));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,11 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.*;

import javax.sql.DataSource;

import org.quartz.spi.JobFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.quartz.CronScheduleBuilder;
Expand Down Expand Up @@ -68,7 +61,7 @@
import org.sakaiproject.component.app.scheduler.jobs.SpringJobBeanWrapper;
import org.sakaiproject.db.api.SqlService;

public class SchedulerManagerImpl implements SchedulerManager
public class SchedulerManagerImpl implements SchedulerManager, SchedulerFactory
{

private static final Logger LOG = LoggerFactory.getLogger(SchedulerManagerImpl.class);
Expand All @@ -82,7 +75,6 @@ public class SchedulerManagerImpl implements SchedulerManager
private String qrtzPropFile;
/** The properties file from sakai.home */
private String qrtzPropFileSakai;
private Properties qrtzProperties;
private TriggerListener globalTriggerListener;
private Boolean autoDdl;
private boolean startScheduler = true;
Expand All @@ -94,10 +86,10 @@ public class SchedulerManagerImpl implements SchedulerManager

// Service dependencies
private ServerConfigurationService serverConfigurationService;
private SchedulerFactory schedFactory;
private Scheduler scheduler;
private SqlService sqlService;

private Scheduler scheduler;
private JobFactory jobFactory;

private LinkedList<TriggerListener>
globalTriggerListeners = new LinkedList<TriggerListener>();
Expand All @@ -111,7 +103,7 @@ public void init()
{
try
{
qrtzProperties = initQuartzConfiguration();
Properties qrtzProperties = initQuartzConfiguration();

qrtzProperties.setProperty("org.quartz.scheduler.instanceId", serverId);

Expand Down Expand Up @@ -193,8 +185,9 @@ public void init()


// start scheduler and load jobs
schedFactory = new StdSchedulerFactory(qrtzProperties);
SchedulerFactory schedFactory = new StdSchedulerFactory(qrtzProperties);
scheduler = schedFactory.getScheduler();
scheduler.setJobFactory(jobFactory);

// loop through persisted jobs removing both the job and associated
// triggers for jobs where the associated job class is not found
Expand All @@ -204,11 +197,14 @@ public void init()
{
JobDetail detail = scheduler.getJobDetail(key);
String bean = detail.getJobDataMap().getString(JobBeanWrapper.SPRING_BEAN_NAME);
Job job = (Job) ComponentManager.get(bean);
if (job == null) {
// We now have jobs that don't explicitly reference a spring bean
if (bean != null && !bean.isEmpty()) {
Job job = (Job) ComponentManager.get(bean);
if (job == null) {
LOG.warn("scheduler cannot load class for persistent job:" + key);
scheduler.deleteJob(key);
scheduler.deleteJob(key);
LOG.warn("deleted persistent job:" + key);
}
}
}
catch (SchedulerException e)
Expand Down Expand Up @@ -694,6 +690,22 @@ public void setScheduler(Scheduler scheduler)
this.scheduler = scheduler;
}

@Override
public Scheduler getScheduler(String schedName) throws SchedulerException
{
if (scheduler.getSchedulerName().equals(schedName))
{
return getScheduler();
}
return null;
}

@Override
public Collection<Scheduler> getAllSchedulers() throws SchedulerException
{
return Collections.singleton(getScheduler());
}

/**
* @param serverConfigurationService The ServerConfigurationService to get our configuation from.
*/
Expand All @@ -719,10 +731,14 @@ public void registerBeanJob(String jobName, JobBeanWrapper job) {
}

public JobBeanWrapper getJobBeanWrapper(String beanWrapperId) {
return (JobBeanWrapper) getBeanJobs().get(beanWrapperId);
return getBeanJobs().get(beanWrapperId);
}

public boolean isStartScheduler() {
public void setJobFactory(JobFactory jobFactory) {
this.jobFactory = jobFactory;
}

public boolean isStartScheduler() {
return startScheduler;
}

Expand Down
Loading

0 comments on commit 65af0ef

Please sign in to comment.