diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/Job.java b/spring-batch-core/src/main/java/org/springframework/batch/core/Job.java index 4a93b954b..6e576eeda 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/Job.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/Job.java @@ -28,19 +28,28 @@ public interface Job { String getName(); + /** + * Flag to indicate if this job can be restarted, at least in principle. + * + * @return true if this job can be restarted after a failure + */ boolean isRestartable(); /** * Run the {@link JobExecution} and update the meta information like status - * and statistics as necessary. This method should not throw any exceptions - * for failed execution. Clients should be careful to inspect the {@link JobExecution} - * status to determine success or failure. + * and statistics as necessary. This method should not throw any exceptions + * for failed execution. Clients should be careful to inspect the + * {@link JobExecution} status to determine success or failure. * * @param execution a {@link JobExecution} */ void execute(JobExecution execution); /** + * If clients need to generate new parameters for the next execution in a + * sequence they can use this incrementer. The return value may be null, in + * the case that this job does not have a natural sequence. + * * @return in incrementer to be used for creating new parameters */ JobParametersIncrementer getJobParametersIncrementer(); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java index 1630dc536..18707e1ea 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java @@ -16,15 +16,26 @@ package org.springframework.batch.core.job; -import java.util.ArrayList; -import java.util.List; +import java.util.Date; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobExecutionException; import org.springframework.batch.core.JobExecutionListener; +import org.springframework.batch.core.JobInstance; +import org.springframework.batch.core.JobInterruptedException; import org.springframework.batch.core.JobParametersIncrementer; +import org.springframework.batch.core.StartLimitExceededException; import org.springframework.batch.core.Step; +import org.springframework.batch.core.StepExecution; import org.springframework.batch.core.listener.CompositeExecutionJobListener; import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.repository.JobRestartException; +import org.springframework.batch.item.ExecutionContext; +import org.springframework.batch.repeat.ExitStatus; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; @@ -41,7 +52,7 @@ import org.springframework.util.ClassUtils; */ public abstract class AbstractJob implements Job, BeanNameAware, InitializingBean { - private List steps = new ArrayList(); + private static final Log logger = LogFactory.getLog(AbstractJob.class); private String name; @@ -71,6 +82,15 @@ public abstract class AbstractJob implements Job, BeanNameAware, InitializingBea this.name = name; } + /** + * Assert mandatory properties: {@link JobRepository}. + * + * @see InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws Exception { + Assert.notNull(jobRepository, "JobRepository must be set"); + } + /** * Set the name property if it is not already set. Because of the order of * the callbacks in a Spring container the name property will be set first @@ -96,59 +116,44 @@ public abstract class AbstractJob implements Job, BeanNameAware, InitializingBea this.name = name; } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see org.springframework.batch.core.domain.IJob#getName() */ public String getName() { return name; } - /* (non-Javadoc) - * @see org.springframework.batch.core.domain.IJob#getSteps() - */ - protected List getSteps() { - return steps; - } - - public void setSteps(List steps) { - this.steps.clear(); - this.steps.addAll(steps); - } - - public void addStep(Step step) { - this.steps.add(step); - } - public void setRestartable(boolean restartable) { this.restartable = restartable; } - /* (non-Javadoc) - * @see org.springframework.batch.core.domain.IJob#isRestartable() + /** + * @see Job#isRestartable() */ public boolean isRestartable() { return restartable; } - + /** * Public setter for the {@link JobParametersIncrementer}. - * @param jobParametersIncrementer the {@link JobParametersIncrementer} to set + * @param jobParametersIncrementer the {@link JobParametersIncrementer} to + * set */ public void setJobParametersIncrementer(JobParametersIncrementer jobParametersIncrementer) { this.jobParametersIncrementer = jobParametersIncrementer; } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see org.springframework.batch.core.Job#getJobParametersIncrementer() */ public JobParametersIncrementer getJobParametersIncrementer() { return this.jobParametersIncrementer; } - public String toString() { - return ClassUtils.getShortName(getClass()) + ": [name=" + name + "]"; - } - /** * Public setter for injecting {@link JobExecutionListener}s. They will all * be given the listener callbacks at the appropriate point in the job. @@ -181,16 +186,189 @@ public abstract class AbstractJob implements Job, BeanNameAware, InitializingBea public void setJobRepository(JobRepository jobRepository) { this.jobRepository = jobRepository; } - - public void afterPropertiesSet() throws Exception { - Assert.notNull(getJobRepository(), "JobRepository must be set"); + + /** + * Extension point for subclasses allowing them to concentrate on processing + * logic and ignore listeners and repository calls. Implementations usually + * are concerned with the ordering of steps, and delegate actual step + * processing to {@link #handleStep(Step, JobExecution)}. + * + * @param execution the current {@link JobExecution} + * @return the last {@link StepExecution} (used to compute the final status + * of the {@link JobExecution}) + * + * @throws JobExecutionException to signal a fatal batch framework error + * (not a business or validation exception) + */ + abstract protected StepExecution doExecute(JobExecution execution) throws JobExecutionException; + + /** + * Run the specified job, handling all listener and repository calls, and + * delegating the actual processing to {@link #doExecute(JobExecution)}. + * + * @see Job#execute(JobExecution) + * @throws StartLimitExceededException if start limit of one of the steps + * was exceeded + */ + public final void execute(JobExecution execution) { + + try { + + if (execution.getStatus() != BatchStatus.STOPPING) { + + execution.setStartTime(new Date()); + updateStatus(execution, BatchStatus.STARTING); + + listener.beforeJob(execution); + + StepExecution lastStepExecution = doExecute(execution); + + if (lastStepExecution != null) { + execution.setStatus(lastStepExecution.getStatus()); + execution.setExitStatus(lastStepExecution.getExitStatus()); + } + } + else { + + // The job was already stopped before we even got this far. Deal + // with it in the same way as any other interruption. + execution.setStatus(BatchStatus.STOPPED); + execution.setExitStatus(ExitStatus.FINISHED); + } + + } + catch (JobInterruptedException e) { + execution.setStatus(BatchStatus.STOPPED); + execution.addFailureException(e); + } + catch (Throwable t) { + execution.setStatus(BatchStatus.FAILED); + execution.addFailureException(t); + } + finally { + + if (execution.getStepExecutions().isEmpty()) { + execution.setExitStatus(ExitStatus.NOOP + .addExitDescription("All steps already completed or no steps configured for this job.")); + } + + execution.setEndTime(new Date()); + + try { + listener.afterJob(execution); + } + catch (Exception e) { + logger.error("Exception encountered in afterStep callback", e); + } + + jobRepository.update(execution); + + } + } - protected JobRepository getJobRepository() { - return jobRepository; + /** + * Convenience method for subclasses to delegate the handling of a specific + * step in the context of the current {@link JobExecution}. + * + * @param step the {@link Step} to execute + * @param execution the currect {@link JobExecution} + * @return the {@link StepExecution} corresponding to this step + * + * @throws JobInterruptedException if the {@link JobExecution} has been + * interrupted + * @throws StartLimitExceededException if the start limit has been exceeded + * for this step + * @throws JobRestartException if the job is in an inconsistent state from + * an earlier failure + */ + protected final StepExecution handleStep(Step step, JobExecution execution) throws JobInterruptedException, + JobRestartException, StartLimitExceededException { + if (execution.getStatus() == BatchStatus.STOPPING) { + throw new JobInterruptedException("JobExecution interrupted."); + } + + JobInstance jobInstance = execution.getJobInstance(); + + StepExecution currentStepExecution = null; + + if (shouldStart(jobInstance, step)) { + + updateStatus(execution, BatchStatus.STARTED); + currentStepExecution = execution.createStepExecution(step.getName()); + + StepExecution lastStepExecution = jobRepository.getLastStepExecution(jobInstance, step.getName()); + + boolean isRestart = (lastStepExecution != null && !lastStepExecution.getStatus().equals( + BatchStatus.COMPLETED)) ? true : false; + + if (isRestart) { + currentStepExecution.setExecutionContext(lastStepExecution.getExecutionContext()); + } + else { + currentStepExecution.setExecutionContext(new ExecutionContext()); + } + + step.execute(currentStepExecution); + + } + + return currentStepExecution; + } - protected CompositeExecutionJobListener getCompositeListener() { - return listener; + /** + * Given a step and configuration, return true if the step should start, + * false if it should not, and throw an exception if the job should finish. + * + * @throws StartLimitExceededException if the start limit has been exceeded + * for this step + * @throws JobRestartException if the job is in an inconsistent state from + * an earlier failure + */ + private boolean shouldStart(JobInstance jobInstance, Step step) throws JobRestartException, + StartLimitExceededException { + + BatchStatus stepStatus; + // if the last execution is null, the step has never been executed. + StepExecution lastStepExecution = jobRepository.getLastStepExecution(jobInstance, step.getName()); + if (lastStepExecution == null) { + stepStatus = BatchStatus.STARTING; + } + else { + stepStatus = lastStepExecution.getStatus(); + } + + if (stepStatus == BatchStatus.UNKNOWN) { + throw new JobRestartException("Cannot restart step from UNKNOWN status. " + + "The last execution ended with a failure that could not be rolled back, " + + "so it may be dangerous to proceed. " + "Manual intervention is probably necessary."); + } + + if (stepStatus == BatchStatus.COMPLETED && step.isAllowStartIfComplete() == false) { + // step is complete, false should be returned, indicating that the + // step should not be started + return false; + } + + if (jobRepository.getStepExecutionCount(jobInstance, step.getName()) < step.getStartLimit()) { + // step start count is less than start max, return true + return true; + } + else { + // start max has been exceeded, throw an exception. + throw new StartLimitExceededException("Maximum start limit exceeded for step: " + step.getName() + + "StartMax: " + step.getStartLimit()); + } } + + private void updateStatus(JobExecution jobExecution, BatchStatus status) { + jobExecution.setStatus(status); + jobRepository.update(jobExecution); + } + + public String toString() { + return ClassUtils.getShortName(getClass()) + ": [name=" + name + "]"; + } + } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/ConditionalJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/ConditionalJob.java new file mode 100644 index 000000000..e57910f1c --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/ConditionalJob.java @@ -0,0 +1,37 @@ +/* + * Copyright 2006-2007 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.core.job; + +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobExecutionException; +import org.springframework.batch.core.StepExecution; + +/** + * @author Dave Syer + * + */ +public class ConditionalJob extends AbstractJob { + + /** + * @see AbstractJob#doExecute(JobExecution) + */ + @Override + protected StepExecution doExecute(JobExecution execution) throws JobExecutionException { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleJob.java index e7b304521..4bcb4e235 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleJob.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleJob.java @@ -16,22 +16,17 @@ package org.springframework.batch.core.job; -import java.util.Date; +import java.util.ArrayList; import java.util.List; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.Job; import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionException; -import org.springframework.batch.core.JobInstance; import org.springframework.batch.core.JobInterruptedException; import org.springframework.batch.core.StartLimitExceededException; import org.springframework.batch.core.Step; import org.springframework.batch.core.StepExecution; -import org.springframework.batch.item.ExecutionContext; -import org.springframework.batch.repeat.ExitStatus; +import org.springframework.batch.core.repository.JobRestartException; /** * Simple implementation of {@link Job} interface providing the ability to run a @@ -43,168 +38,50 @@ import org.springframework.batch.repeat.ExitStatus; */ public class SimpleJob extends AbstractJob { - private static final Log logger = LogFactory.getLog(SimpleJob.class); + private List steps = new ArrayList(); /** - * Run the specified job by looping through the steps and delegating to the - * {@link Step}. + * Public setter for the steps in this job. Overrides any calls to + * {@link #addStep(Step)}. * - * @see org.springframework.batch.core.Job#execute(org.springframework.batch.core.JobExecution) - * @throws StartLimitExceededException if start limit of one of the steps - * was exceeded + * @param steps the steps to execute */ - public void execute(JobExecution execution) { - - - List steps = getSteps(); - - try { - - if (execution.getStatus() != BatchStatus.STOPPING) { - - execution.setStartTime(new Date()); - updateStatus(execution, BatchStatus.STARTING); - - getCompositeListener().beforeJob(execution); - - StepExecution lastStepExecution = handleSteps(steps, execution); - - if(lastStepExecution != null){ - execution.setStatus(lastStepExecution.getStatus()); - execution.setExitStatus(lastStepExecution.getExitStatus()); - } - } - else{ - - // The job was already stopped before we even got this far. Deal - // with it in the same way as any other interruption. - execution.setStatus(BatchStatus.STOPPED); - execution.setExitStatus(ExitStatus.FINISHED); - } - - } - catch (JobInterruptedException e) { - execution.setStatus(BatchStatus.STOPPED); - execution.addFailureException(e); - } - catch (Throwable t) { - execution.setStatus(BatchStatus.FAILED); - execution.addFailureException(t); - } - finally { - if (execution.getStepExecutions().size() == 0) { - ExitStatus status = ExitStatus.FAILED; - if (steps.size() > 0) { - status = ExitStatus.NOOP - .addExitDescription("All steps already completed. No processing was done."); - } - else { - status = ExitStatus.NOOP.addExitDescription("No steps configured for this job."); - } - - execution.setExitStatus(status); - } - - execution.setEndTime(new Date()); - - try { - getCompositeListener().afterJob(execution); - } - catch (Exception e) { - logger.error("Exception encountered in afterStep callback", e); - } - - getJobRepository().update(execution); - } - + public void setSteps(List steps) { + this.steps.clear(); + this.steps.addAll(steps); } - private void updateStatus(JobExecution jobExecution, BatchStatus status) { - jobExecution.setStatus(status); - getJobRepository().update(jobExecution); - } - - /* - * Private handler of steps. Returns the last StepExecution successfully processed if it exists, null - * if non were processed. + /** + * Convenience method for adding a single step to the job. * + * @param step a {@link Step} to add */ - private StepExecution handleSteps(List steps, JobExecution execution) throws JobExecutionException{ - - JobInstance jobInstance = execution.getJobInstance(); - StepExecution currentStepExecution = null; + public void addStep(Step step) { + this.steps.add(step); + } + + /** + * Handler of steps sequentially as provided, checking each one for success + * before moving to the next. Returns the last {@link StepExecution} + * successfully processed if it exists, and null if none were processed. + * + * @param execution the current {@link JobExecution} + * @return the last successful {@link StepExecution} + * + * @see AbstractJob#handleStep(Step, JobExecution) + */ + protected StepExecution doExecute(JobExecution execution) throws JobInterruptedException, JobRestartException, + StartLimitExceededException { + + StepExecution stepExecution = null; for (Step step : steps) { - - if (execution.getStatus() == BatchStatus.STOPPING) { - throw new JobInterruptedException("JobExecution interrupted."); - } - - if (shouldStart(jobInstance, step)) { - - updateStatus(execution, BatchStatus.STARTED); - currentStepExecution = execution.createStepExecution(step.getName()); - - StepExecution lastStepExecution = getJobRepository().getLastStepExecution(jobInstance, - step.getName()); - - boolean isRestart = (lastStepExecution != null && !lastStepExecution.getStatus().equals( - BatchStatus.COMPLETED)) ? true : false; - - if (isRestart) { - currentStepExecution.setExecutionContext(lastStepExecution.getExecutionContext()); - } - else { - currentStepExecution.setExecutionContext(new ExecutionContext()); - } - - step.execute(currentStepExecution); - - if (currentStepExecution.getStatus() == BatchStatus.FAILED - || currentStepExecution.getStatus() == BatchStatus.STOPPED) { - return currentStepExecution; - } + stepExecution = handleStep(step, execution); + if (stepExecution.getStatus() == BatchStatus.FAILED || stepExecution.getStatus() == BatchStatus.STOPPED) { + return stepExecution; } } - return currentStepExecution; + return stepExecution; } - /* - * Given a step and configuration, return true if the step should start, - * false if it should not, and throw an exception if the job should finish. - */ - private boolean shouldStart(JobInstance jobInstance, Step step) throws JobExecutionException { - - BatchStatus stepStatus; - // if the last execution is null, the step has never been executed. - StepExecution lastStepExecution = getJobRepository().getLastStepExecution(jobInstance, step.getName()); - if (lastStepExecution == null) { - stepStatus = BatchStatus.STARTING; - } - else { - stepStatus = lastStepExecution.getStatus(); - } - - if (stepStatus == BatchStatus.UNKNOWN) { - throw new JobExecutionException("Cannot restart step from UNKNOWN status. " - + "The last execution ended with a failure that could not be rolled back, " - + "so it may be dangerous to proceed. " + "Manual intervention is probably necessary."); - } - - if (stepStatus == BatchStatus.COMPLETED && step.isAllowStartIfComplete() == false) { - // step is complete, false should be returned, indicating that the - // step should not be started - return false; - } - - if (getJobRepository().getStepExecutionCount(jobInstance, step.getName()) < step.getStartLimit()) { - // step start count is less than start max, return true - return true; - } - else { - // start max has been exceeded, throw an exception. - throw new StartLimitExceededException("Maximum start limit exceeded for step: " + step.getName() - + "StartMax: " + step.getStartLimit()); - } - } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobRegistryBackgroundJobRunner.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobRegistryBackgroundJobRunner.java index 1d27acfde..4f52d3018 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobRegistryBackgroundJobRunner.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobRegistryBackgroundJobRunner.java @@ -75,9 +75,11 @@ public class JobRegistryBackgroundJobRunner { private ApplicationContext parentContext = null; + public static boolean testing = false; + final private String parentContextPath; - private static List errors = new ArrayList(); + private static List errors = new ArrayList(); /** * @param parentContextPath @@ -100,7 +102,7 @@ public class JobRegistryBackgroundJobRunner { * creation. * @return the errors */ - public static List getErrors() { + public static List getErrors() { return errors; } @@ -197,8 +199,11 @@ public class JobRegistryBackgroundJobRunner { return; } - System.out.println("Started application. Hit any key to exit."); - System.in.read(); + synchronized (JobRegistryBackgroundJobRunner.class) { + System.out + .println("Started application. Interrupt (CTRL-C) or call JobRegistryBackgroundJobRunner.stop() to exit."); + JobRegistryBackgroundJobRunner.class.wait(); + } } @@ -210,4 +215,13 @@ public class JobRegistryBackgroundJobRunner { this.parentContext = parent; } + /** + * If embedded in a JVM, call this method to terminate the main method. + */ + public static void stop() { + synchronized (JobRegistryBackgroundJobRunner.class) { + JobRegistryBackgroundJobRunner.class.notify(); + } + } + } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/AbstractJobTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/AbstractJobTests.java index 00a6856fe..680fa0051 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/AbstractJobTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/AbstractJobTests.java @@ -15,41 +15,38 @@ */ package org.springframework.batch.core.job; -import java.util.Collections; - -import junit.framework.TestCase; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import org.junit.Test; import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.step.StepSupport; +import org.springframework.batch.core.JobExecutionException; +import org.springframework.batch.core.StepExecution; /** * @author Dave Syer * */ -public class AbstractJobTests extends TestCase { +public class AbstractJobTests { - AbstractJob job = new AbstractJob("job") { - public void execute(JobExecution execution) { - throw new UnsupportedOperationException(); - } - }; + AbstractJob job = new StubJob("job"); /** * Test method for {@link org.springframework.batch.core.job.AbstractJob#getName()}. */ + @Test public void testGetName() { - job = new AbstractJob(){ - public void execute(JobExecution execution) { - // No-op - } - }; + job = new StubJob(); assertNull(job.getName()); } /** * Test method for {@link org.springframework.batch.core.job.AbstractJob#setBeanName(java.lang.String)}. */ + @Test public void testSetBeanName() { job.setBeanName("foo"); assertEquals("job", job.getName()); @@ -58,53 +55,32 @@ public class AbstractJobTests extends TestCase { /** * Test method for {@link org.springframework.batch.core.job.AbstractJob#setBeanName(java.lang.String)}. */ + @Test public void testSetBeanNameWithNullName() { - job = new AbstractJob(null) { - public void execute(JobExecution execution) { - // NO-OP - } - }; + job = new StubJob(null); assertEquals(null, job.getName()); job.setBeanName("foo"); assertEquals("foo", job.getName()); } - /** - * Test method for {@link org.springframework.batch.core.job.AbstractJob#setSteps(java.util.List)}. - */ - public void testSetSteps() { - job.setSteps(Collections.singletonList((Step) new StepSupport("step"))); - assertEquals(1, job.getSteps().size()); - } - - /** - * Test method for - * {@link org.springframework.batch.core.job.AbstractJob#addStep(org.springframework.batch.core.Step)}. - */ - public void testAddStep() { - job.addStep(new StepSupport("step")); - assertEquals(1, job.getSteps().size()); - } - /** * Test method for {@link org.springframework.batch.core.job.AbstractJob#setRestartable(boolean)}. */ + @Test public void testSetRestartable() { assertFalse(job.isRestartable()); job.setRestartable(true); assertTrue(job.isRestartable()); } + @Test public void testToString() throws Exception { String value = job.toString(); assertTrue("Should contain name: " + value, value.indexOf("name=") >= 0); } + @Test public void testAfterPropertiesSet() throws Exception { - AbstractJob job = new AbstractJob() { - public void execute(JobExecution execution) { - } - }; job.setJobRepository(null); try { job.afterPropertiesSet(); @@ -114,5 +90,31 @@ public class AbstractJobTests extends TestCase { assertTrue(e.getMessage().contains("JobRepository")); } } - + + /** + * @author Dave Syer + * + */ + private static class StubJob extends AbstractJob { + /** + * @param name + */ + private StubJob(String name) { + super(name); + } + + /** + * No-name constructor + */ + public StubJob() { + super(); + } + + @Override + protected StepExecution doExecute(JobExecution execution) throws JobExecutionException { + return null; + } + + } + } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.java index dc284c278..ca400ead8 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.java @@ -19,14 +19,21 @@ package org.springframework.batch.core.job; import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.verify; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; -import junit.framework.TestCase; - +import org.junit.Before; +import org.junit.Test; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobExecutionException; @@ -58,7 +65,7 @@ import org.springframework.batch.repeat.ExitStatus; * * @author Lucas Ward */ -public class SimpleJobTests extends TestCase { +public class SimpleJobTests { private JobRepository jobRepository; @@ -88,8 +95,8 @@ public class SimpleJobTests extends TestCase { private SimpleJob job; - protected void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() throws Exception { MapJobInstanceDao.clear(); MapJobExecutionDao.clear(); @@ -132,7 +139,30 @@ public class SimpleJobTests extends TestCase { } + /** + * Test method for {@link SimpleJob#setSteps(java.util.List)}. + */ + @Test + public void testSetSteps() { + job.setSteps(Collections.singletonList((Step) new StepSupport("step"))); + job.execute(jobExecution); + assertEquals(1, jobExecution.getStepExecutions().size()); + } + + /** + * Test method for + * {@link SimpleJob#addStep(org.springframework.batch.core.Step)}. + */ + @Test + public void testAddStep() { + job.setSteps(Collections. emptyList()); + job.addStep(new StepSupport("step")); + job.execute(jobExecution); + assertEquals(1, jobExecution.getStepExecutions().size()); + } + // Test to ensure the exit status returned by the last step is returned + @Test public void testExitStatusReturned() throws JobExecutionException { final ExitStatus customStatus = new ExitStatus(true, "test"); @@ -162,10 +192,7 @@ public class SimpleJobTests extends TestCase { assertEquals(customStatus, jobExecution.getExitStatus()); } - protected void tearDown() throws Exception { - super.tearDown(); - } - + @Test public void testRunNormally() throws Exception { step1.setStartLimit(5); step2.setStartLimit(5); @@ -179,6 +206,7 @@ public class SimpleJobTests extends TestCase { assertFalse(step2.passedInJobContext.isEmpty()); } + @Test public void testRunNormallyWithListener() throws Exception { job.setJobExecutionListeners(new JobExecutionListenerSupport[] { new JobExecutionListenerSupport() { public void beforeJob(JobExecution jobExecution) { @@ -193,6 +221,7 @@ public class SimpleJobTests extends TestCase { assertEquals(4, list.size()); } + @Test public void testRunWithSimpleStepExecutor() throws Exception { job.setJobRepository(jobRepository); @@ -205,6 +234,7 @@ public class SimpleJobTests extends TestCase { } + @Test public void testExecutionContextIsSet() throws Exception { testRunNormally(); assertEquals(jobInstance, jobExecution.getJobInstance()); @@ -213,6 +243,7 @@ public class SimpleJobTests extends TestCase { assertEquals(step2.getName(), stepExecution2.getStepName()); } + @Test public void testInterrupted() throws Exception { step1.setStartLimit(5); step2.setStartLimit(5); @@ -225,6 +256,7 @@ public class SimpleJobTests extends TestCase { checkRepository(BatchStatus.STOPPED, ExitStatus.FAILED); } + @Test public void testFailed() throws Exception { step1.setStartLimit(5); step2.setStartLimit(5); @@ -239,6 +271,7 @@ public class SimpleJobTests extends TestCase { checkRepository(BatchStatus.FAILED, ExitStatus.FAILED); } + @Test public void testFailedWithListener() throws Exception { job.setJobExecutionListeners(new JobExecutionListenerSupport[] { new JobExecutionListenerSupport() { public void afterJob(JobExecution jobExecution) { @@ -255,6 +288,7 @@ public class SimpleJobTests extends TestCase { checkRepository(BatchStatus.FAILED, ExitStatus.FAILED); } + @Test public void testFailedWithError() throws Exception { step1.setStartLimit(5); step2.setStartLimit(5); @@ -268,6 +302,7 @@ public class SimpleJobTests extends TestCase { checkRepository(BatchStatus.FAILED, ExitStatus.FAILED); } + @Test public void testStepShouldNotStart() throws Exception { // Start policy will return false, keeping the step from being started. step1.setStartLimit(0); @@ -280,13 +315,14 @@ public class SimpleJobTests extends TestCase { .indexOf("start limit exceeded") >= 0); } + @Test public void testNoSteps() throws Exception { job.setSteps(new ArrayList()); job.execute(jobExecution); ExitStatus exitStatus = jobExecution.getExitStatus(); assertTrue("Wrong message in execution: " + exitStatus, exitStatus.getExitDescription().indexOf( - "No steps configured") >= 0); + "no steps configured") >= 0); } // public void testNoStepsExecuted() throws Exception { @@ -302,6 +338,7 @@ public class SimpleJobTests extends TestCase { // "steps already completed")); // } + @Test public void testNotExecutedIfAlreadyStopped() throws Exception { jobExecution.stop(); job.execute(jobExecution); @@ -312,6 +349,7 @@ public class SimpleJobTests extends TestCase { assertEquals(ExitStatus.NOOP.getExitCode(), exitStatus.getExitCode()); } + @Test public void testRestart() throws Exception { step1.setAllowStartIfComplete(true); final RuntimeException exception = new RuntimeException("Foo!"); @@ -328,6 +366,7 @@ public class SimpleJobTests extends TestCase { assertFalse(step2.passedInStepContext.isEmpty()); } + @Test public void testInterruptWithListener() throws Exception { step1.setProcessException(new JobInterruptedException("job interrupted!")); @@ -347,6 +386,7 @@ public class SimpleJobTests extends TestCase { /** * Execution context should be restored on restart. */ + @Test public void testRestartScenario() throws Exception { job.setRestartable(true); @@ -375,6 +415,7 @@ public class SimpleJobTests extends TestCase { assertFalse(step2.passedInJobContext.isEmpty()); } + @Test public void testInterruptJob() throws Exception { step1 = new StubStep("interruptStep") { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/JobRegistryBackgroundJobRunnerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/JobRegistryBackgroundJobRunnerTests.java index 106d1ba7b..66eb4ba52 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/JobRegistryBackgroundJobRunnerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/JobRegistryBackgroundJobRunnerTests.java @@ -50,5 +50,6 @@ public class JobRegistryBackgroundJobRunnerTests { public void tearDown() throws Exception { System.clearProperty(JobRegistryBackgroundJobRunner.EMBEDDED); JobRegistryBackgroundJobRunner.getErrors().clear(); + JobRegistryBackgroundJobRunner.stop(); } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleStepFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleStepFactoryBeanTests.java index f7b090401..d57c3939c 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleStepFactoryBeanTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleStepFactoryBeanTests.java @@ -34,7 +34,6 @@ import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.Step; import org.springframework.batch.core.StepListener; -import org.springframework.batch.core.job.AbstractJob; import org.springframework.batch.core.job.SimpleJob; import org.springframework.batch.core.listener.ItemListenerSupport; import org.springframework.batch.core.repository.dao.MapExecutionContextDao; @@ -71,15 +70,12 @@ public class SimpleStepFactoryBeanTests { private ItemReader reader; - private AbstractJob job = new SimpleJob() { - { - setBeanName("simpleJob"); - } - }; + private SimpleJob job = new SimpleJob(); @Before public void setUp() throws Exception { job.setJobRepository(repository); + job.setBeanName("simpleJob"); MapJobInstanceDao.clear(); MapJobExecutionDao.clear(); MapStepExecutionDao.clear(); @@ -151,7 +147,7 @@ public class SimpleStepFactoryBeanTests { job.setSteps(Collections.singletonList(step)); JobExecution jobExecution = repository.createJobExecution(job.getName(), new JobParameters()); - + job.execute(jobExecution); assertEquals("Error!", jobExecution.getAllFailureExceptions().get(0).getMessage()); diff --git a/spring-batch-samples/src/main/resources/log4j.properties b/spring-batch-samples/src/main/resources/log4j.properties index 33ed59a2b..168d462ec 100644 --- a/spring-batch-samples/src/main/resources/log4j.properties +++ b/spring-batch-samples/src/main/resources/log4j.properties @@ -22,7 +22,7 @@ log4j.rootLogger=info, stdout #log4j.logger.org.hibernate.connection.DriverManagerConnectionProvider=trace ### enable spring -log4j.logger.org.springframework=info +#log4j.logger.org.springframework=info #log4j.logger.org.springframework.transaction=debug #log4j.logger.org.springframework.jdbc.core=debug #log4j.logger.org.springframework.orm=debug diff --git a/spring-batch-samples/src/test/java/TestSuite.java b/spring-batch-samples/src/test/java/TestSuite.java new file mode 100644 index 000000000..f3b75e658 --- /dev/null +++ b/spring-batch-samples/src/test/java/TestSuite.java @@ -0,0 +1,30 @@ +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.springframework.batch.sample.HibernateJobFunctionalTests; +import org.springframework.batch.sample.launch.RemoteLauncherTests; + +/* + * Copyright 2006-2007 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. + */ + +/** + * @author Dave Syer + * + */ +@RunWith(Suite.class) +@Suite.SuiteClasses( { RemoteLauncherTests.class, HibernateJobFunctionalTests.class }) +public class TestSuite { + +} diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/launch/RemoteLauncherTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/launch/RemoteLauncherTests.java index a86916dbe..53fbdee47 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/sample/launch/RemoteLauncherTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/sample/launch/RemoteLauncherTests.java @@ -28,7 +28,8 @@ import javax.management.MalformedObjectNameException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.junit.Before; +import org.junit.AfterClass; +import org.junit.BeforeClass; import org.junit.Test; import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.launch.support.JobRegistryBackgroundJobRunner; @@ -42,7 +43,7 @@ import org.springframework.jmx.support.MBeanServerConnectionFactoryBean; * */ public class RemoteLauncherTests { - + private static Log logger = LogFactory.getLog(RemoteLauncherTests.class); private static List errors = new ArrayList(); @@ -51,6 +52,8 @@ public class RemoteLauncherTests { private static JobLoader loader; + static private Thread thread; + @Test public void testConnect() throws Exception { String message = errors.isEmpty() ? "" : errors.get(0).getMessage(); @@ -63,12 +66,13 @@ public class RemoteLauncherTests { assertEquals(0, errors.size()); assertTrue(isConnected()); try { - launcher.start("foo", "time="+(new Date().getTime())); + launcher.start("foo", "time=" + (new Date().getTime())); fail("Expected RuntimeException"); - } catch (RuntimeException e) { - //expected; + } + catch (RuntimeException e) { + // expected; String message = e.getMessage(); - assertTrue("Wrong message: "+message, message.contains("NoSuchJobException")); + assertTrue("Wrong message: " + message, message.contains("NoSuchJobException")); } } @@ -81,15 +85,13 @@ public class RemoteLauncherTests { /* * (non-Javadoc) + * * @see junit.framework.TestCase#setUp() */ - @Before - public void setUp() throws Exception { - if (launcher != null) { - return; - } + @BeforeClass + public static void setUp() throws Exception { System.setProperty("com.sun.management.jmxremote", ""); - Thread thread = new Thread(new Runnable() { + thread = new Thread(new Runnable() { public void run() { try { JobRegistryBackgroundJobRunner.main("adhoc-job-launcher-context.xml", "jobs/adhocLoopJob.xml"); @@ -106,6 +108,11 @@ public class RemoteLauncherTests { } } + @AfterClass + public static void cleanUp() { + JobRegistryBackgroundJobRunner.stop(); + } + private static boolean isConnected() throws Exception { boolean connected = false; if (!JobRegistryBackgroundJobRunner.getErrors().isEmpty()) { @@ -114,7 +121,8 @@ public class RemoteLauncherTests { if (launcher == null) { MBeanServerConnectionFactoryBean connectionFactory = new MBeanServerConnectionFactoryBean(); try { - launcher = (JobOperator) getMBean(connectionFactory, "spring:service=batch,bean=jobOperator", JobOperator.class); + launcher = (JobOperator) getMBean(connectionFactory, "spring:service=batch,bean=jobOperator", + JobOperator.class); loader = (JobLoader) getMBean(connectionFactory, "spring:service=batch,bean=jobLoader", JobLoader.class); } catch (MBeanServerNotFoundException e) { @@ -124,7 +132,7 @@ public class RemoteLauncherTests { } try { launcher.getJobNames(); - connected = loader.getConfigurations().size()>0; + connected = loader.getConfigurations().size() > 0; logger.info("Configurations loaded: " + loader.getConfigurations()); } catch (InvalidInvocationException e) { @@ -133,8 +141,8 @@ public class RemoteLauncherTests { return connected; } - private static Object getMBean(MBeanServerConnectionFactoryBean connectionFactory, String objectName, Class interfaceType) - throws MalformedObjectNameException { + private static Object getMBean(MBeanServerConnectionFactoryBean connectionFactory, String objectName, + Class interfaceType) throws MalformedObjectNameException { MBeanProxyFactoryBean factory = new MBeanProxyFactoryBean(); factory.setObjectName(objectName); factory.setProxyInterface(interfaceType);