├── .gitignore ├── .travis.yml ├── src ├── test │ └── java │ │ └── com │ │ └── fiestacabin │ │ └── dropwizard │ │ └── quartz │ │ ├── test │ │ └── SampleJob.java │ │ ├── GuiceJobFactoryTest.java │ │ └── ManagedSchedulerTest.java └── main │ └── java │ └── com │ └── fiestacabin │ └── dropwizard │ └── quartz │ ├── Scheduled.java │ ├── SchedulerConfiguration.java │ ├── GuiceJobFactory.java │ └── ManagedScheduler.java ├── README.md └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .classpath 3 | .project 4 | .settings/ 5 | .idea 6 | *.iml -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk7 4 | - openjdk7 5 | - oraclejdk8 6 | install: mvn install -DskipTests=false -Dgpg.skip=true 7 | 8 | -------------------------------------------------------------------------------- /src/test/java/com/fiestacabin/dropwizard/quartz/test/SampleJob.java: -------------------------------------------------------------------------------- 1 | package com.fiestacabin.dropwizard.quartz.test; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | import org.quartz.Job; 6 | import org.quartz.JobExecutionContext; 7 | import org.quartz.JobExecutionException; 8 | 9 | import com.fiestacabin.dropwizard.quartz.Scheduled; 10 | 11 | @Scheduled(interval=5, unit=TimeUnit.MINUTES) 12 | public class SampleJob implements Job { 13 | 14 | public void execute(JobExecutionContext context) 15 | throws JobExecutionException { 16 | 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/fiestacabin/dropwizard/quartz/Scheduled.java: -------------------------------------------------------------------------------- 1 | package com.fiestacabin.dropwizard.quartz; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | 10 | @Target(ElementType.TYPE) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | public @interface Scheduled { 13 | String cron() default ""; 14 | int interval() default -1; 15 | int delayInMillis() default 0; 16 | TimeUnit unit() default TimeUnit.SECONDS; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/fiestacabin/dropwizard/quartz/SchedulerConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.fiestacabin.dropwizard.quartz; 2 | 3 | import java.util.TimeZone; 4 | 5 | public class SchedulerConfiguration { 6 | 7 | private String basePackage; 8 | private TimeZone timezone; 9 | 10 | public SchedulerConfiguration() {} 11 | 12 | public SchedulerConfiguration(String basePackage) { 13 | this.basePackage = basePackage; 14 | this.timezone = TimeZone.getDefault(); 15 | } 16 | 17 | public String getBasePackage() { 18 | return basePackage; 19 | } 20 | 21 | public TimeZone getTimezone() { 22 | return timezone; 23 | } 24 | 25 | public void setBasePackage(String basePackage) { 26 | this.basePackage = basePackage; 27 | } 28 | 29 | public void setTimezone(String timezoneId) { 30 | this.timezone = TimeZone.getTimeZone(timezoneId); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/fiestacabin/dropwizard/quartz/GuiceJobFactory.java: -------------------------------------------------------------------------------- 1 | package com.fiestacabin.dropwizard.quartz; 2 | 3 | import javax.inject.Inject; 4 | 5 | import org.quartz.Job; 6 | import org.quartz.JobDetail; 7 | import org.quartz.Scheduler; 8 | import org.quartz.SchedulerException; 9 | import org.quartz.spi.JobFactory; 10 | import org.quartz.spi.TriggerFiredBundle; 11 | 12 | import com.google.inject.Injector; 13 | 14 | public class GuiceJobFactory implements JobFactory { 15 | 16 | private Injector injector; 17 | 18 | @Inject 19 | public GuiceJobFactory(Injector injector) { 20 | this.injector = injector; 21 | } 22 | 23 | public Job newJob(TriggerFiredBundle bundle, Scheduler scheduler) 24 | throws SchedulerException { 25 | 26 | JobDetail jobDetail = bundle.getJobDetail(); 27 | Class jobClass = jobDetail.getJobClass(); 28 | return injector.getInstance(jobClass); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/com/fiestacabin/dropwizard/quartz/GuiceJobFactoryTest.java: -------------------------------------------------------------------------------- 1 | package com.fiestacabin.dropwizard.quartz; 2 | 3 | import static org.junit.Assert.assertSame; 4 | import static org.mockito.Mockito.mock; 5 | import static org.mockito.Mockito.when; 6 | 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.mockito.InjectMocks; 10 | import org.mockito.Mock; 11 | import org.mockito.MockitoAnnotations; 12 | import org.quartz.Job; 13 | import org.quartz.Scheduler; 14 | import org.quartz.impl.JobDetailImpl; 15 | import org.quartz.spi.TriggerFiredBundle; 16 | 17 | import com.fiestacabin.dropwizard.quartz.test.SampleJob; 18 | import com.google.inject.Injector; 19 | 20 | 21 | public class GuiceJobFactoryTest { 22 | 23 | @Mock Injector injector; 24 | @Mock TriggerFiredBundle bundle; 25 | 26 | @InjectMocks GuiceJobFactory jobFactory; 27 | 28 | @Before 29 | public void init() { 30 | MockitoAnnotations.initMocks(this); 31 | } 32 | 33 | @Test 34 | public void testJobCreation() throws Exception { 35 | JobDetailImpl jd = new JobDetailImpl(); 36 | jd.setJobClass(SampleJob.class); 37 | 38 | when(bundle.getJobDetail()).thenReturn(jd); 39 | 40 | SampleJob j = new SampleJob(); 41 | when(injector.getInstance(SampleJob.class)).thenReturn(j); 42 | 43 | Job newJob = jobFactory.newJob(bundle, mock(Scheduler.class)); 44 | assertSame(j, newJob); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/fiestacabin/dropwizard/quartz/ManagedScheduler.java: -------------------------------------------------------------------------------- 1 | package com.fiestacabin.dropwizard.quartz; 2 | 3 | import static org.quartz.JobBuilder.newJob; 4 | import static org.quartz.SimpleScheduleBuilder.simpleSchedule; 5 | import static org.quartz.TriggerBuilder.newTrigger; 6 | import io.dropwizard.lifecycle.Managed; 7 | 8 | import java.util.Date; 9 | import java.util.Set; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | import javax.inject.Inject; 13 | 14 | import org.quartz.CronScheduleBuilder; 15 | import org.quartz.Job; 16 | import org.quartz.JobDetail; 17 | import org.quartz.Scheduler; 18 | import org.quartz.Trigger; 19 | import org.quartz.TriggerBuilder; 20 | import org.reflections.Reflections; 21 | import org.reflections.scanners.SubTypesScanner; 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | 25 | public class ManagedScheduler implements Managed { 26 | private static final Logger LOG = LoggerFactory.getLogger(ManagedScheduler.class); 27 | 28 | private Scheduler scheduler; 29 | private GuiceJobFactory jobFactory; 30 | private SchedulerConfiguration config; 31 | 32 | @Inject 33 | public ManagedScheduler(Scheduler scheduler, GuiceJobFactory jobFactory, 34 | SchedulerConfiguration config) { 35 | this.scheduler = scheduler; 36 | this.jobFactory = jobFactory; 37 | this.config = config; 38 | } 39 | 40 | public void start() throws Exception { 41 | scheduler.setJobFactory(jobFactory); 42 | scheduler.start(); 43 | 44 | Reflections reflections = new Reflections(config.getBasePackage(), new SubTypesScanner()); 45 | Set> scheduledClasses = reflections.getSubTypesOf(Job.class); 46 | 47 | for (Class scheduledClass : scheduledClasses) { 48 | Scheduled scheduleAnn = scheduledClass 49 | .getAnnotation(Scheduled.class); 50 | if (scheduleAnn != null) { 51 | JobDetail job = newJob(scheduledClass).build(); 52 | Trigger trigger = buildTrigger(scheduleAnn); 53 | 54 | LOG.info("Scheduled job {} with trigger {}", job, trigger); 55 | scheduler.scheduleJob(job, trigger); 56 | } 57 | } 58 | } 59 | 60 | public Trigger buildTrigger(Scheduled ann) { 61 | TriggerBuilder trigger = newTrigger(); 62 | 63 | if (ann.cron() != null && ann.cron().trim().length() > 0) { 64 | trigger.withSchedule(CronScheduleBuilder.cronSchedule(ann.cron()).inTimeZone(config.getTimezone())); 65 | } else if (ann.interval() != -1) { 66 | trigger.withSchedule(simpleSchedule() 67 | .withIntervalInMilliseconds( 68 | TimeUnit.MILLISECONDS.convert(ann.interval(), ann.unit())) 69 | .repeatForever()) 70 | .startAt(new Date(System.currentTimeMillis() + ann.delayInMillis())); 71 | } else { 72 | throw new IllegalArgumentException("One of 'cron', 'interval' is required for the @Scheduled annotation"); 73 | } 74 | 75 | return trigger.build(); 76 | } 77 | 78 | public void stop() throws Exception { 79 | scheduler.shutdown(); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | dropwizard-quartz [![Build Status](https://travis-ci.org/jaredstehler/dropwizard-quartz.svg?branch=master)](https://travis-ci.org/jaredstehler/dropwizard-quartz) 2 | ================= 3 | 4 | This is a simple Job Scheduler implementation for dropwizard, integrating Guice and Quartz. The nice thing about it is that it allows you to use @Inject to wire up dependencies in your Job instances, and define your scheduling via a @Scheduled annotation. 5 | 6 | Usage 7 | ----- 8 | 9 | ### Maven Central Dependency ### 10 | 11 | 12 | com.fiestacabin.dropwizard.quartz 13 | dropwizard-quartz 14 | 0.7.1 15 | 16 | 17 | In order to use this framework, you need to add an instance of ManagedScheduler to your Dropwizard environment (or use the dropwizard-guice AutoConfigApplication). This will search the classpath for Job classes marked with the @Scheduled annotation and register them with the Quartz scheduler. 18 | 19 | ### Example Integration with dropwizard-guice ### 20 | 21 | The main service class, which constructs as an auto config service including the local app's base package as well as that of the managed scheduler class, so it will automatically register with the dropwizard environment: 22 | 23 | ```java 24 | public class DwQuartzService extends AutoConfigApplication { 25 | 26 | public DwQuartzService() { 27 | super("dw-quartz", DwQuartzService.class.getPackage().getName(), 28 | ManagedScheduler.class.getPackage().getName()); 29 | } 30 | 31 | @Override 32 | protected Injector createInjector(DwQuartzConfiguration configuration) { 33 | return super.createInjector(configuration).createChildInjector(new DwQuartzModule()); 34 | } 35 | 36 | public static void main(String[] args) throws Exception { 37 | new DwQuartzService().run(args); 38 | } 39 | 40 | } 41 | ``` 42 | 43 | Next, the app's guice module defines a provider for the quartz scheduler, and binds an instance of the scheduler config: 44 | 45 | ```java 46 | public class DwQuartzModule extends AbstractModule { 47 | 48 | @Override 49 | protected void configure() { 50 | bind(SchedulerConfiguration.class).toInstance(new SchedulerConfiguration("sandbox")); 51 | } 52 | 53 | @Provides 54 | @Singleton 55 | Scheduler provideScheduler() throws SchedulerException { 56 | return StdSchedulerFactory.getDefaultScheduler(); 57 | } 58 | 59 | } 60 | ``` 61 | 62 | The @Scheduled annotation has two incantations, one using a cron string, and the other specifying a recurring interval. 63 | 64 | Here's an example using the cron syntax, setting up a job to run at midnight every weekday: 65 | 66 | @Scheduled(cron="0 0 0 ? * MON-FRI") 67 | public class MyJob implements org.quartz.Job { 68 | @Inject 69 | public MyJob(MyDep dep){ 70 | this.dep = dep; 71 | } 72 | 73 | void execute(JobExecutionContext context) throws JobExecutionException { /* ... */ } 74 | } 75 | 76 | And here's an example using the interval syntax (setting up a job which runs every 5 minutes): 77 | 78 | @Scheduled(interval=5, unit=TimeUnit.MINUTES) 79 | public class OtherJob implements org.quartz.Job { 80 | @Inject 81 | public MyJob(MyDep dep){ 82 | this.dep = dep; 83 | } 84 | 85 | void execute(JobExecutionContext context) throws JobExecutionException { /* ... */ } 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/com/fiestacabin/dropwizard/quartz/ManagedSchedulerTest.java: -------------------------------------------------------------------------------- 1 | package com.fiestacabin.dropwizard.quartz; 2 | 3 | import static org.hamcrest.MatcherAssert.assertThat; 4 | import static org.hamcrest.Matchers.hasItem; 5 | import static org.junit.Assert.assertEquals; 6 | import static org.mockito.Matchers.any; 7 | import static org.mockito.Mockito.atLeast; 8 | import static org.mockito.Mockito.verify; 9 | 10 | import java.util.Date; 11 | import java.util.Set; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | import org.joda.time.DateTimeZone; 15 | import org.joda.time.Interval; 16 | import org.junit.Before; 17 | import org.junit.Test; 18 | import org.junit.runner.RunWith; 19 | import org.mockito.ArgumentCaptor; 20 | import org.mockito.Mock; 21 | import org.mockito.runners.MockitoJUnitRunner; 22 | import org.quartz.Job; 23 | import org.quartz.JobDetail; 24 | import org.quartz.Scheduler; 25 | import org.quartz.Trigger; 26 | 27 | import com.fiestacabin.dropwizard.quartz.test.SampleJob; 28 | import com.google.common.collect.Sets; 29 | 30 | @RunWith(MockitoJUnitRunner.class) 31 | public class ManagedSchedulerTest { 32 | 33 | @Mock private Scheduler scheduler; 34 | @Mock private GuiceJobFactory jobFactory; 35 | private SchedulerConfiguration configuration; 36 | 37 | ManagedScheduler managedScheduler; 38 | 39 | @Before 40 | public void initMocks() { 41 | configuration = new SchedulerConfiguration("com.fiestacabin.dropwizard.quartz.test"); 42 | configuration.setTimezone("UTC"); 43 | 44 | managedScheduler = new ManagedScheduler(scheduler, jobFactory, configuration); 45 | } 46 | 47 | @Test 48 | public void itParsesRepeatingCronExpressions() throws Exception { 49 | Interval i = getFireInterval(FiveMinuteInterval.class); 50 | assertEquals(5, i.toDuration().getStandardMinutes()); 51 | } 52 | 53 | @Test 54 | public void itParsesDailyCronExpressions() throws Exception { 55 | Interval i = getFireInterval(DailyMorningInterval.class); 56 | assertEquals(9, i.getStart().toDateTime(DateTimeZone.UTC).getHourOfDay()); 57 | assertEquals(1, i.toDuration().getStandardDays()); 58 | } 59 | 60 | @Test 61 | public void itUsesConfiguredTimeZone() throws Exception { 62 | configuration.setTimezone("America/New_York"); 63 | 64 | Interval i = getFireInterval(DailyMorningInterval.class); 65 | assertEquals(13, i.getStart().toDateTime(DateTimeZone.UTC).getHourOfDay()); 66 | assertEquals(1, i.toDuration().getStandardDays()); 67 | } 68 | 69 | @Test 70 | public void itParsesSpecificIntervals() throws Exception { 71 | assertEquals(5, getFireInterval(FiveSecondInterval.class).toDuration().getStandardSeconds()); 72 | } 73 | 74 | @Test(expected=IllegalArgumentException.class) 75 | public void itExpectsEitherCronOrInterval() throws Exception { 76 | getFireInterval(Invalid.class); 77 | } 78 | 79 | @Test 80 | public void itDiscoversJobClasses() throws Exception { 81 | managedScheduler.start(); 82 | 83 | verify(scheduler).setJobFactory(jobFactory); 84 | verify(scheduler).start(); 85 | 86 | ArgumentCaptor jobDetail = ArgumentCaptor.forClass(JobDetail.class); 87 | verify(scheduler, atLeast(1)).scheduleJob(jobDetail.capture(), any(Trigger.class)); 88 | 89 | Set> jobClasses = Sets.newHashSet(); 90 | for(JobDetail jd : jobDetail.getAllValues()) { 91 | jobClasses.add(jd.getJobClass()); 92 | } 93 | 94 | assertThat(jobClasses, hasItem(SampleJob.class)); 95 | } 96 | 97 | @Test 98 | public void itShutsDownQuartz() throws Exception { 99 | managedScheduler.stop(); 100 | verify(scheduler).shutdown(); 101 | } 102 | 103 | private Interval getFireInterval(Class c){ 104 | Scheduled s = c.getAnnotation(Scheduled.class); 105 | Trigger t = managedScheduler.buildTrigger(s); 106 | 107 | Date start = t.getStartTime(); 108 | Date next = t.getFireTimeAfter(start); 109 | 110 | start = next; 111 | next = t.getFireTimeAfter(start); 112 | 113 | Interval i = new Interval(start.getTime(), next.getTime()); 114 | return i; 115 | } 116 | 117 | @Scheduled(cron="0 */5 * * * ?") 118 | public static class FiveMinuteInterval {} 119 | 120 | @Scheduled(cron="0 0 9 ? * *") 121 | public static class DailyMorningInterval {} 122 | 123 | @Scheduled(interval=5, unit=TimeUnit.SECONDS) 124 | public static class FiveSecondInterval {} 125 | 126 | @Scheduled 127 | public static class Invalid {} 128 | 129 | } 130 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | com.fiestacabin.dropwizard.quartz 4 | dropwizard-quartz 5 | 0.7.3-SNAPSHOT 6 | jar 7 | 8 | 9 | org.sonatype.oss 10 | oss-parent 11 | 7 12 | 13 | 14 | Dropwizard-Quartz 15 | 16 | This is a simple Job Scheduler implementation for dropwizard, integrating Guice and Quartz. 17 | 18 | https://github.com/jaredstehler/dropwizard-quartz 19 | 20 | 21 | 22 | The Apache Software License, Version 2.0 23 | http://www.apache.org/licenses/LICENSE-2.0.txt 24 | repo 25 | 26 | 27 | 28 | 29 | scm:git:git@github.com:jaredstehler/dropwizard-quartz.git 30 | scm:git:git@github.com:jaredstehler/dropwizard-quartz.git 31 | git@github.com:jaredstehler/dropwizard-quartz.git 32 | 33 | 34 | 35 | 36 | jaredstehler 37 | Jared Stehler 38 | jared.stehler@gmail.com 39 | 40 | 41 | 42 | 43 | UTF-8 44 | 45 | 46 | 47 | 48 | 49 | org.apache.maven.plugins 50 | maven-compiler-plugin 51 | 2.5 52 | 53 | 54 | 1.6 55 | 1.6 56 | 57 | 58 | 59 | 60 | org.apache.maven.plugins 61 | maven-gpg-plugin 62 | 1.4 63 | 64 | 65 | sign-artifacts 66 | verify 67 | 68 | sign 69 | 70 | 71 | 72 | 73 | 74 | 75 | org.apache.maven.plugins 76 | maven-source-plugin 77 | 2.2.1 78 | 79 | 80 | attach-sources 81 | 82 | jar-no-fork 83 | 84 | 85 | 86 | 87 | 88 | org.apache.maven.plugins 89 | maven-javadoc-plugin 90 | 2.9 91 | 92 | 93 | generate-javadoc-jar 94 | 95 | jar 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | io.dropwizard 106 | dropwizard-core 107 | 0.8.0 108 | 109 | 110 | com.google.inject 111 | guice 112 | 3.0 113 | 114 | 115 | org.reflections 116 | reflections 117 | 0.9.9 118 | 119 | 120 | com.google.guava 121 | guava 122 | 123 | 124 | 125 | 126 | org.quartz-scheduler 127 | quartz 128 | 2.1.5 129 | 130 | 131 | slf4j-api 132 | org.slf4j 133 | 134 | 135 | 136 | 137 | 138 | junit 139 | junit-dep 140 | 4.10 141 | test 142 | 143 | 144 | org.hamcrest 145 | hamcrest-core 146 | 1.2.1 147 | test 148 | 149 | 150 | org.hamcrest 151 | hamcrest-integration 152 | 1.2.1 153 | test 154 | 155 | 156 | org.mockito 157 | mockito-all 158 | 1.9.0 159 | test 160 | 161 | 162 | joda-time 163 | joda-time 164 | 2.1 165 | test 166 | 167 | 168 | 169 | --------------------------------------------------------------------------------