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 fdd0b7b70..4a93b954b 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 @@ -32,12 +32,13 @@ public interface Job { /** * Run the {@link JobExecution} and update the meta information like status - * and statistics as necessary. + * 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} - * @throws JobExecutionException */ - void execute(JobExecution execution) throws JobExecutionException; + void execute(JobExecution execution); /** * @return in incrementer to be used for creating new parameters diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecution.java b/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecution.java index 91281a58b..7207d344b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecution.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecution.java @@ -16,9 +16,11 @@ package org.springframework.batch.core; +import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashSet; +import java.util.List; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.repeat.ExitStatus; @@ -48,6 +50,8 @@ public class JobExecution extends Entity { private volatile ExitStatus exitStatus = ExitStatus.UNKNOWN; private volatile ExecutionContext executionContext = new ExecutionContext(); + + private volatile List failureExceptions = new ArrayList(); /** * Because a JobExecution isn't valid unless the job is set, this @@ -234,12 +238,63 @@ public class JobExecution extends Entity { stepExecutions.add(stepExecution); } + /** + * Get the date representing the last time this JobExecution was updated in the + * JobRepository. + * + * @return Date representing the last time this JobExecution was updated. + */ public Date getLastUpdated() { return lastUpdated; } + /** + * Set the last time this JobExecution was updated. + * + * @param lastUpdated + */ public void setLastUpdated(Date lastUpdated) { this.lastUpdated = lastUpdated; } + public List getFailureExceptions() { + return failureExceptions; + } + + /** + * Set the list of failure causing exceptions for this JobExecution. It + * should be noted that the exceptions should only be for the job execution + * not step executions. + * + * @param failureExceptions + */ + public void setFailureExceptions(List failureExceptions) { + this.failureExceptions = failureExceptions; + } + + /** + * Add the provided throwable to the failure exception list. + * + * @param t + */ + public void addFailureException(Throwable t){ + this.failureExceptions.add(t); + } + + /** + * Return all failure causing exceptions for this JobExecution, including + * step executions. + * + * @return List containing all exceptions causing failure for this + * JobExecution. + */ + public List getAllFailureExceptions(){ + + List allExceptions = new ArrayList(failureExceptions); + for(StepExecution stepExecution: stepExecutions){ + allExceptions.addAll(stepExecution.getFailureExceptions()); + } + + return allExceptions; + } } \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/StepExecution.java b/spring-batch-core/src/main/java/org/springframework/batch/core/StepExecution.java index d63019180..da8d4a6e5 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/StepExecution.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/StepExecution.java @@ -16,7 +16,9 @@ package org.springframework.batch.core; +import java.util.ArrayList; import java.util.Date; +import java.util.List; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.repeat.ExitStatus; @@ -68,6 +70,8 @@ public class StepExecution extends Entity { private volatile boolean terminateOnly; private volatile int filterCount; + + private volatile List failureExceptions = new ArrayList(); /** * Constructor with mandatory properties. @@ -436,6 +440,18 @@ public class StepExecution extends Entity { public void setLastUpdated(Date lastUpdated) { this.lastUpdated = lastUpdated; } + + public List getFailureExceptions() { + return failureExceptions; + } + + public void setFailureExceptions(List failureExceptions) { + this.failureExceptions = failureExceptions; + } + + public void addFailureException(Throwable throwable){ + this.failureExceptions.add(throwable); + } /* * (non-Javadoc) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactory.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactory.java index bbad0185a..59201f4eb 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactory.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactory.java @@ -90,10 +90,9 @@ public class ApplicationContextJobFactory implements JobFactory { /** * @param execution - * @throws JobExecutionException * @see org.springframework.batch.core.Job#execute(org.springframework.batch.core.JobExecution) */ - public void execute(JobExecution execution) throws JobExecutionException { + public void execute(JobExecution execution) { try { delegate.execute(execution); } 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 7f9e54f03..c62b35aeb 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 @@ -28,7 +28,6 @@ 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.core.UnexpectedJobExecutionException; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.repeat.ExitStatus; @@ -50,7 +49,7 @@ public class SimpleJob extends AbstractJob { * @throws StartLimitExceededException if start limit of one of the steps * was exceeded */ - public void execute(JobExecution execution) throws JobExecutionException { + public void execute(JobExecution execution){ JobInstance jobInstance = execution.getJobInstance(); @@ -97,6 +96,10 @@ public class SimpleJob extends AbstractJob { step.execute(currentStepExecution); + if(currentStepExecution.getStatus() == BatchStatus.FAILED || + currentStepExecution.getStatus() == BatchStatus.STOPPED ){ + break; + } } } @@ -105,20 +108,30 @@ public class SimpleJob extends AbstractJob { throw new JobInterruptedException("JobExecution interrupted."); } - updateStatus(execution, BatchStatus.COMPLETED); + updateStatus(execution, currentStepExecution.getStatus()); - getCompositeListener().afterJob(execution); + //This is temporary, given the changes to how exceptions are handled, there really should only be an afterJob method + //but it's being left as is until the listener contract can be discussed. + if(execution.getStatus() == BatchStatus.COMPLETED){ + getCompositeListener().afterJob(execution); + } + else if(execution.getStatus() == BatchStatus.FAILED){ + getCompositeListener().onError(execution, currentStepExecution.getFailureExceptions().get(0)); + } + else if(execution.getStatus() == BatchStatus.STOPPED){ + getCompositeListener().onInterrupt(execution); + } } catch (JobInterruptedException e) { execution.setStatus(BatchStatus.STOPPED); + execution.addFailureException(e); getCompositeListener().onInterrupt(execution); - rethrow(e); } catch (Throwable t) { execution.setStatus(BatchStatus.FAILED); + execution.addFailureException(t); getCompositeListener().onError(execution, t); - rethrow(t); } finally { ExitStatus status = ExitStatus.FAILED; @@ -185,19 +198,4 @@ public class SimpleJob extends AbstractJob { + "StartMax: " + step.getStartLimit()); } } - - /** - * @param t - */ - private static void rethrow(Throwable t) throws RuntimeException { - if (t instanceof RuntimeException) { - throw (RuntimeException) t; - } - else if (t instanceof Error) { - throw (Error) t; - } - else { - throw new UnexpectedJobExecutionException("Unexpected checked exception in job execution", t); - } - } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobLauncher.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobLauncher.java index 81709f533..557f02461 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobLauncher.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobLauncher.java @@ -37,10 +37,12 @@ import org.springframework.batch.core.repository.JobExecutionAlreadyRunningExcep public interface JobLauncher { /** - * Start a job execution for the given {@link Job} and {@link JobParameters}. + * Start a job execution for the given {@link Job} and {@link JobParameters}. If + * a JobExecution was able to be created successfully, it will always be returned + * by this method, regardless of whether or not the execution was successful. * - * @return the exit code from the job if it returns synchronously. If the - * implementation is asynchronous, the status might well be unknown. + * @return the {@link JobExecution} if it returns synchronously. If the + * implementation is asynchronous, the status might well be unknown. * * @throws JobExecutionAlreadyRunningException if the JobInstance identified * by the properties already has an execution running. diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobLauncher.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobLauncher.java index 2e16c82dd..c8f4646c4 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobLauncher.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobLauncher.java @@ -99,8 +99,8 @@ public class SimpleJobLauncher implements JobLauncher, InitializingBean { try { logger.info("Job: [" + job + "] launched with the following parameters: [" + jobParameters + "]"); job.execute(jobExecution); - logger.info("Job: [" + job + "] completed successfully with the following parameters: [" - + jobParameters + "]"); + logger.info("Job: [" + job + "] completed with the following parameters: [" + + jobParameters + "] and the following status: [" + jobExecution.getStatus() + "]"); } catch (Throwable t) { logger.info("Job: [" + job + "] failed with the following parameters: [" + jobParameters + "]", t); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java index 530afa2ca..25be8c920 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java @@ -188,12 +188,8 @@ public abstract class AbstractStep implements Step, InitializingBean, BeanNameAw try { getCompositeListener().beforeStep(stepExecution); - try { - open(stepExecution.getExecutionContext()); - } - catch (Exception e) { - throw new UnexpectedJobExecutionException("Failed to initialize the step", e); - } + open(stepExecution.getExecutionContext()); + exitStatus = doExecute(stepExecution); // Check if someone is trying to stop us @@ -220,6 +216,7 @@ public abstract class AbstractStep implements Step, InitializingBean, BeanNameAw logger.error("Encountered an error executing the step: " + e.getClass() + ": " + e.getMessage()); stepExecution.setStatus(determineBatchStatus(e)); exitStatus = getDefaultExitStatusForFailure(e); + stepExecution.addFailureException(e); try { exitStatus = exitStatus.and(getCompositeListener().onErrorInStep(stepExecution, e)); @@ -227,8 +224,8 @@ public abstract class AbstractStep implements Step, InitializingBean, BeanNameAw } catch (Exception ex) { logger.error("Encountered an error on listener error callback.", ex); + stepExecution.addFailureException(ex); } - rethrow(e); } finally { @@ -252,35 +249,18 @@ public abstract class AbstractStep implements Step, InitializingBean, BeanNameAw } catch (Exception e) { logger.error("Exception while closing step execution resources", e); - throw new UnexpectedJobExecutionException("Exception while closing step resources", e); + stepExecution.addFailureException(e); } if (commitException != null) { stepExecution.setStatus(BatchStatus.UNKNOWN); logger.error("Encountered an error saving batch meta data." + "This job is now in an unknown state and should not be restarted.", commitException); - throw new UnexpectedJobExecutionException("Encountered an error saving batch meta data.", - commitException); + stepExecution.addFailureException(commitException); } } } - private static void rethrow(Throwable e) throws JobInterruptedException { - if (e instanceof Error) { - throw (Error) e; - } - if (e instanceof JobInterruptedException) { - throw (JobInterruptedException) e; - } - else if (e.getCause() instanceof JobInterruptedException) { - throw (JobInterruptedException) e.getCause(); - } - else if (e instanceof RuntimeException) { - throw (RuntimeException) e; - } - throw new UnexpectedJobExecutionException("Unexpected checked exception in step execution", e); - } - /** * Determine the step status based on the exception. */ diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/JobExecutionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/JobExecutionTests.java index 47ba2b425..f396edc4d 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/JobExecutionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/JobExecutionTests.java @@ -15,21 +15,24 @@ */ package org.springframework.batch.core; -import java.util.Date; +import static org.junit.Assert.*; -import junit.framework.TestCase; +import java.util.Date; +import java.util.List; import org.apache.commons.lang.SerializationUtils; +import org.junit.Test; import org.springframework.batch.repeat.ExitStatus; /** * @author Dave Syer * */ -public class JobExecutionTests extends TestCase { +public class JobExecutionTests { private JobExecution execution = new JobExecution(new JobInstance(new Long(11), new JobParameters(), "foo"), new Long(12)); + @Test public void testJobExecution() { assertNull(new JobExecution(new JobInstance(null,null,"foo")).getId()); } @@ -38,6 +41,7 @@ public class JobExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.JobExecution#getEndTime()}. */ + @Test public void testGetEndTime() { assertNull(execution.getEndTime()); execution.setEndTime(new Date(100L)); @@ -48,6 +52,7 @@ public class JobExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.JobExecution#getEndTime()}. */ + @Test public void testIsRunning() { assertTrue(execution.isRunning()); execution.setEndTime(new Date(100L)); @@ -58,6 +63,7 @@ public class JobExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.JobExecution#getEndTime()}. */ + @Test public void testIsRunningWithStoppedExecution() { assertTrue(execution.isRunning()); execution.stop(); @@ -69,6 +75,7 @@ public class JobExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.JobExecution#getStartTime()}. */ + @Test public void testGetStartTime() { execution.setStartTime(new Date(0L)); assertEquals(0L, execution.getStartTime().getTime()); @@ -78,6 +85,7 @@ public class JobExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.JobExecution#getStatus()}. */ + @Test public void testGetStatus() { assertEquals(BatchStatus.STARTING, execution.getStatus()); execution.setStatus(BatchStatus.COMPLETED); @@ -88,6 +96,7 @@ public class JobExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.JobExecution#getJobId()}. */ + @Test public void testGetJobId() { assertEquals(11, execution.getJobId().longValue()); execution = new JobExecution(new JobInstance(new Long(23), new JobParameters(), "testJob"), null); @@ -98,6 +107,7 @@ public class JobExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.JobExecution#getJobId()}. */ + @Test public void testGetJobIdForNullJob() { execution = new JobExecution(null, null); assertEquals(null, execution.getJobId()); @@ -107,6 +117,7 @@ public class JobExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.JobExecution#getJobId()}. */ + @Test public void testGetJob() { assertNotNull(execution.getJobInstance()); } @@ -115,22 +126,26 @@ public class JobExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.JobExecution#getExitStatus()}. */ + @Test public void testGetExitCode() { assertEquals(ExitStatus.UNKNOWN, execution.getExitStatus()); execution.setExitStatus(new ExitStatus(true, "23")); assertEquals("23", execution.getExitStatus().getExitCode()); } + @Test public void testContextContainsInfo() throws Exception { assertEquals("foo", execution.getJobInstance().getJobName()); } + @Test public void testAddAndRemoveStepExecution() throws Exception { assertEquals(0, execution.getStepExecutions().size()); execution.createStepExecution("step"); assertEquals(1, execution.getStepExecutions().size()); } + @Test public void testStop() throws Exception { StepExecution stepExecution = execution.createStepExecution("step"); assertFalse(stepExecution.isTerminateOnly()); @@ -138,21 +153,42 @@ public class JobExecutionTests extends TestCase { assertTrue(stepExecution.isTerminateOnly()); } + @Test public void testToString() throws Exception { assertTrue("JobExecution string does not contain id", execution.toString().indexOf("id=") >= 0); assertTrue("JobExecution string does not contain name: " + execution, execution.toString().indexOf("foo") >= 0); } + @Test public void testToStringWithNullJob() throws Exception { execution = new JobExecution(new JobInstance(null,null,"foo")); assertTrue("JobExecution string does not contain id", execution.toString().indexOf("id=") >= 0); assertTrue("JobExecution string does not contain job: " + execution, execution.toString().indexOf("job=") >= 0); } + @Test public void testSerialization() { byte[] serialized = SerializationUtils.serialize(execution); JobExecution deserialize = (JobExecution) SerializationUtils.deserialize(serialized); assertEquals(execution, deserialize); assertNotNull(deserialize.createStepExecution("foo")); } + + public void testFailureExceptions(){ + + RuntimeException exception = new RuntimeException(); + assertEquals(0, execution.getFailureExceptions().size()); + execution.addFailureException(exception); + assertEquals(1, execution.getFailureExceptions().size()); + assertEquals(exception, execution.getFailureExceptions().get(0)); + StepExecution stepExecution1 = execution.createStepExecution("execution1"); + RuntimeException stepException1 = new RuntimeException(); + stepExecution1.addFailureException(stepException1); + execution.createStepExecution("execution2"); + List allExceptions = execution.getAllFailureExceptions(); + assertEquals(2, allExceptions.size()); + assertEquals(1, execution.getFailureExceptions().size()); + assertTrue(allExceptions.contains(exception)); + assertTrue(allExceptions.contains(stepException1)); + } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/StepExecutionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/StepExecutionTests.java index 55ac6f2e0..d41c1daab 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/StepExecutionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/StepExecutionTests.java @@ -15,13 +15,15 @@ */ package org.springframework.batch.core; +import static org.junit.Assert.*; + import java.util.Date; import java.util.HashSet; import java.util.Set; -import junit.framework.TestCase; - import org.apache.commons.lang.SerializationUtils; +import org.junit.Before; +import org.junit.Test; import org.springframework.batch.core.step.StepSupport; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.repeat.ExitStatus; @@ -30,7 +32,7 @@ import org.springframework.batch.repeat.ExitStatus; * @author Dave Syer * */ -public class StepExecutionTests extends TestCase { +public class StepExecutionTests { private StepExecution execution = newStepExecution(new StepSupport("stepName"), new Long(23)); @@ -40,15 +42,17 @@ public class StepExecutionTests extends TestCase { - @Override - protected void setUp() throws Exception { + @Before + public void setUp() throws Exception { foobarEc.put("foo", "bar"); } + @Test public void testStepExecution() { assertNull(new StepExecution("step", null).getId()); } + @Test public void testStepExecutionWithNullId() { assertNull(new StepExecution("stepName", new JobExecution(new JobInstance(null,null,"foo"))).getId()); } @@ -57,6 +61,7 @@ public class StepExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.JobExecution#getEndTime()}. */ + @Test public void testGetEndTime() { assertNull(execution.getEndTime()); execution.setEndTime(new Date(0L)); @@ -67,6 +72,7 @@ public class StepExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.JobExecution#getStartTime()}. */ + @Test public void testGetStartTime() { assertNotNull(execution.getStartTime()); execution.setStartTime(new Date(10L)); @@ -77,6 +83,7 @@ public class StepExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.JobExecution#getStatus()}. */ + @Test public void testGetStatus() { assertEquals(BatchStatus.STARTING, execution.getStatus()); execution.setStatus(BatchStatus.COMPLETED); @@ -87,6 +94,7 @@ public class StepExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.JobExecution#getJobId()}. */ + @Test public void testGetJobId() { assertEquals(23, execution.getJobExecutionId().longValue()); } @@ -95,6 +103,7 @@ public class StepExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.JobExecution#getExitStatus()}. */ + @Test public void testGetExitCode() { assertEquals(ExitStatus.CONTINUABLE, execution.getExitStatus()); execution.setExitStatus(ExitStatus.FINISHED); @@ -105,20 +114,24 @@ public class StepExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.StepExecution#getCommitCount()}. */ + @Test public void testGetCommitCount() { execution.setCommitCount(123); assertEquals(123, execution.getCommitCount()); } + @Test public void testGetFilterCount() { execution.setFilterCount(123); assertEquals(123, execution.getFilterCount()); } + @Test public void testGetJobExecution() throws Exception { assertNotNull(execution.getJobExecution()); } + @Test public void testApplyContribution() throws Exception { StepContribution contribution = execution.createStepContribution(); contribution.incrementReadSkipCount(); @@ -134,12 +147,14 @@ public class StepExecutionTests extends TestCase { assertEquals(1, execution.getFilterCount()); } + @Test public void testTerminateOnly() throws Exception { assertFalse(execution.isTerminateOnly()); execution.setTerminateOnly(); assertTrue(execution.isTerminateOnly()); } + @Test public void testNullNameIsIllegal() throws Exception { try { new StepExecution(null, new JobExecution(new JobInstance(null, null, "job"))); @@ -150,6 +165,7 @@ public class StepExecutionTests extends TestCase { } } + @Test public void testToString() throws Exception { assertTrue("Should contain read count: " + execution.toString(), execution.toString().indexOf("read") >= 0); assertTrue("Should contain write count: " + execution.toString(), execution.toString().indexOf("write") >= 0); @@ -159,6 +175,7 @@ public class StepExecutionTests extends TestCase { execution.toString().indexOf("rollback") >= 0); } + @Test public void testExecutionContext() throws Exception { assertNotNull(execution.getExecutionContext()); ExecutionContext context = new ExecutionContext(); @@ -167,6 +184,7 @@ public class StepExecutionTests extends TestCase { assertEquals("bar", execution.getExecutionContext().getString("foo")); } + @Test public void testEqualsWithSameIdentifier() throws Exception { Step step = new StepSupport("stepName"); Entity stepExecution1 = newStepExecution(step, new Long(11)); @@ -174,30 +192,36 @@ public class StepExecutionTests extends TestCase { assertEquals(stepExecution1, stepExecution2); } + @Test public void testEqualsWithNull() throws Exception { Entity stepExecution = newStepExecution(new StepSupport("stepName"), new Long(11)); assertFalse(stepExecution.equals(null)); } + @Test public void testEqualsWithNullIdentifiers() throws Exception { Entity stepExecution = newStepExecution(new StepSupport("stepName"), new Long(11)); assertFalse(stepExecution.equals(blankExecution)); } + @Test public void testEqualsWithNullJob() throws Exception { Entity stepExecution = newStepExecution(new StepSupport("stepName"), new Long(11)); assertFalse(stepExecution.equals(blankExecution)); } + @Test public void testEqualsWithSelf() throws Exception { assertTrue(execution.equals(execution)); } + @Test public void testEqualsWithDifferent() throws Exception { Entity stepExecution = newStepExecution(new StepSupport("foo"), new Long(13)); assertFalse(execution.equals(stepExecution)); } + @Test public void testEqualsWithNullStepId() throws Exception { Step step = new StepSupport("name"); execution = newStepExecution(step, new Long(31)); @@ -207,15 +231,18 @@ public class StepExecutionTests extends TestCase { assertTrue(execution.equals(stepExecution)); } + @Test public void testHashCode() throws Exception { assertTrue("Hash code same as parent", new Entity(execution.getId()).hashCode() != execution.hashCode()); } + @Test public void testHashCodeWithNullIds() throws Exception { assertTrue("Hash code not same as parent", new Entity(execution.getId()).hashCode() != blankExecution .hashCode()); } + @Test public void testHashCodeViaHashSet() throws Exception { Set set = new HashSet(); set.add(execution); @@ -224,6 +251,7 @@ public class StepExecutionTests extends TestCase { assertTrue(set.contains(execution)); } + @Test public void testSerialization() throws Exception { ExitStatus status = ExitStatus.NOOP; @@ -236,6 +264,16 @@ public class StepExecutionTests extends TestCase { assertEquals(execution, deserialized); assertEquals(status, deserialized.getExitStatus()); } + + @Test + public void testAddException() throws Exception{ + + RuntimeException exception = new RuntimeException(); + assertEquals(0, execution.getFailureExceptions().size()); + execution.addFailureException(exception); + assertEquals(1, execution.getFailureExceptions().size()); + assertEquals(exception, execution.getFailureExceptions().get(0)); + } private StepExecution newStepExecution(Step step, Long long2) { JobInstance job = new JobInstance(new Long(3), new JobParameters(), "testJob"); 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 99174322d..00a6856fe 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 @@ -20,7 +20,6 @@ import java.util.Collections; import junit.framework.TestCase; import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionException; import org.springframework.batch.core.Step; import org.springframework.batch.core.step.StepSupport; @@ -31,7 +30,7 @@ import org.springframework.batch.core.step.StepSupport; public class AbstractJobTests extends TestCase { AbstractJob job = new AbstractJob("job") { - public void execute(JobExecution execution) throws JobExecutionException { + public void execute(JobExecution execution) { throw new UnsupportedOperationException(); } }; @@ -41,7 +40,7 @@ public class AbstractJobTests extends TestCase { */ public void testGetName() { job = new AbstractJob(){ - public void execute(JobExecution execution) throws JobExecutionException { + public void execute(JobExecution execution) { // No-op } }; @@ -61,7 +60,7 @@ public class AbstractJobTests extends TestCase { */ public void testSetBeanNameWithNullName() { job = new AbstractJob(null) { - public void execute(JobExecution execution) throws JobExecutionException { + public void execute(JobExecution execution) { // NO-OP } }; @@ -103,7 +102,7 @@ public class AbstractJobTests extends TestCase { public void testAfterPropertiesSet() throws Exception { AbstractJob job = new AbstractJob() { - public void execute(JobExecution execution) throws JobExecutionException { + public void execute(JobExecution execution) { } }; job.setJobRepository(null); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobFailureTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobFailureTests.java new file mode 100644 index 000000000..6d24492f7 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobFailureTests.java @@ -0,0 +1,19 @@ +package org.springframework.batch.core.job; + +import org.junit.Test; + +/** + * Test suite for various failure scenarios during job processing. + * + * @author Lucas Ward + * + */ +public class SimpleJobFailureTests { + + + @Test + public void testStepFailure(){ + + + } +} 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 f7f7255a6..63af89891 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 @@ -16,7 +16,9 @@ package org.springframework.batch.core.job; -import static org.easymock.EasyMock.*; +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; import java.io.Serializable; import java.util.ArrayList; @@ -32,7 +34,6 @@ import org.springframework.batch.core.JobExecutionListener; import org.springframework.batch.core.JobInstance; import org.springframework.batch.core.JobInterruptedException; import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StartLimitExceededException; import org.springframework.batch.core.Step; import org.springframework.batch.core.StepExecution; import org.springframework.batch.core.UnexpectedJobExecutionException; @@ -217,13 +218,9 @@ public class SimpleJobTests extends TestCase { step2.setStartLimit(5); final JobInterruptedException exception = new JobInterruptedException("Interrupt!"); step1.setProcessException(exception); - try { - job.execute(jobExecution); - fail(); - } - catch (UnexpectedJobExecutionException e) { - assertEquals(exception, e.getCause()); - } + job.execute(jobExecution); + assertEquals(1, jobExecution.getAllFailureExceptions().size()); + assertEquals(exception, jobExecution.getAllFailureExceptions().get(0)); assertEquals(0, list.size()); checkRepository(BatchStatus.STOPPED, ExitStatus.FAILED); } @@ -233,13 +230,10 @@ public class SimpleJobTests extends TestCase { step2.setStartLimit(5); final RuntimeException exception = new RuntimeException("Foo!"); step1.setProcessException(exception); - try { - job.execute(jobExecution); - fail(); - } - catch (RuntimeException e) { - assertEquals(exception, e); - } + + job.execute(jobExecution); + assertEquals(1, jobExecution.getAllFailureExceptions().size()); + assertEquals(exception, jobExecution.getAllFailureExceptions().get(0)); assertEquals(0, list.size()); checkRepository(BatchStatus.FAILED, ExitStatus.FAILED); } @@ -253,15 +247,10 @@ public class SimpleJobTests extends TestCase { final RuntimeException exception = new RuntimeException("Foo!"); step1.setProcessException(exception); - try { - job.execute(jobExecution); - fail(); - } - catch (RuntimeException e) { - assertEquals(exception, e); - } + job.execute(jobExecution); + assertEquals(1, jobExecution.getAllFailureExceptions().size()); + assertEquals(exception, jobExecution.getAllFailureExceptions().get(0)); assertEquals(1, list.size()); - assertSame(exception, list.get(0)); checkRepository(BatchStatus.FAILED, ExitStatus.FAILED); } @@ -270,13 +259,10 @@ public class SimpleJobTests extends TestCase { step2.setStartLimit(5); final Error exception = new Error("Foo!"); step1.setProcessException(exception); - try { - job.execute(jobExecution); - fail(); - } - catch (Error e) { - assertEquals(exception, e); - } + + job.execute(jobExecution); + assertEquals(1, jobExecution.getAllFailureExceptions().size()); + assertEquals(exception, jobExecution.getAllFailureExceptions().get(0)); assertEquals(0, list.size()); checkRepository(BatchStatus.FAILED, ExitStatus.FAILED); } @@ -285,15 +271,12 @@ public class SimpleJobTests extends TestCase { // Start policy will return false, keeping the step from being started. step1.setStartLimit(0); - try { - job.execute(jobExecution); - fail("Expected BatchCriticalException"); - } - catch (StartLimitExceededException ex) { - // expected - assertTrue("Wrong message in exception: " + ex.getMessage(), ex.getMessage() + job.execute(jobExecution); + + assertEquals(1, jobExecution.getFailureExceptions().size()); + Throwable ex = jobExecution.getFailureExceptions().get(0); + assertTrue("Wrong message in exception: " + ex.getMessage(), ex.getMessage() .indexOf("start limit exceeded") >= 0); - } } public void testNoSteps() throws Exception { @@ -320,13 +303,10 @@ public class SimpleJobTests extends TestCase { public void testNotExecutedIfAlreadyStopped() throws Exception { jobExecution.stop(); - try { - job.execute(jobExecution); - fail(); - } - catch (UnexpectedJobExecutionException e) { - assertTrue(e.getCause() instanceof JobInterruptedException); - } + job.execute(jobExecution); + + assertEquals(1, jobExecution.getFailureExceptions().size()); + assertTrue(jobExecution.getFailureExceptions().get(0) instanceof JobInterruptedException); assertEquals(0, list.size()); checkRepository(BatchStatus.STOPPED, ExitStatus.NOOP); ExitStatus exitStatus = jobExecution.getExitStatus(); @@ -338,24 +318,15 @@ public class SimpleJobTests extends TestCase { final RuntimeException exception = new RuntimeException("Foo!"); step2.setProcessException(exception); - try { - job.execute(jobExecution); - fail(); - } - catch (RuntimeException e) { - assertSame(exception, e); - } + job.execute(jobExecution); + Throwable e = jobExecution.getAllFailureExceptions().get(0); + assertSame(exception, e); - try { - job.execute(jobExecution); - fail(); - } - catch (RuntimeException e) { - assertSame(exception, e); - } + job.execute(jobExecution); + e = jobExecution.getAllFailureExceptions().get(0); + assertSame(exception, e); assertTrue(step1.passedInStepContext.isEmpty()); assertFalse(step2.passedInStepContext.isEmpty()); - } public void testInterruptWithListener() throws Exception { @@ -368,13 +339,8 @@ public class SimpleJobTests extends TestCase { job.setJobExecutionListeners(new JobExecutionListener[] { listener }); - try { - job.execute(jobExecution); - fail(); - } - catch (UnexpectedJobExecutionException e) { - // expected - } + job.execute(jobExecution); + assertEquals(BatchStatus.STOPPED, jobExecution.getStatus()); verify(listener); } @@ -390,13 +356,10 @@ public class SimpleJobTests extends TestCase { final RuntimeException exception = new RuntimeException("Foo!"); step2.setProcessException(exception); - try { - job.execute(jobExecution); - fail(); - } - catch (RuntimeException e) { - assertSame(exception, e); - } + job.execute(jobExecution); + assertEquals(1, jobExecution.getAllFailureExceptions().size()); + Throwable e = jobExecution.getAllFailureExceptions().get(0); + assertSame(exception, e); assertTrue(step1.passedInJobContext.isEmpty()); assertFalse(step2.passedInJobContext.isEmpty()); @@ -405,13 +368,10 @@ public class SimpleJobTests extends TestCase { jobExecution = jobRepository.createJobExecution(job.getName(), jobParameters); - try { - job.execute(jobExecution); - fail(); - } - catch (RuntimeException e) { - assertSame(exception, e); - } + job.execute(jobExecution); + assertEquals(1, jobExecution.getAllFailureExceptions().size()); + e = jobExecution.getAllFailureExceptions().get(0); + assertSame(exception, e); assertFalse(step1.passedInJobContext.isEmpty()); assertFalse(step2.passedInJobContext.isEmpty()); } @@ -428,16 +388,11 @@ public class SimpleJobTests extends TestCase { }; job.setSteps(Arrays.asList(new Step[] { step1, step2 })); - - try { - job.execute(jobExecution); - fail(); - } - catch (UnexpectedJobExecutionException expected) { - assertTrue(expected.getCause() instanceof JobInterruptedException); - assertEquals("JobExecution interrupted.", expected.getCause().getMessage()); - } - + job.execute(jobExecution); + Throwable expected = jobExecution.getAllFailureExceptions().get(0); + assertTrue(expected instanceof JobInterruptedException); + assertEquals("JobExecution interrupted.", expected.getMessage()); + assertNull("Second step was not executed", step2.passedInStepContext); } @@ -509,17 +464,20 @@ public class SimpleJobTests extends TestCase { if (exception instanceof RuntimeException) { stepExecution.setExitStatus(ExitStatus.FAILED); stepExecution.setStatus(BatchStatus.FAILED); - throw (RuntimeException) exception; + stepExecution.addFailureException(exception); + return; } if (exception instanceof Error) { stepExecution.setExitStatus(ExitStatus.FAILED); stepExecution.setStatus(BatchStatus.FAILED); - throw (Error) exception; + stepExecution.addFailureException(exception); + return; } if (exception instanceof JobInterruptedException) { stepExecution.setExitStatus(ExitStatus.FAILED); - stepExecution.setStatus(BatchStatus.FAILED); - throw (JobInterruptedException) exception; + stepExecution.setStatus(BatchStatus.STOPPED); + stepExecution.addFailureException(exception); + return; } if (runnable != null) { runnable.run(); 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 3da6e237a..f7b090401 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 @@ -151,14 +151,9 @@ public class SimpleStepFactoryBeanTests { job.setSteps(Collections.singletonList(step)); JobExecution jobExecution = repository.createJobExecution(job.getName(), new JobParameters()); - try { - job.execute(jobExecution); - fail("Expected RuntimeException"); - } - catch (RuntimeException e) { - // expected - assertEquals("Error!", e.getMessage()); - } + + job.execute(jobExecution); + assertEquals("Error!", jobExecution.getAllFailureExceptions().get(0).getMessage()); assertEquals(BatchStatus.FAILED, jobExecution.getStatus()); assertEquals(0, written.size()); @@ -180,14 +175,9 @@ public class SimpleStepFactoryBeanTests { job.setSteps(Collections.singletonList((Step) step)); JobExecution jobExecution = repository.createJobExecution(job.getName(), new JobParameters()); - try { - job.execute(jobExecution); - fail("Expected RuntimeException"); - } - catch (RuntimeException e) { - assertEquals("Foo", e.getMessage()); - // expected - } + + job.execute(jobExecution); + assertEquals("Foo", jobExecution.getAllFailureExceptions().get(0).getMessage()); assertEquals(BatchStatus.FAILED, jobExecution.getStatus()); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SkipLimitStepFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SkipLimitStepFactoryBeanTests.java index 145ad4c66..12e50864c 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SkipLimitStepFactoryBeanTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SkipLimitStepFactoryBeanTests.java @@ -3,7 +3,6 @@ package org.springframework.batch.core.step.item; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.Arrays; @@ -16,6 +15,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; 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.JobInstance; import org.springframework.batch.core.JobParameters; @@ -24,8 +24,6 @@ import org.springframework.batch.core.StepExecution; import org.springframework.batch.core.StepListener; import org.springframework.batch.core.listener.SkipListenerSupport; import org.springframework.batch.core.step.JobRepositorySupport; -import org.springframework.batch.core.step.skip.SkipLimitExceededException; -import org.springframework.batch.core.step.skip.SkipListenerFailedException; import org.springframework.batch.item.ItemProcessor; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.ItemWriter; @@ -148,13 +146,8 @@ public class SkipLimitStepFactoryBeanTests { Step step = (Step) factory.getObject(); StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); - try { - step.execute(stepExecution); - fail(); - } - catch (FatalRuntimeException expected) { - assertTrue(expected.getMessage().equals("Ouch!")); - } + step.execute(stepExecution); + assertTrue(stepExecution.getFailureExceptions().get(0).getMessage().equals("Ouch!")); } /** @@ -169,12 +162,7 @@ public class SkipLimitStepFactoryBeanTests { StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); - try { - step.execute(stepExecution); - fail("Expected SkipLimitExceededException."); - } - catch (SkipLimitExceededException e) { - } + step.execute(stepExecution); assertEquals(1, stepExecution.getSkipCount()); @@ -205,13 +193,9 @@ public class SkipLimitStepFactoryBeanTests { StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); - try { - step.execute(stepExecution); - fail("Expected SkipLimitExceededException."); - } - catch (SkipLimitExceededException e) { - } - + step.execute(stepExecution); + assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); + assertEquals(3, stepExecution.getSkipCount()); assertEquals(2, stepExecution.getReadSkipCount()); assertEquals(1, stepExecution.getWriteSkipCount()); @@ -249,13 +233,9 @@ public class SkipLimitStepFactoryBeanTests { StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); - try { - step.execute(stepExecution); - fail("Expected SkipListenerFailedException."); - } - catch (SkipListenerFailedException e) { - assertEquals("oops", e.getCause().getMessage()); - } + step.execute(stepExecution); + assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); + assertEquals("oops", stepExecution.getFailureExceptions().get(0).getCause().getMessage()); assertEquals(1, stepExecution.getSkipCount()); assertEquals(1, stepExecution.getReadSkipCount()); @@ -286,14 +266,9 @@ public class SkipLimitStepFactoryBeanTests { StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); - try { - step.execute(stepExecution); - fail("Expected SkipListenerFailedException."); - } - catch (SkipListenerFailedException e) { - assertEquals("oops", e.getCause().getMessage()); - } - + step.execute(stepExecution); + assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); + assertEquals("oops", stepExecution.getFailureExceptions().get(0).getCause().getMessage()); assertEquals(3, stepExecution.getSkipCount()); assertEquals(2, stepExecution.getReadSkipCount()); assertEquals(1, stepExecution.getWriteSkipCount()); @@ -406,13 +381,8 @@ public class SkipLimitStepFactoryBeanTests { StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); - try { - step.execute(stepExecution); - fail("Expected SkipLimitExceededException."); - } - catch (SkipLimitExceededException e) { - } - + step.execute(stepExecution); + assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); assertEquals("bad skip count", 3, stepExecution.getSkipCount()); assertEquals("bad read skip count", 2, stepExecution.getReadSkipCount()); assertEquals("bad write skip count", 1, stepExecution.getWriteSkipCount()); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/StatefulRetryStepFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/StatefulRetryStepFactoryBeanTests.java index 089a423ff..0cad5196b 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/StatefulRetryStepFactoryBeanTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/StatefulRetryStepFactoryBeanTests.java @@ -29,6 +29,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; 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.JobParameters; import org.springframework.batch.core.JobParametersBuilder; @@ -361,14 +362,9 @@ public class StatefulRetryStepFactoryBeanTests { Step step = (Step) factory.getObject(); StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); - try { - step.execute(stepExecution); - fail("Expected SkipLimitExceededException"); - } - catch (SkipLimitExceededException e) { - // expected - } - + step.execute(stepExecution); + assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); + List expectedOutput = Arrays.asList(StringUtils.commaDelimitedListToStringArray("")); assertEquals(expectedOutput, written); @@ -418,15 +414,9 @@ public class StatefulRetryStepFactoryBeanTests { Step step = (Step) factory.getObject(); StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); - try { - step.execute(stepExecution); - fail("Expected RuntimeException"); - } - catch (RuntimeException e) { - // expected - String message = e.getMessage(); - assertTrue("Wrong message: " + message, message.contains("Write error - planned but not skippable.")); - } + step.execute(stepExecution); + String message = stepExecution.getFailureExceptions().get(0).getMessage(); + assertTrue("Wrong message: " + message, message.contains("Write error - planned but not skippable.")); List expectedOutput = Arrays.asList(StringUtils.commaDelimitedListToStringArray("")); assertEquals(expectedOutput, written); @@ -466,14 +456,9 @@ public class StatefulRetryStepFactoryBeanTests { AbstractStep step = (AbstractStep) factory.getObject(); StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); - try { - step.execute(stepExecution); - fail("Expected SkipLimitExceededException"); - } - catch (SkipLimitExceededException e) { - // expected - } - + step.execute(stepExecution); + assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); + List expectedOutput = Arrays.asList(StringUtils.commaDelimitedListToStringArray("")); assertEquals(expectedOutput, written); @@ -519,14 +504,9 @@ public class StatefulRetryStepFactoryBeanTests { AbstractStep step = (AbstractStep) factory.getObject(); StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); - try { - step.execute(stepExecution); - fail("Expected RetryCacheCapacityExceededException"); - } - catch (RetryCacheCapacityExceededException e) { - // expected - } - + step.execute(stepExecution); + assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); + // We added a bogus cache so no items are actually skipped // because they aren't recognised as eligible assertEquals(0, stepExecution.getSkipCount()); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/TaskletStepExceptionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/TaskletStepExceptionTests.java new file mode 100644 index 000000000..88678e050 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/TaskletStepExceptionTests.java @@ -0,0 +1,265 @@ +/** + * + */ +package org.springframework.batch.core.step.item; + +import static org.junit.Assert.*; +import static org.springframework.batch.core.BatchStatus.*; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobInstance; +import org.springframework.batch.core.JobInterruptedException; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.listener.StepExecutionListenerSupport; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.repository.JobRestartException; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.core.step.tasklet.TaskletStep; +import org.springframework.batch.item.ExecutionContext; +import org.springframework.batch.item.ItemStream; +import org.springframework.batch.item.ItemStreamException; +import org.springframework.batch.item.ItemStreamSupport; +import org.springframework.batch.repeat.ExitStatus; +import org.springframework.batch.support.transaction.ResourcelessTransactionManager; +import org.springframework.core.AttributeAccessor; +import org.springframework.transaction.TransactionException; +import org.springframework.transaction.support.DefaultTransactionStatus; + +/** + * Tests for the behavior of TaskletStep in a failure scenario. + * + * @author Lucas Ward + * + */ +public class TaskletStepExceptionTests { + + TaskletStep taskletStep; + StepExecution stepExecution; + UpdateCountingJobRepository jobRepository; + static RuntimeException taskletException = new RuntimeException(); + static JobInterruptedException interruptedException = new JobInterruptedException(""); + + @Before + public void init(){ + taskletStep = new TaskletStep(); + taskletStep.setTasklet(new ExceptionTasklet()); + jobRepository = new UpdateCountingJobRepository(); + taskletStep.setJobRepository(jobRepository); + taskletStep.setTransactionManager(new ResourcelessTransactionManager()); + + JobInstance jobInstance = new JobInstance(1L, new JobParameters(), "testJob" ); + JobExecution jobExecution = new JobExecution(jobInstance); + stepExecution = new StepExecution("testStep", jobExecution); + } + + @Test + public void testApplicationException() throws Exception{ + + taskletStep.execute(stepExecution); + assertEquals(FAILED, stepExecution.getStatus()); + } + + @Test + public void testInterrupted() throws Exception{ + taskletStep.setStepExecutionListeners(new StepExecutionListener[]{new InterruptionListener()}); + taskletStep.execute(stepExecution); + assertEquals(STOPPED, stepExecution.getStatus()); + } + + @Test + public void testOpenFailure() throws Exception{ + final RuntimeException exception = new RuntimeException(); + taskletStep.setStreams(new ItemStream[]{new ItemStreamSupport(){ + @Override + public void open(ExecutionContext executionContext) + throws ItemStreamException { + throw exception; + } + }}); + + taskletStep.execute(stepExecution); + assertEquals(FAILED, stepExecution.getStatus()); + assertTrue(stepExecution.getFailureExceptions().contains(exception)); + assertEquals(1,jobRepository.getUpdateCount()); + } + + @Test + public void testBeforeStepFailure() throws Exception{ + + final RuntimeException exception = new RuntimeException(); + taskletStep.setStepExecutionListeners(new StepExecutionListenerSupport[]{new StepExecutionListenerSupport(){ + @Override + public void beforeStep(StepExecution stepExecution) { + throw exception; + } + }}); + taskletStep.execute(stepExecution); + assertEquals(FAILED, stepExecution.getStatus()); + assertTrue(stepExecution.getFailureExceptions().contains(exception)); + assertEquals(1,jobRepository.getUpdateCount()); + } + + @Test + public void testAfterStepFailure() throws Exception{ + + final RuntimeException exception = new RuntimeException(); + taskletStep.setStepExecutionListeners(new StepExecutionListenerSupport[]{new StepExecutionListenerSupport(){ + @Override + public ExitStatus afterStep(StepExecution stepExecution) { + throw exception; + } + }}); + taskletStep.setTasklet(new Tasklet(){ + + public ExitStatus execute(StepContribution contribution, + AttributeAccessor attributes) throws Exception { + return ExitStatus.FINISHED; + } + + }); + taskletStep.execute(stepExecution); + assertEquals(FAILED, stepExecution.getStatus()); + assertTrue(stepExecution.getFailureExceptions().contains(exception)); + assertEquals(2,jobRepository.getUpdateCount()); + } + + @Test + public void testOnErrorInStepFAilure() throws Exception{ + + final RuntimeException exception = new RuntimeException(); + taskletStep.setStepExecutionListeners(new StepExecutionListenerSupport[]{new StepExecutionListenerSupport(){ + @Override + public ExitStatus onErrorInStep(StepExecution stepExecution, + Throwable e) { + throw exception; + } + }}); + taskletStep.execute(stepExecution); + assertEquals(FAILED, stepExecution.getStatus()); + assertTrue(stepExecution.getFailureExceptions().contains(taskletException)); + assertTrue(stepExecution.getFailureExceptions().contains(exception)); + assertEquals(1,jobRepository.getUpdateCount()); + } + + @Test + public void testCloseError() throws Exception{ + + final RuntimeException exception = new RuntimeException(); + taskletStep.setStreams(new ItemStream[]{new ItemStreamSupport(){ + @Override + public void close(ExecutionContext executionContext) + throws ItemStreamException { + throw exception; + } + }}); + + taskletStep.execute(stepExecution); + assertEquals(FAILED, stepExecution.getStatus()); + assertTrue(stepExecution.getFailureExceptions().contains(taskletException)); + assertTrue(stepExecution.getFailureExceptions().contains(exception)); + assertEquals(1,jobRepository.getUpdateCount()); + } + + @Test + public void testCommitError() throws Exception{ + + final RuntimeException exception = new RuntimeException(); + taskletStep.setTransactionManager(new ResourcelessTransactionManager(){ + @Override + protected void doCommit(DefaultTransactionStatus status) + throws TransactionException { + throw exception; + } + }); + + taskletStep.setTasklet(new Tasklet(){ + + public ExitStatus execute(StepContribution contribution, + AttributeAccessor attributes) throws Exception { + return ExitStatus.FINISHED; + } + + }); + + taskletStep.execute(stepExecution); + assertEquals(UNKNOWN, stepExecution.getStatus()); + Throwable e = stepExecution.getFailureExceptions().get(0); + assertEquals(exception, e.getCause()); + } + + @Test + public void testUpdateError() throws Exception{ + + final RuntimeException exception = new RuntimeException(); + taskletStep.setJobRepository(new UpdateCountingJobRepository(){ + @Override + public void update(StepExecution arg0) { + throw exception; + } + }); + + taskletStep.execute(stepExecution); + assertEquals(UNKNOWN, stepExecution.getStatus()); + assertTrue(stepExecution.getFailureExceptions().contains(taskletException)); + assertTrue(stepExecution.getFailureExceptions().contains(exception)); + } + + private static class ExceptionTasklet implements Tasklet{ + + public ExitStatus execute(StepContribution contribution, + AttributeAccessor attributes) throws Exception { + + throw taskletException; + } + } + + private static class InterruptionListener extends StepExecutionListenerSupport{ + + @Override + public void beforeStep(StepExecution stepExecution) { + stepExecution.setTerminateOnly(); + } + } + + private static class UpdateCountingJobRepository implements JobRepository{ + + private int updateCount = 0; + + public void add(StepExecution stepExecution) { } + + public JobExecution createJobExecution(String jobName, + JobParameters jobParameters) + throws JobExecutionAlreadyRunningException, + JobRestartException, JobInstanceAlreadyCompleteException { + return null;} + + public StepExecution getLastStepExecution(JobInstance jobInstance, + String stepName) {return null;} + + public int getStepExecutionCount(JobInstance jobInstance, + String stepName) {return 0; } + + public boolean isJobInstanceExists(String jobName, + JobParameters jobParameters) {return false;} + + public void update(JobExecution jobExecution) { } + + public void update(StepExecution stepExecution) { + updateCount++; + } + + public void updateExecutionContext(StepExecution stepExecution) { } + + public int getUpdateCount() { + return updateCount; + } + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/ChunkOrientedStepIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/ChunkOrientedStepIntegrationTests.java index b4047a9b6..b24e75f8a 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/ChunkOrientedStepIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/ChunkOrientedStepIntegrationTests.java @@ -18,7 +18,6 @@ package org.springframework.batch.core.step.tasklet; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import java.util.Arrays; import java.util.List; @@ -39,7 +38,6 @@ import org.springframework.batch.core.repository.dao.MapJobExecutionDao; import org.springframework.batch.core.repository.dao.MapJobInstanceDao; import org.springframework.batch.core.repository.dao.MapStepExecutionDao; import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; -import org.springframework.batch.core.step.tasklet.TaskletStep; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.ItemWriter; @@ -134,27 +132,20 @@ public class ChunkOrientedStepIntegrationTests { }); // step.setLastExecution(stepExecution); - try { - step.execute(stepExecution); - fail("Expected BatchCriticalException"); - } - catch (RuntimeException e) { + step.execute(stepExecution); + assertEquals(BatchStatus.UNKNOWN, stepExecution.getStatus()); + StepExecution lastStepExecution = jobRepository.getLastStepExecution(jobExecution.getJobInstance(), step.getName()); + assertEquals(lastStepExecution, stepExecution); + assertFalse(lastStepExecution == stepExecution); - assertEquals(BatchStatus.UNKNOWN, stepExecution.getStatus()); - StepExecution lastStepExecution = jobRepository.getLastStepExecution(jobExecution.getJobInstance(), step.getName()); - assertEquals(lastStepExecution, stepExecution); - assertFalse(lastStepExecution == stepExecution); + // If the StepExecution is not saved after the failure it will be + // STARTED instead of UNKNOWN + assertEquals(BatchStatus.UNKNOWN, lastStepExecution.getStatus()); + String msg = stepExecution.getExitStatus().getExitDescription(); + assertTrue(msg.contains("Fatal error detected during commit")); + // The original rollback was caused by this one: + assertEquals("Simulate commit failure", stepExecution.getFailureExceptions().get(0).getCause().getMessage()); - // If the StepExecution is not saved after the failure it will be - // STARTED instead of UNKNOWN - assertEquals(BatchStatus.UNKNOWN, lastStepExecution.getStatus()); - - String msg = stepExecution.getExitStatus().getExitDescription(); - assertTrue(msg.contains("Fatal error detected during commit")); - // The original rollback was caused by this one: - assertEquals("Simulate commit failure", e.getCause().getMessage()); - - } } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/StepExecutorInterruptionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/StepExecutorInterruptionTests.java index a00227ad5..a7f8c2f65 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/StepExecutorInterruptionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/StepExecutorInterruptionTests.java @@ -169,15 +169,10 @@ public class StepExecutorInterruptionTests extends TestCase { } }); - try { - step.execute(stepExecution); - fail("Expected planned RuntimeException"); - } catch (RuntimeException e) { - assertEquals("Planned!", e.getMessage()); - } - + step.execute(stepExecution); + + assertEquals("Planned!", stepExecution.getFailureExceptions().get(0).getMessage()); assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); - } /** diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/TaskletStepTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/TaskletStepTests.java index b42a7f6ba..d7c37c42e 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/TaskletStepTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/TaskletStepTests.java @@ -36,7 +36,6 @@ import org.springframework.batch.core.JobInterruptedException; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.StepExecution; import org.springframework.batch.core.StepExecutionListener; -import org.springframework.batch.core.UnexpectedJobExecutionException; import org.springframework.batch.core.job.JobSupport; import org.springframework.batch.core.listener.StepExecutionListenerSupport; import org.springframework.batch.core.repository.JobRepository; @@ -172,16 +171,8 @@ public class TaskletStepTests { step.setJobRepository(repository); step.afterPropertiesSet(); - try { - step.execute(stepExecution); - fail(); - } - catch (Exception e) { - assertEquals(BatchStatus.UNKNOWN, stepExecution.getStatus()); - assertEquals("Fatal error detected during update of step execution", e.getMessage()); - assertEquals("stub exception", e.getCause().getMessage()); - } - + step.execute(stepExecution); + assertEquals(BatchStatus.UNKNOWN, stepExecution.getStatus()); } @Test @@ -328,14 +319,11 @@ public class TaskletStepTests { counter++; } }); - try { - step.execute(stepExecution); - fail(); - } - catch (RuntimeException e) { - assertEquals("Fatal error detected during save of step execution context", e.getMessage()); - assertEquals("foo", e.getCause().getMessage()); - } + + step.execute(stepExecution); + Throwable e = stepExecution.getFailureExceptions().get(0); + assertEquals("Fatal error detected during save of step execution context", e.getMessage()); + assertEquals("foo", e.getCause().getMessage()); assertEquals(BatchStatus.UNKNOWN, stepExecution.getStatus()); } @@ -497,13 +485,8 @@ public class TaskletStepTests { }, itemWriter)); JobExecution jobExecution = new JobExecution(jobInstance); StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); - try { - step.execute(stepExecution); - fail("Expected RuntimeException"); - } - catch (RuntimeException e) { - assertEquals("FOO", e.getMessage()); - } + step.execute(stepExecution); + assertEquals("FOO", stepExecution.getFailureExceptions().get(0).getMessage()); assertEquals(1, list.size()); } @@ -533,7 +516,7 @@ public class TaskletStepTests { } @Test - public void testStatusForInterruptedException() { + public void testStatusForInterruptedException() throws Exception{ StepInterruptionPolicy interruptionPolicy = new StepInterruptionPolicy() { @@ -560,16 +543,11 @@ public class TaskletStepTests { stepExecution.setExecutionContext(foobarEc); - try { - step.execute(stepExecution); - fail("Expected JobInterruptedException"); - } - catch (JobInterruptedException ex) { - assertEquals(BatchStatus.STOPPED, stepExecution.getStatus()); - String msg = stepExecution.getExitStatus().getExitDescription(); - assertTrue("Message does not contain 'JobInterruptedException': " + msg, contains(msg, + step.execute(stepExecution); + assertEquals(BatchStatus.STOPPED, stepExecution.getStatus()); + String msg = stepExecution.getExitStatus().getExitDescription(); + assertTrue("Message does not contain 'JobInterruptedException': " + msg, contains(msg, "JobInterruptedException")); - } } @Test @@ -589,15 +567,10 @@ public class TaskletStepTests { stepExecution.setExecutionContext(foobarEc); // step.setLastExecution(stepExecution); - try { - step.execute(stepExecution); - fail("Expected RuntimeException"); - } - catch (RuntimeException ex) { - assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); - // The original rollback was caused by this one: - assertEquals("Foo", ex.getMessage()); - } + step.execute(stepExecution); + assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); + // The original rollback was caused by this one: + assertEquals("Foo", stepExecution.getFailureExceptions().get(0).getMessage()); } @Test @@ -617,15 +590,10 @@ public class TaskletStepTests { stepExecution.setExecutionContext(foobarEc); // step.setLastExecution(stepExecution); - try { - step.execute(stepExecution); - fail("Expected Error"); - } - catch (Error ex) { - assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); - // The original rollback was caused by this one: - assertEquals("Foo", ex.getMessage()); - } + step.execute(stepExecution); + assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); + // The original rollback was caused by this one: + assertEquals("Foo", stepExecution.getFailureExceptions().get(0).getMessage()); } @Test @@ -651,17 +619,12 @@ public class TaskletStepTests { stepExecution.setExecutionContext(foobarEc); // step.setLastExecution(stepExecution); - try { - step.execute(stepExecution); - fail("Expected UnexpectedJobExecutionException"); - } - catch (RuntimeException ex) { - assertEquals(BatchStatus.UNKNOWN, stepExecution.getStatus()); - String msg = stepExecution.getExitStatus().getExitDescription(); - assertTrue("Message does not contain ResetFailedException: " + msg, contains(msg, "ResetFailedException")); - // The original rollback was caused by this one: - assertEquals("Bar", ex.getCause().getMessage()); - } + step.execute(stepExecution); + assertEquals(BatchStatus.UNKNOWN, stepExecution.getStatus()); + String msg = stepExecution.getExitStatus().getExitDescription(); + assertTrue("Message does not contain ResetFailedException: " + msg, contains(msg, "ResetFailedException")); + // The original rollback was caused by this one: + assertEquals("Bar", stepExecution.getFailureExceptions().get(0).getCause().getMessage()); } @Test @@ -680,19 +643,15 @@ public class TaskletStepTests { stepExecution.setExecutionContext(foobarEc); // step.setLastExecution(stepExecution); - try { - step.execute(stepExecution); - fail("Expected BatchCriticalException"); - } - catch (RuntimeException ex) { - assertEquals(BatchStatus.UNKNOWN, stepExecution.getStatus()); - String msg = stepExecution.getExitStatus().getExitDescription(); - assertTrue(msg.contains("Fatal error detected during commit")); - msg = ex.getMessage(); - assertTrue(msg.contains("Fatal error detected during commit")); - // The original rollback was caused by this one: - assertEquals("Bar", ex.getCause().getMessage()); - } + step.execute(stepExecution); + assertEquals(BatchStatus.UNKNOWN, stepExecution.getStatus()); + String msg = stepExecution.getExitStatus().getExitDescription(); + assertTrue(msg.contains("Fatal error detected during commit")); + Throwable ex = stepExecution.getFailureExceptions().get(0); + msg = ex.getMessage(); + assertTrue(msg.contains("Fatal error detected during commit")); + // The original rollback was caused by this one: + assertEquals("Bar", ex.getCause().getMessage()); } @Test @@ -708,20 +667,15 @@ public class TaskletStepTests { JobExecution jobExecutionContext = new JobExecution(jobInstance); StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); - try { - step.execute(stepExecution); - fail("Expected RuntimeException"); - } - catch (RuntimeException ex) { - // The job actually completed, but the streams couldn't be closed. - assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus()); - String msg = stepExecution.getExitStatus().getExitDescription(); - assertEquals("", msg); - msg = ex.getMessage(); - assertTrue("Message does not contain 'closing step': " + msg, contains(msg, "closing step")); - // The original rollback was caused by this one: - assertEquals("Bar", ex.getCause().getMessage()); - } + step.execute(stepExecution); + // The job actually completed, but the streams couldn't be closed. + assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus()); + String msg = stepExecution.getExitStatus().getExitDescription(); + assertEquals("", msg); + Throwable ex = stepExecution.getFailureExceptions().get(0); + msg = ex.getMessage(); + // The original rollback was caused by this one: + assertEquals("Bar", ex.getMessage()); } @Test @@ -743,20 +697,15 @@ public class TaskletStepTests { stepExecution.setExecutionContext(foobarEc); // step.setLastExecution(stepExecution); - try { - step.execute(stepExecution); - fail("Expected InfrastructureException"); - } - catch (UnexpectedJobExecutionException ex) { + step.execute(stepExecution); // The job actually completed, but the streams couldn't be closed. - assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus()); - String msg = stepExecution.getExitStatus().getExitDescription(); - assertEquals("", msg); - msg = ex.getMessage(); - assertTrue("Message does not contain 'closing': " + msg, contains(msg, "closing")); - // The original rollback was caused by this one: - assertEquals("Bar", ex.getCause().getMessage()); - } + assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus()); + String msg = stepExecution.getExitStatus().getExitDescription(); + assertEquals("", msg); + Throwable ex = stepExecution.getFailureExceptions().get(0); + msg = ex.getMessage(); + // The original rollback was caused by this one: + assertEquals("Bar", ex.getMessage()); } /** @@ -777,16 +726,12 @@ public class TaskletStepTests { StepExecution stepExecution = new StepExecution(step.getName(), new JobExecution(jobInstance)); - try { - step.execute(stepExecution); - fail("Expected InfrastructureException"); - } - catch (RuntimeException expected) { - assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); - assertEquals("CRASH!", expected.getMessage()); - assertFalse(stepExecution.getExecutionContext().isEmpty()); - assertTrue(stepExecution.getExecutionContext().getString("spam").equals("bucket")); - } + step.execute(stepExecution); + assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); + Throwable expected = stepExecution.getFailureExceptions().get(0); + assertEquals("CRASH!", expected.getMessage()); + assertFalse(stepExecution.getExecutionContext().isEmpty()); + assertTrue(stepExecution.getExecutionContext().getString("spam").equals("bucket")); } @Test @@ -820,14 +765,10 @@ public class TaskletStepTests { }; step.setStepExecutionListeners(new StepExecutionListener[] { listener }); StepExecution stepExecution = new StepExecution(step.getName(), new JobExecution(jobInstance)); - try { - step.execute(stepExecution); - fail(); - } - catch (RuntimeException expected) { - assertEquals("exception thrown in afterStep to signal failure", expected.getMessage()); - } + step.execute(stepExecution); + Throwable expected = stepExecution.getFailureExceptions().get(0); + assertEquals("exception thrown in afterStep to signal failure", expected.getMessage()); assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); }