Guice-Quartz integration

GuiceQuartzのintegrationを書いてみました。 CronTriggerのみの実装です。
Jobをimplementsしたinterfaceにannotationを書だけで定期的にメソッドが呼ばれるようになります。
見たほうが早いのでとりあえずソース。

呼び出すinterface。

package net.nagaseyasuhito.sandbox.worker;

import net.nagaseyasuhito.sandbox.guice.Scheduled;
import net.nagaseyasuhito.sandbox.worker.impl.SandboxWorkerImpl;

import org.quartz.Job;

import com.google.inject.ImplementedBy;

@ImplementedBy(SandboxWorkerImpl.class)
@Scheduled(expression = "0 * * * * ?")
public interface SandboxWorker extends Job {
}

実装クラス。

package net.nagaseyasuhito.sandbox.worker.impl;

import net.nagaseyasuhito.sandbox.worker.SandboxWorker;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import com.wideplay.warp.persist.Transactional;

public class SandboxWorkerImpl implements SandboxWorker {
	@Override
	@Transactional
	public void execute(JobExecutionContext context) throws JobExecutionException {
		// do something
	}
}

このinterfaceをguiceに登録し、QuartzService.start()を呼びます。

Module quartzModule = 
	QuartzService.
	usingAnnotation().
	add(SandboxWorker.class).
	buildModule();
Injector injector = Guice.createInjector(quartzModule);
injector.getInstance(QuartzService.class).start();

Quartzの設定がすっきりします。お試しあれ。

GuiceのModule

package net.nagaseyasuhito.sandbox.guice;

import java.text.ParseException;
import java.util.HashSet;
import java.util.Set;

import org.quartz.CronTrigger;
import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerListener;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.spi.JobFactory;
import org.quartz.spi.TriggerFiredBundle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Module;

public class QuartzService {
	private static final Logger log = LoggerFactory.getLogger(QuartzService.class);

	public static QuartzService usingAnnotation() {
		return new QuartzService();
	}

	@Inject
	private Injector injector;

	private Scheduler scheduler;

	private QuartzService() {
		try {
			this.scheduler = new StdSchedulerFactory().getScheduler();
			this.scheduler.addGlobalTriggerListener(new TriggerListener() {
				private Set<String> runningJobs = new HashSet<String>();

				@Override
				public String getName() {
					return this.getClass().getName();
				}

				@Override
				public void triggerComplete(Trigger trigger, JobExecutionContext context, int instructionCode) {
					synchronized (this.runningJobs) {
						this.runningJobs.remove(context.getJobDetail().getFullName());
					}
				}

				@Override
				public void triggerFired(Trigger trigger, JobExecutionContext context) {
				}

				@Override
				public void triggerMisfired(Trigger trigger) {
				}

				@Override
				public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
					synchronized (this.runningJobs) {
						if (this.runningJobs.contains(context.getJobDetail().getFullName())) {
							return true;
						} else {
							this.runningJobs.add(context.getJobDetail().getFullName());
							return false;
						}
					}
				}
			});
			this.scheduler.setJobFactory(new JobFactory() {

				@SuppressWarnings("unchecked")
				public Job newJob(TriggerFiredBundle bundle) throws SchedulerException {
					JobDetail jobDetail = bundle.getJobDetail();
					Class<? extends Job> jobClass = jobDetail.getJobClass();
					try {
						return QuartzService.this.injector.getInstance(jobClass);
					} catch (Exception e) {
						throw new SchedulerException("Problem instantiating class '" + jobDetail.getJobClass().getName() + "'", e);
					}
				}
			});
		} catch (SchedulerException e) {
			QuartzService.log.error("QuartzService initialize failed", e);
		}
	}

	public QuartzService add(Class<? extends Job> clazz) {
		Scheduled scheduled = clazz.getAnnotation(Scheduled.class);
		if (scheduled != null) {
			try {
				Trigger trigger = new CronTrigger(clazz.getCanonicalName(), scheduled.group(), scheduled.expression());
				JobDetail job = new JobDetail(clazz.getCanonicalName(), scheduled.group(), clazz);
				this.scheduler.scheduleJob(job, trigger);
			} catch (ParseException e) {
				QuartzService.log.error("CronTrigger expression parse failed(" + scheduled.expression() + "). skipped", e);
			} catch (SchedulerException e) {
				QuartzService.log.error("job scheduling failed. skipped", e);
			}
		}

		return this;
	}

	public Module buildModule() {
		return new AbstractModule() {
			@Override
			protected void configure() {
				this.bind(QuartzService.class).toInstance(QuartzService.this);
			}
		};
	}

	public void shutdown() {
		if (this.scheduler != null) {
			try {
				this.scheduler.shutdown(true);
			} catch (SchedulerException e) {
				QuartzService.log.error("QuartzService shutdown failed", e);
			}
		}
	}

	public void start() {
		try {
			this.scheduler.start();
		} catch (SchedulerException e) {
			QuartzService.log.error("QuartzService start failed", e);
		}
	}
}

呼び出すinterfaceに書くannotation。

package net.nagaseyasuhito.sandbox.guice;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Scheduled {
	String expression();

	String group() default "default";
}