diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/SimpleJobRepository.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/SimpleJobRepository.java index 41f534994..a9ad50d6c 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/SimpleJobRepository.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/SimpleJobRepository.java @@ -250,7 +250,7 @@ public class SimpleJobRepository implements JobRepository { * @see org.springframework.batch.core.repository.JobRepository#saveOrUpdateExecutionContext(org.springframework.batch.core.domain.StepExecution) */ public void saveOrUpdateExecutionContext(StepExecution stepExecution) { - saveOrUpdate(stepExecution); + // Until there is an interface change ( stepExecutionDao.saveOrUpdateExecutionContext(stepExecution); jobExecutionDao.saveOrUpdateExecutionContext(stepExecution.getJobExecution()); } 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 23a54beda..fbf81ecff 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 @@ -171,6 +171,7 @@ public abstract class AbstractStep implements Step, InitializingBean, BeanNameAw exitStatus = exitStatus.and(getCompositeListener().afterStep(stepExecution)); try { + getJobRepository().saveOrUpdate(stepExecution); getJobRepository().saveOrUpdateExecutionContext(stepExecution); } catch (Exception e) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ItemOrientedStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ItemOrientedStep.java index 0f5e7591c..d516e1f5c 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ItemOrientedStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ItemOrientedStep.java @@ -211,6 +211,7 @@ public class ItemOrientedStep extends AbstractStep { */ protected ExitStatus doExecute(final StepExecution stepExecution) throws Exception { stream.update(stepExecution.getExecutionContext()); + getJobRepository().saveOrUpdate(stepExecution); getJobRepository().saveOrUpdateExecutionContext(stepExecution); itemHandler.mark(); @@ -362,6 +363,12 @@ public class ItemOrientedStep extends AbstractStep { throw new FatalException("Failed while processing rollback", e); } } + + if (fatalException.hasException()) { + // Try and give the system a chance to update the stepExecution with + // the failure status. + getJobRepository().saveOrUpdate(stepExecution); + } } private static class ExceptionHolder { 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 028a96fb4..e8c5ca4f9 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,7 +19,6 @@ package org.springframework.batch.core.job; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Properties; import junit.framework.TestCase; @@ -45,6 +44,7 @@ import org.springframework.batch.core.repository.dao.MapStepExecutionDao; import org.springframework.batch.core.repository.dao.StepExecutionDao; import org.springframework.batch.core.repository.support.SimpleJobRepository; import org.springframework.batch.core.step.StepSupport; +import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.repeat.ExitStatus; /** @@ -269,7 +269,6 @@ public class SimpleJobTests extends TestCase { catch (Error e) { assertEquals(exception, e); } - System.err.println(list); assertEquals(0, list.size()); checkRepository(BatchStatus.FAILED, ExitStatus.FAILED); } @@ -347,6 +346,7 @@ public class SimpleJobTests extends TestCase { assertSame(exception, e); } assertTrue(step1.passedInStepContext.isEmpty()); + System.err.println(step2.passedInStepContext); assertFalse(step2.passedInStepContext.isEmpty()); } @@ -463,9 +463,9 @@ public class SimpleJobTests extends TestCase { private JobRepository jobRepository; - private Properties passedInStepContext; + private ExecutionContext passedInStepContext; - private Properties passedInJobContext; + private ExecutionContext passedInJobContext; /** * @param string @@ -495,10 +495,11 @@ public class SimpleJobTests extends TestCase { public void execute(StepExecution stepExecution) throws JobInterruptedException, UnexpectedJobExecutionException { - passedInJobContext = stepExecution.getJobExecution().getExecutionContext().getProperties(); - passedInStepContext = stepExecution.getExecutionContext().getProperties(); + passedInJobContext = new ExecutionContext(stepExecution.getJobExecution().getExecutionContext()); + passedInStepContext = new ExecutionContext(stepExecution.getExecutionContext()); stepExecution.getExecutionContext().putString("stepKey", "stepValue"); stepExecution.getJobExecution().getExecutionContext().putString("jobKey", "jobValue"); + jobRepository.saveOrUpdate(stepExecution); jobRepository.saveOrUpdateExecutionContext(stepExecution); if (exception instanceof RuntimeException) { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ItemOrientedStepIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ItemOrientedStepIntegrationTests.java new file mode 100644 index 000000000..5f1b35cd7 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ItemOrientedStepIntegrationTests.java @@ -0,0 +1,166 @@ +/* + * 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.step.item; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.sql.DataSource; + +import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobSupport; +import org.springframework.batch.core.repository.JobRepository; +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.item.AbstractItemWriter; +import org.springframework.batch.item.ExecutionContext; +import org.springframework.batch.item.ItemReader; +import org.springframework.batch.item.support.ListItemReader; +import org.springframework.batch.repeat.policy.SimpleCompletionPolicy; +import org.springframework.batch.repeat.support.RepeatTemplate; +import org.springframework.batch.support.PropertiesConverter; +import org.springframework.test.AbstractDependencyInjectionSpringContextTests; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.support.TransactionSynchronizationAdapter; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +/** + * @author Dave Syer + * + */ +public class ItemOrientedStepIntegrationTests extends AbstractDependencyInjectionSpringContextTests { + + private List processed = new ArrayList(); + + private ItemOrientedStep step; + + private Job job; + + private PlatformTransactionManager transactionManager; + + private DataSource dataSource; + + private JobRepository jobRepository; + + /** + * Public setter for the PlatformTransactionManager. + * @param transactionManager the transactionManager to set + */ + public void setTransactionManager(PlatformTransactionManager transactionManager) { + this.transactionManager = transactionManager; + } + + /** + * Public setter for the DataSource. + * @param dataSource the dataSource to set + */ + public void setDataSource(DataSource dataSource) { + this.dataSource = dataSource; + } + + private ItemReader getReader(String[] args) { + return new ListItemReader(Arrays.asList(args)); + } + + protected void onSetUp() throws Exception { + MapJobInstanceDao.clear(); + MapStepExecutionDao.clear(); + MapJobExecutionDao.clear(); + + JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean(); + jobRepositoryFactoryBean.setDatabaseType("hsql"); + jobRepositoryFactoryBean.setDataSource(dataSource); + jobRepositoryFactoryBean.setTransactionManager(transactionManager); + jobRepositoryFactoryBean.afterPropertiesSet(); + jobRepository = (JobRepository) jobRepositoryFactoryBean.getObject(); + RepeatTemplate template; + + step = new ItemOrientedStep("stepName"); + step.setJobRepository(jobRepository); + step.setTransactionManager(transactionManager); + template = new RepeatTemplate(); + template.setCompletionPolicy(new SimpleCompletionPolicy(1)); + step.setStepOperations(template); + + // Only process one item: + template = new RepeatTemplate(); + template.setCompletionPolicy(new SimpleCompletionPolicy(1)); + step.setChunkOperations(template); + + job = new JobSupport("FOO"); + + step.setTransactionManager(transactionManager); + + } + + /* + * (non-Javadoc) + * @see org.springframework.test.AbstractSingleSpringContextTests#getConfigLocations() + */ + protected String[] getConfigLocations() { + return new String[] { "sql-dao-test.xml" }; + } + + public void testStatusForCommitFailedException() throws Exception { + + step.setItemHandler(new SimpleItemHandler(getReader(new String[] { "a", "b", "c" }), new AbstractItemWriter() { + public void write(Object data) throws Exception { + processed.add((String) data); + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { + public void beforeCommit(boolean readOnly) { + throw new RuntimeException("Simulate commit failure"); + } + }); + } + })); + + JobExecution jobExecution = jobRepository.createJobExecution(job, new JobParameters()); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); + + stepExecution.setExecutionContext(new ExecutionContext(PropertiesConverter.stringToProperties("foo=bar"))); + // step.setLastExecution(stepExecution); + + try { + step.execute(stepExecution); + fail("Expected BatchCriticalException"); + } + catch (RuntimeException e) { + + assertEquals(BatchStatus.UNKNOWN, stepExecution.getStatus()); + StepExecution lastStepExecution = jobRepository.getLastStepExecution(jobExecution.getJobInstance(), step); + 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", e.getCause().getMessage()); + + } + } + +} diff --git a/spring-batch-core/src/test/resources/log4j.properties b/spring-batch-core/src/test/resources/log4j.properties index f242f56df..aa887525f 100644 --- a/spring-batch-core/src/test/resources/log4j.properties +++ b/spring-batch-core/src/test/resources/log4j.properties @@ -8,6 +8,7 @@ log4j.category.org.apache.activemq=ERROR log4j.category.org.springframework.batch=DEBUG log4j.category.org.springframework.batch.support=INFO # log4j.category.org.springframework.transaction=INFO +log4j.category.org.springframework.jdbc=DEBUG # log4j.category.org.hibernate.SQL=DEBUG # for debugging datasource initialization diff --git a/spring-batch-infrastructure-tests/pom.xml b/spring-batch-infrastructure-tests/pom.xml index bb61c923e..a58e5aaac 100644 --- a/spring-batch-infrastructure-tests/pom.xml +++ b/spring-batch-infrastructure-tests/pom.xml @@ -59,7 +59,7 @@ hsqldb hsqldb - test + runtime commons-io diff --git a/spring-batch-samples/.springBeans b/spring-batch-samples/.springBeans index af76377a4..63237cf04 100644 --- a/spring-batch-samples/.springBeans +++ b/spring-batch-samples/.springBeans @@ -1,10 +1,11 @@ 1 - + + src/main/resources/jobs/fixedLengthImportJob.xml src/main/resources/jobs/multilineJob.xml