/* * Copyright 2009-2010 the original author or authors. * * 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 org.springframework.batch.admin.launch; import java.util.Date; import java.util.HashSet; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; import org.springframework.batch.core.Job; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.factory.InitializingBean; import org.springframework.jmx.export.annotation.ManagedAttribute; import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.jmx.export.annotation.ManagedResource; import org.springframework.util.Assert; /** * Wrapper for a {@link JobLauncher} that synchronizes jobs globally so that * only one execution of a given Job can be active at once. * * @author Dave Syer * */ @Aspect @ManagedResource public class JobLauncherSynchronizer implements InitializingBean { private static final Log logger = LogFactory.getLog(JobLauncherSynchronizer.class); private JobExplorer jobExplorer; private JobRepository jobRepository; private Set<String> jobNames = new HashSet<String>(); /** * The {@link JobExplorer} to use to inspect existing executions. * * @param jobExplorer a {@link JobExplorer} */ public void setJobExplorer(JobExplorer jobExplorer) { this.jobExplorer = jobExplorer; } /** * The {@link JobRepository} needed for updates to execution data. * * @param jobRepository a {@link JobRepository} */ public void setJobRepository(JobRepository jobRepository) { this.jobRepository = jobRepository; } /** * Set of job names that will be synchronized. Others are ignored. * * @param jobNames the job names */ public void setJobNames(Set<String> jobNames) { this.jobNames = jobNames; } /** * A job name that will be synchronized. * * @param jobName the job name */ @ManagedOperation public void addJobName(String jobName) { this.jobNames.add(jobName); } /** * Remove a job name from the list to synchronize. * * @param jobName the job name */ @ManagedOperation public void removeJobName(String jobName) { this.jobNames.remove(jobName); } /** * @return the jobNames */ @ManagedAttribute public Set<String> getJobNames() { return jobNames; } public void afterPropertiesSet() throws Exception { Assert.notNull(jobExplorer, "A JobExplorer must be provided"); Assert.notNull(jobRepository, "A JobRepository must be provided"); } @Before("execution(* org.springframework.batch..JobLauncher+.*(..)) && args(job,..)") public void checkJobBeforeLaunch(Job job) throws JobExecutionAlreadyRunningException { String jobName = job.getName(); logger.debug("Checking for synchronization on Job: " + jobName); if (!jobNames.contains(jobName)) { logger.debug("Not synchronizing Job: " + jobName); return; } Set<JobExecution> running = jobExplorer.findRunningJobExecutions(jobName); if (!running.isEmpty()) { throw new JobExecutionAlreadyRunningException("An instance of this job is already active: "+jobName); } logger.debug("Job checked and no duplicates detected: " + jobName); } @AfterReturning(value = "execution(* org.springframework.batch..JobRepository+.createJobExecution(..)) && args(jobName,..)", returning = "jobExecution") public void checkJobDuringLaunch(String jobName, JobExecution jobExecution) throws JobExecutionAlreadyRunningException { logger.debug("Re-checking for synchronization on JobExecution: " + jobExecution); if (!jobNames.contains(jobName)) { logger.debug("Not re-checking for synchronization of Job: " + jobName); return; } Set<JobExecution> running = jobExplorer.findRunningJobExecutions(jobName); if (running.size() > 1) { jobExecution.setEndTime(new Date()); jobExecution.upgradeStatus(BatchStatus.ABANDONED); jobExecution.setExitStatus(jobExecution.getExitStatus().and(ExitStatus.NOOP).addExitDescription( "Not executed because another execution was detected for the same Job.")); jobRepository.update(jobExecution); throw new JobExecutionAlreadyRunningException("An instance of this job is already active: "+jobName); } } }