Java tutorial
/* * Copyright 2014 Ken Blair * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.kenblair.scheduler.service; import net.kenblair.scheduler.entity.Schedulable; import net.kenblair.scheduler.entity.ScheduledJob; import net.kenblair.scheduler.repository.ScheduledJobRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.Lifecycle; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.support.CronTrigger; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ScheduledFuture; /** * A Spring based scheduler utilizing an injected {@link TaskScheduler} for executing {@link Schedulable} beans. * <p/> * Jobs are retrieved from an injected {@link ScheduledJobRepository} and scheduled according to their trigger. A job * that is already scheduled but returns {@code false} from {@link ScheduledJob#equalTo(ScheduledJob)} will be * canceled and rescheduled. * * @author kblair */ @Service public class Scheduler implements Runnable, ApplicationContextAware, Lifecycle { private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final ScheduledJobRepository repository; private final TaskScheduler taskScheduler; private final Map<String, Job> jobs; private ScheduledFuture schedulerTask; private ApplicationContext context; /** * Construct a new scheduler with the injected dependencies. * * @param repository The repository. * @param taskScheduler The task scheduler. */ @Autowired public Scheduler(final ScheduledJobRepository repository, final TaskScheduler taskScheduler) { this.repository = repository; this.taskScheduler = taskScheduler; this.jobs = new HashMap<>(); } /** * Executes the scheduler. * <p/> * Should be run on a regular basis such as every 30 seconds. */ public void run() { final Set<String> jobsToCancel = new HashSet<>(jobs.keySet()); for (final ScheduledJob job : repository.findEnabledJobs()) { try { jobsToCancel.remove(job.getId()); final String id = job.getId(); if (jobs.containsKey(id)) { if (!jobs.get(id).sameAs(job)) { jobs.remove(id).cancel(); } else { continue; } } final Job theJob = new Job(getBean(job), job); jobs.put(id, theJob); theJob.schedule(); } catch (final Throwable t) { logger.error("Exception while processing scheduled jobs", t); } } for (final String id : jobsToCancel) { jobs.remove(id).cancel(); } } /** * A convenience method that will run the scheduled job exactly once. * <p/> * Note that this will build and configure a new bean. If the job is already running it will execute separately * using the newly configured bean. This is primarily useful for running disabled jobs or jobs that run on a sparse * schedule such as for testing. * * @param job The job to execute. */ public void executeOnce(final ScheduledJob job) { final Schedulable bean = getBean(job); bean.run(); bean.cancel(); } /** * Build and configure a bean for the job. * * @param job The job. * @return The bean. */ private Schedulable getBean(final ScheduledJob job) { final Schedulable bean = context.getBean(job.getBean(), Schedulable.class); bean.configure(job, job.getConfiguration()); return bean; } /** * The application context to retrieve beans from. * * @param applicationContext The context. */ @Override public void setApplicationContext(final ApplicationContext applicationContext) { this.context = applicationContext; } /** * Bootstraps the scheduler. */ @Override public void start() { // TODO: make this configurable schedulerTask = taskScheduler.schedule(this, new CronTrigger("*/30 * * * * *")); } /** * Stops the scheduler gracefully. */ @Override public void stop() { schedulerTask.cancel(true); for (final String id : new HashSet<>(jobs.keySet())) { jobs.remove(id).cancel(); } schedulerTask = null; } /** * Returns {@code true} if the scheduler is currently running. * * @return True if running, false otherwise. */ @Override public boolean isRunning() { return schedulerTask == null; } /** * Container for the bean, job and future. */ class Job implements Runnable { private final Schedulable bean; private final ScheduledJob job; private ScheduledFuture future; private Job(final Schedulable bean, final ScheduledJob job) { this.bean = bean; this.job = job; } @Override public void run() { repository.updateLastRun(job.getId()); bean.run(); } public Job schedule() { future = taskScheduler.schedule(this, job.getTrigger()); return this; } public Job cancel() { future.cancel(true); bean.cancel(); return this; } public boolean sameAs(final ScheduledJob job) { return this.job.equalTo(job); } } }