diff --git a/spring-batch-core/.classpath b/spring-batch-core/.classpath index 6165ff76e..b6d9ce7c7 100644 --- a/spring-batch-core/.classpath +++ b/spring-batch-core/.classpath @@ -1,10 +1,10 @@ - + - - + + - + diff --git a/spring-batch-core/.project b/spring-batch-core/.project index aee145583..0dfc18b4e 100644 --- a/spring-batch-core/.project +++ b/spring-batch-core/.project @@ -1,30 +1,30 @@ - - - spring-batch-core - Core domain for batch processing, expressing a domain of Jobs, Steps, Chunks, etc. - - spring-batch-infrastructure - - - - org.eclipse.jdt.core.javabuilder - - - - - org.springframework.ide.eclipse.core.springbuilder - - - - - org.devzuz.q.maven.jdt.core.mavenIncrementalBuilder - - - - - - org.springframework.ide.eclipse.core.springnature - org.eclipse.jdt.core.javanature - org.devzuz.q.maven.jdt.core.mavenNature - - + + + spring-batch-core + Core domain for batch processing, expressing a domain of Jobs, Steps, Chunks, etc. + + spring-batch-infrastructure + + + + org.eclipse.jdt.core.javabuilder + + + + + org.springframework.ide.eclipse.core.springbuilder + + + + + org.devzuz.q.maven.jdt.core.mavenIncrementalBuilder + + + + + + org.springframework.ide.eclipse.core.springnature + org.eclipse.jdt.core.javanature + org.devzuz.q.maven.jdt.core.mavenNature + + diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/domain/JobListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/domain/JobListener.java index 2fc58c5e5..e770a568a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/domain/JobListener.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/domain/JobListener.java @@ -15,8 +15,12 @@ */ package org.springframework.batch.core.domain; - /** + * Provide callbacks at specific points in the lifecycle of a {@link Job}. + * Implementations can be stateful if they are careful to either ensure thread + * safety, or to use one instance of a listener per job, assuming that job + * instances themselves are not used by more than one thread. + * * @author Dave Syer * */ @@ -30,7 +34,7 @@ public interface JobListener { void beforeJob(JobExecution jobExecution); /** - * + * Callback after successful completion of a job. */ void afterJob(); } diff --git a/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/support/AbstractStepFactoryBean.java b/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/support/AbstractStepFactoryBean.java index b602f3fe4..794c72042 100644 --- a/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/support/AbstractStepFactoryBean.java +++ b/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/support/AbstractStepFactoryBean.java @@ -21,7 +21,7 @@ import org.springframework.batch.execution.step.ItemOrientedStep; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.ItemWriter; import org.springframework.beans.factory.BeanNameAware; -import org.springframework.beans.factory.config.AbstractFactoryBean; +import org.springframework.beans.factory.FactoryBean; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.util.Assert; @@ -33,7 +33,7 @@ import org.springframework.util.Assert; * @author Dave Syer * */ -public abstract class AbstractStepFactoryBean extends AbstractFactoryBean implements BeanNameAware { +public abstract class AbstractStepFactoryBean implements FactoryBean, BeanNameAware { private String name; @@ -49,6 +49,8 @@ public abstract class AbstractStepFactoryBean extends AbstractFactoryBean implem private JobRepository jobRepository; + private boolean singleton = true; + /** * */ @@ -140,7 +142,12 @@ public abstract class AbstractStepFactoryBean extends AbstractFactoryBean implem this.transactionManager = transactionManager; } - protected Object createInstance() throws Exception { + /** + * Create a {@link Step} from the configuration provided. + * + * @see org.springframework.beans.factory.FactoryBean#getObject() + */ + public final Object getObject() throws Exception { ItemOrientedStep step = new ItemOrientedStep(getName()); applyConfiguration(step); return step; @@ -156,7 +163,7 @@ public abstract class AbstractStepFactoryBean extends AbstractFactoryBean implem Assert.notNull(getItemWriter(), "ItemWriter must be provided"); Assert.notNull(jobRepository, "JobRepository must be provided"); Assert.notNull(transactionManager, "TransactionManager must be provided"); - + step.setItemProcessor(new SimpleItemHandler(itemReader, itemWriter)); step.setTransactionManager(transactionManager); step.setJobRepository(jobRepository); @@ -169,4 +176,23 @@ public abstract class AbstractStepFactoryBean extends AbstractFactoryBean implem return Step.class; } + /** + * Returns true by default, but in most cases a {@link Step} should not be + * treated as thread safe. Clients are recommended to create a new step for + * each job execution. + * + * @see org.springframework.beans.factory.FactoryBean#isSingleton() + */ + public boolean isSingleton() { + return this.singleton; + } + + /** + * Public setter for the singleton flag. + * @param singleton the value to set. Defaults to true. + */ + public void setSingleton(boolean singleton) { + this.singleton = singleton; + } + } \ No newline at end of file diff --git a/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/support/StatefulRetryStepFactoryBean.java b/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/support/StatefulRetryStepFactoryBean.java index 44144e973..149eff84b 100644 --- a/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/support/StatefulRetryStepFactoryBean.java +++ b/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/support/StatefulRetryStepFactoryBean.java @@ -39,9 +39,6 @@ import org.springframework.batch.retry.support.RetryTemplate; * limit given by the {@link RetryPolicy}. When the retry is exhausted instead * of the item being skipped it is handled by an {@link ItemRecoverer}.
* - * TODO: checking for null retry callback is a sucky way of determining if a - * stateful retry has been requested. - * * @author Dave Syer * */ @@ -51,6 +48,8 @@ public class StatefulRetryStepFactoryBean extends DefaultStepFactoryBean { private ItemKeyGenerator itemKeyGenerator; + private ItemRecoverer itemRecoverer; + /** * Public setter for the {@link RetryPolicy}. * @param retryPolicy the {@link RetryPolicy} to set @@ -73,6 +72,18 @@ public class StatefulRetryStepFactoryBean extends DefaultStepFactoryBean { this.itemKeyGenerator = itemKeyGenerator; } + /** + * Public setter for the {@link ItemRecoverer}. If this is set the + * {@link ItemRecoverer#recover(Object, Throwable)} will be called when + * retry is exhausted, and within the business transaction (which will not + * roll back because of any other item-related errors). + * + * @param itemRecoverer the {@link ItemRecoverer} to set + */ + public void setItemRecoverer(ItemRecoverer itemRecoverer) { + this.itemRecoverer = itemRecoverer; + } + /** * @param step * @@ -87,36 +98,20 @@ public class StatefulRetryStepFactoryBean extends DefaultStepFactoryBean { // exception handler limit, so this is a hack for now. getStepOperations().setExceptionHandler(new SimpleLimitExceptionHandler(Integer.MAX_VALUE)); - ItemReaderRetryCallback retryCallback = new ItemReaderRetryCallback(getItemReader(), getKeyGenerator(), + ItemReaderRetryCallback retryCallback = new ItemReaderRetryCallback(getItemReader(), itemKeyGenerator, getItemWriter()); + retryCallback.setRecoverer(itemRecoverer); ItemReaderRetryPolicy itemProviderRetryPolicy = new ItemReaderRetryPolicy(retryPolicy); RetryTemplate retryTemplate = new RetryTemplate(); retryTemplate.setRetryPolicy(itemProviderRetryPolicy); - StatefulRetryItemHandler itemProcessor = new StatefulRetryItemHandler(getItemReader(), getItemWriter(), retryTemplate, retryCallback); - + StatefulRetryItemHandler itemProcessor = new StatefulRetryItemHandler(getItemReader(), getItemWriter(), + retryTemplate, retryCallback); + step.setItemProcessor(itemProcessor); - - } - } - - /** - * @return an {@link ItemKeyGenerator} or null if none is found. - */ - private ItemKeyGenerator getKeyGenerator() { - - if (itemKeyGenerator != null) { - return itemKeyGenerator; } - if (getItemReader() instanceof ItemKeyGenerator) { - return (ItemKeyGenerator) getItemReader(); - } - if (getItemWriter() instanceof ItemKeyGenerator) { - return (ItemKeyGenerator) getItemWriter(); - } - return null; } @@ -129,10 +124,11 @@ public class StatefulRetryStepFactoryBean extends DefaultStepFactoryBean { /** * @param itemReader * @param itemWriter - * @param retryCallback - * @param retryTemplate + * @param retryCallback + * @param retryTemplate */ - public StatefulRetryItemHandler(ItemReader itemReader, ItemWriter itemWriter, RetryOperations retryTemplate, ItemReaderRetryCallback retryCallback) { + public StatefulRetryItemHandler(ItemReader itemReader, ItemWriter itemWriter, RetryOperations retryTemplate, + ItemReaderRetryCallback retryCallback) { super(itemReader, itemWriter); this.retryOperations = retryTemplate; this.retryCallback = retryCallback; diff --git a/spring-batch-execution/src/test/java/org/springframework/batch/execution/repository/SimpleJobRepositoryTests.java b/spring-batch-execution/src/test/java/org/springframework/batch/execution/repository/SimpleJobRepositoryTests.java index 18351e0db..47b649e9c 100644 --- a/spring-batch-execution/src/test/java/org/springframework/batch/execution/repository/SimpleJobRepositoryTests.java +++ b/spring-batch-execution/src/test/java/org/springframework/batch/execution/repository/SimpleJobRepositoryTests.java @@ -79,8 +79,6 @@ public class SimpleJobRepositoryTests extends TestCase { ExecutionContext executionContext; - private JobExecution jobExecution; - public void setUp() throws Exception { jobExecutionDao = (JobExecutionDao) jobExecutionDaoControl.getMock(); diff --git a/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/ItemOrientedStepTests.java b/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/ItemOrientedStepTests.java index 13289df86..a0429a04d 100644 --- a/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/ItemOrientedStepTests.java +++ b/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/ItemOrientedStepTests.java @@ -393,7 +393,7 @@ public class ItemOrientedStepTests extends TestCase { public void testAfterStep() throws Exception{ - final ExitStatus customStatus = new ExitStatus(true, "custom code"); + final ExitStatus customStatus = new ExitStatus(false, "custom code"); itemOrientedStep.setStepListeners(new StepListener[] {new StepListenerSupport() { public ExitStatus afterStep() { diff --git a/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/support/DefaultStepFactoryBeanTests.java b/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/support/DefaultStepFactoryBeanTests.java index 903d7376a..6300d777a 100644 --- a/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/support/DefaultStepFactoryBeanTests.java +++ b/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/support/DefaultStepFactoryBeanTests.java @@ -26,7 +26,6 @@ import junit.framework.TestCase; import org.springframework.batch.core.domain.BatchListener; import org.springframework.batch.core.domain.BatchStatus; import org.springframework.batch.core.domain.JobExecution; -import org.springframework.batch.core.domain.JobInstance; import org.springframework.batch.core.domain.JobParameters; import org.springframework.batch.core.listener.ItemListenerSupport; import org.springframework.batch.execution.job.SimpleJob; @@ -36,7 +35,6 @@ import org.springframework.batch.execution.repository.dao.MapJobInstanceDao; import org.springframework.batch.execution.repository.dao.MapStepExecutionDao; import org.springframework.batch.execution.step.AbstractStep; import org.springframework.batch.execution.step.ItemOrientedStep; -import org.springframework.batch.execution.step.support.DefaultStepFactoryBean; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.ItemWriter; import org.springframework.batch.item.reader.ListItemReader; @@ -84,7 +82,6 @@ public class DefaultStepFactoryBeanTests extends TestCase { private DefaultStepFactoryBean getStep(String[] args) throws Exception { DefaultStepFactoryBean factory = new DefaultStepFactoryBean(); - factory.setSingleton(false); List items = TransactionAwareProxyFactory.createTransactionalList(); items.addAll(Arrays.asList(args)); @@ -95,13 +92,6 @@ public class DefaultStepFactoryBeanTests extends TestCase { factory.setJobRepository(repository); factory.setTransactionManager(new ResourcelessTransactionManager()); factory.setBeanName("stepName"); - // step.setItemRecoverer(new ItemRecoverer() { - // public boolean recover(Object item, Throwable cause) { - // recovered.add(item); - // assertTrue(TransactionSynchronizationManager.isActualTransactionActive()); - // return true; - // } - // }); return factory; } @@ -115,12 +105,10 @@ public class DefaultStepFactoryBeanTests extends TestCase { step.setName("step2"); job.addStep(step); - JobInstance jobInstance = repository.createJobExecution(job, new JobParameters()).getJobInstance(); + JobExecution jobExecution = repository.createJobExecution(job, new JobParameters()); - JobExecution jobExecutionContext = new JobExecution(jobInstance); - - job.execute(jobExecutionContext); - assertEquals(BatchStatus.COMPLETED, jobExecutionContext.getStatus()); + job.execute(jobExecution); + assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); assertEquals(3, processed.size()); assertTrue(processed.contains("foo")); } @@ -154,6 +142,7 @@ public class DefaultStepFactoryBeanTests extends TestCase { public void onReadError(Exception ex) { recovered.add(ex); } + public void onWriteError(Exception ex, Object item) { recovered.add(ex); } @@ -173,8 +162,6 @@ public class DefaultStepFactoryBeanTests extends TestCase { assertEquals(null, provider.read()); assertEquals(3, recovered.size()); } - - // TODO: test recovery and stateful retry public void testExceptionTerminates() throws Exception { DefaultStepFactoryBean factory = getStep(new String[] { "foo", "bar", "spam" }); diff --git a/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/support/RepeatOperationsStepFactoryBeanTests.java b/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/support/RepeatOperationsStepFactoryBeanTests.java index 9b60306bf..b564740b4 100644 --- a/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/support/RepeatOperationsStepFactoryBeanTests.java +++ b/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/support/RepeatOperationsStepFactoryBeanTests.java @@ -44,7 +44,14 @@ public class RepeatOperationsStepFactoryBeanTests extends TestCase { private List list; private JobExecution jobExecution = new JobExecution(new JobInstance(new Long(0L), new JobParameters(), - new JobSupport("job")));; + new JobSupport("job"))); + + protected void setUp() throws Exception { + factory.setItemReader(new ListItemReader(new ArrayList())); + factory.setItemWriter(new EmptyItemWriter()); + factory.setJobRepository(new JobRepositorySupport()); + factory.setTransactionManager(new ResourcelessTransactionManager()); + } public void testType() throws Exception { assertEquals(Step.class, factory.getObjectType()); @@ -70,8 +77,6 @@ public class RepeatOperationsStepFactoryBeanTests extends TestCase { } }); - factory.setSingleton(false); - Step step = (Step) factory.getObject(); step.execute(new StepExecution(step, jobExecution)); diff --git a/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/support/StatefulRetryStepFactoryBeanTests.java b/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/support/StatefulRetryStepFactoryBeanTests.java index 917b457cd..7b20df886 100644 --- a/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/support/StatefulRetryStepFactoryBeanTests.java +++ b/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/support/StatefulRetryStepFactoryBeanTests.java @@ -15,24 +15,115 @@ */ package org.springframework.batch.execution.step.support; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + import junit.framework.TestCase; +import org.springframework.batch.core.domain.JobExecution; +import org.springframework.batch.core.domain.JobParameters; +import org.springframework.batch.core.domain.JobParametersBuilder; import org.springframework.batch.core.domain.Step; +import org.springframework.batch.core.domain.StepExecution; +import org.springframework.batch.execution.job.JobSupport; +import org.springframework.batch.execution.repository.SimpleJobRepository; +import org.springframework.batch.execution.repository.dao.MapJobExecutionDao; +import org.springframework.batch.execution.repository.dao.MapJobInstanceDao; +import org.springframework.batch.execution.repository.dao.MapStepExecutionDao; +import org.springframework.batch.execution.step.ItemOrientedStep; +import org.springframework.batch.item.ItemReader; +import org.springframework.batch.item.ItemRecoverer; +import org.springframework.batch.item.ItemWriter; +import org.springframework.batch.item.reader.ListItemReader; +import org.springframework.batch.item.writer.AbstractItemWriter; +import org.springframework.batch.retry.policy.AlwaysRetryPolicy; +import org.springframework.batch.support.transaction.ResourcelessTransactionManager; +import org.springframework.batch.support.transaction.TransactionAwareProxyFactory; +import org.springframework.transaction.support.TransactionSynchronizationManager; /** * @author Dave Syer - * + * */ public class StatefulRetryStepFactoryBeanTests extends TestCase { - private AbstractStepFactoryBean factory = new StatefulRetryStepFactoryBean(); - + private StatefulRetryStepFactoryBean factory = new StatefulRetryStepFactoryBean(); + + private List recovered = new ArrayList(); + + private List processed = new ArrayList(); + + private SimpleJobRepository repository = new SimpleJobRepository(new MapJobInstanceDao(), new MapJobExecutionDao(), + new MapStepExecutionDao()); + + JobExecution jobExecution; + + private ItemWriter processor = new AbstractItemWriter() { + public void write(Object data) throws Exception { + processed.add((String) data); + } + }; + + /* + * (non-Javadoc) + * @see junit.framework.TestCase#setUp() + */ + protected void setUp() throws Exception { + + MapJobInstanceDao.clear(); + MapJobExecutionDao.clear(); + MapStepExecutionDao.clear(); + + factory.setBeanName("step"); + + factory.setItemReader(new ListItemReader(new ArrayList())); + factory.setItemWriter(processor); + factory.setJobRepository(repository); + factory.setTransactionManager(new ResourcelessTransactionManager()); + + JobSupport job = new JobSupport("jobName"); + job.setRestartable(true); + JobParameters jobParameters = new JobParametersBuilder().addString("statefulTest", "make_this_unique").toJobParameters(); + jobExecution = repository.createJobExecution(job, jobParameters); + jobExecution.setEndTime(new Date()); + + } + public void testType() throws Exception { assertEquals(Step.class, factory.getObjectType()); } - + public void testDefaultValue() throws Exception { assertTrue(factory.getObject() instanceof Step); } + public void testRecovery() throws Exception { + factory.setItemRecoverer(new ItemRecoverer() { + public boolean recover(Object item, Throwable cause) { + recovered.add(item); + assertTrue(TransactionSynchronizationManager.isActualTransactionActive()); + return true; + } + }); + List items = TransactionAwareProxyFactory.createTransactionalList(); + items.addAll(Arrays.asList(new String[] { "a", "b", "c" })); + ItemReader provider = new ListItemReader(items) { + int count = 0; + public Object read() { + count++; + if (count == 2) { + throw new RuntimeException("Temporary error - retry for success."); + } + return super.read(); + } + }; + factory.setItemReader(provider); + factory.setRetryPolicy(new AlwaysRetryPolicy()); + ItemOrientedStep step = (ItemOrientedStep) factory.getObject(); + + step.execute(new StepExecution(step, jobExecution)); + } + } diff --git a/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/support/StepExecutorInterruptionTests.java b/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/support/StepExecutorInterruptionTests.java index 877771d93..9d5c58895 100644 --- a/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/support/StepExecutorInterruptionTests.java +++ b/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/support/StepExecutorInterruptionTests.java @@ -26,12 +26,9 @@ import org.springframework.batch.core.domain.StepExecution; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.execution.job.JobSupport; import org.springframework.batch.execution.repository.SimpleJobRepository; -import org.springframework.batch.execution.repository.dao.JobExecutionDao; -import org.springframework.batch.execution.repository.dao.JobInstanceDao; import org.springframework.batch.execution.repository.dao.MapJobExecutionDao; import org.springframework.batch.execution.repository.dao.MapJobInstanceDao; import org.springframework.batch.execution.repository.dao.MapStepExecutionDao; -import org.springframework.batch.execution.repository.dao.StepExecutionDao; import org.springframework.batch.execution.step.ItemOrientedStep; import org.springframework.batch.item.reader.AbstractItemReader; import org.springframework.batch.item.reader.ItemReaderAdapter; @@ -42,14 +39,6 @@ import org.springframework.batch.support.transaction.ResourcelessTransactionMana public class StepExecutorInterruptionTests extends TestCase { - private JobRepository jobRepository; - - private JobInstanceDao jobInstanceDao = new MapJobInstanceDao(); - - private JobExecutionDao jobExecutionDao = new MapJobExecutionDao(); - - private StepExecutionDao stepExecutionDao = new MapStepExecutionDao(); - private ItemOrientedStep step; private JobExecution jobExecution; @@ -61,7 +50,7 @@ public class StepExecutorInterruptionTests extends TestCase { MapJobExecutionDao.clear(); MapStepExecutionDao.clear(); - jobRepository = new SimpleJobRepository(jobInstanceDao, jobExecutionDao, stepExecutionDao); + JobRepository jobRepository = new SimpleJobRepository(new MapJobInstanceDao(), new MapJobExecutionDao(), new MapStepExecutionDao()); JobSupport jobConfiguration = new JobSupport(); step = new ItemOrientedStep("interruptedStep"); diff --git a/spring-batch-infrastructure/.classpath b/spring-batch-infrastructure/.classpath index bf5d1cf16..317ed9707 100644 --- a/spring-batch-infrastructure/.classpath +++ b/spring-batch-infrastructure/.classpath @@ -1,8 +1,8 @@ - - + + diff --git a/spring-batch-infrastructure/.settings/org.eclipse.jdt.core.prefs b/spring-batch-infrastructure/.settings/org.eclipse.jdt.core.prefs index d60e8dcbb..2021b6bf0 100644 --- a/spring-batch-infrastructure/.settings/org.eclipse.jdt.core.prefs +++ b/spring-batch-infrastructure/.settings/org.eclipse.jdt.core.prefs @@ -1,8 +1,6 @@ -#Fri Feb 29 13:52:09 GMT 2008 +#Tue Mar 04 12:26:32 GMT 2008 eclipse.preferences.version=1 instance/org.eclipse.core.net/org.eclipse.core.net.hasMigrated=true -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.4 org.eclipse.jdt.core.compiler.compliance=1.4 org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.enumIdentifier=warning -org.eclipse.jdt.core.compiler.source=1.4 diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/ExitStatus.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/ExitStatus.java index 2e85062bb..a88f8c4b0 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/ExitStatus.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/ExitStatus.java @@ -49,7 +49,7 @@ public class ExitStatus implements Serializable { * Convenient constant value representing finished processing. */ public static final ExitStatus FINISHED = new ExitStatus(false, "COMPLETED"); - + /** * Convenient constant value representing interrupted processing. */ @@ -131,8 +131,11 @@ public class ExitStatus implements Serializable { /** * Create a new {@link ExitStatus} with a logical combination of the - * continuable flag, and a concatenation of the codes. If the input is null - * we just return this. + * continuable flag, and a concatenation of the descriptions. The exit code + * is only replaced if the result is continuable or the input is not + * continuable.
+ * + * If the input is null just return this. * * @param status an {@link ExitStatus} to combine with this one. * @return a new {@link ExitStatus} with {@link #isContinuable()} the @@ -142,7 +145,11 @@ public class ExitStatus implements Serializable { if (status == null) { return this; } - return and(status.continuable).replaceExitCode(status.exitCode).addExitDescription(status.exitDescription); + ExitStatus result = and(status.continuable).addExitDescription(status.exitDescription); + if (result.continuable || !status.continuable) { + result = result.replaceExitCode(status.exitCode); + } + return result; } /* diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/ExitStatusTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/ExitStatusTests.java index e46a12a83..21ccc969f 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/ExitStatusTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/ExitStatusTests.java @@ -21,12 +21,13 @@ import junit.framework.TestCase; /** * @author Dave Syer - * + * */ public class ExitStatusTests extends TestCase { /** - * Test method for {@link org.springframework.batch.repeat.ExitStatus#ExitStatus(boolean, int)}. + * Test method for + * {@link org.springframework.batch.repeat.ExitStatus#ExitStatus(boolean, int)}. */ public void testExitStatusBooleanInt() { ExitStatus status = new ExitStatus(true, "10"); @@ -35,7 +36,8 @@ public class ExitStatusTests extends TestCase { } /** - * Test method for {@link org.springframework.batch.repeat.ExitStatus#ExitStatus(boolean, int)}. + * Test method for + * {@link org.springframework.batch.repeat.ExitStatus#ExitStatus(boolean, int)}. */ public void testExitStatusConstantsContinuable() { ExitStatus status = ExitStatus.CONTINUABLE; @@ -44,14 +46,15 @@ public class ExitStatusTests extends TestCase { } /** - * Test method for {@link org.springframework.batch.repeat.ExitStatus#ExitStatus(boolean, int)}. + * Test method for + * {@link org.springframework.batch.repeat.ExitStatus#ExitStatus(boolean, int)}. */ public void testExitStatusConstantsFinished() { ExitStatus status = ExitStatus.FINISHED; assertFalse(status.isContinuable()); assertEquals("COMPLETED", status.getExitCode()); } - + /** * Test equality of exit statuses. * @@ -60,13 +63,13 @@ public class ExitStatusTests extends TestCase { public void testEqualsWithSameProperties() throws Exception { assertEquals(ExitStatus.CONTINUABLE, new ExitStatus(true, "CONTINUABLE")); } - - public void testEqualsSelf(){ + + public void testEqualsSelf() { ExitStatus status = new ExitStatus(true, "test"); assertEquals(status, status); } - - public void testEquals(){ + + public void testEquals() { assertEquals(new ExitStatus(true, "test"), new ExitStatus(true, "test")); } @@ -89,57 +92,93 @@ public class ExitStatusTests extends TestCase { } /** - * Test method for {@link org.springframework.batch.repeat.ExitStatus#and(boolean)}. + * Test method for + * {@link org.springframework.batch.repeat.ExitStatus#and(boolean)}. */ public void testAndBoolean() { assertTrue(ExitStatus.CONTINUABLE.and(true).isContinuable()); assertFalse(ExitStatus.CONTINUABLE.and(false).isContinuable()); - } - - /** - * Test method for {@link org.springframework.batch.repeat.ExitStatus#and(org.springframework.batch.repeat.ExitStatus)}. - */ - public void testAndExitStatus() { - assertTrue(ExitStatus.CONTINUABLE.and(ExitStatus.CONTINUABLE.isContinuable()).isContinuable()); - assertFalse(ExitStatus.CONTINUABLE.and(ExitStatus.FINISHED.isContinuable()).isContinuable()); - assertTrue(ExitStatus.FINISHED.and(ExitStatus.CONTINUABLE.isContinuable()).getExitCode() - == ExitStatus.FINISHED.getExitCode()); ExitStatus status = new ExitStatus(false, "CUSTOM_CODE", "CUSTOM_DESCRIPTION"); assertTrue(status.and(true).getExitCode() == "CUSTOM_CODE"); assertTrue(status.and(true).getExitDescription() == "CUSTOM_DESCRIPTION"); } + /** + * Test method for + * {@link org.springframework.batch.repeat.ExitStatus#and(org.springframework.batch.repeat.ExitStatus)}. + */ + public void testAndExitStatusStillContinuable() { + assertTrue(ExitStatus.CONTINUABLE.and(ExitStatus.CONTINUABLE).isContinuable()); + assertFalse(ExitStatus.CONTINUABLE.and(ExitStatus.FINISHED).isContinuable()); + assertTrue(ExitStatus.CONTINUABLE.and(ExitStatus.CONTINUABLE).getExitCode() == ExitStatus.CONTINUABLE + .getExitCode()); + } + + /** + * Test method for + * {@link org.springframework.batch.repeat.ExitStatus#and(org.springframework.batch.repeat.ExitStatus)}. + */ + public void testAndExitStatusWhenFinishedAddedToContinuable() { + assertEquals(ExitStatus.FINISHED.getExitCode(), ExitStatus.CONTINUABLE.and(ExitStatus.FINISHED).getExitCode()); + } + + /** + * Test method for + * {@link org.springframework.batch.repeat.ExitStatus#and(org.springframework.batch.repeat.ExitStatus)}. + */ + public void testAndExitStatusWhenContinuableAddedToFinished() { + assertEquals(ExitStatus.FINISHED.getExitCode(), ExitStatus.FINISHED.and(ExitStatus.CONTINUABLE).getExitCode()); + } + + /** + * Test method for + * {@link org.springframework.batch.repeat.ExitStatus#and(org.springframework.batch.repeat.ExitStatus)}. + */ + public void testAndExitStatusWhenCustomContinuableAddedToContinuable() { + assertEquals("CUSTOM", ExitStatus.CONTINUABLE.and(ExitStatus.CONTINUABLE.replaceExitCode("CUSTOM")) + .getExitCode()); + } + + /** + * Test method for + * {@link org.springframework.batch.repeat.ExitStatus#and(org.springframework.batch.repeat.ExitStatus)}. + */ + public void testAndExitStatusWhenCustomContinuableAddedToFinished() { + assertEquals(ExitStatus.FINISHED.getExitCode(), ExitStatus.FINISHED.and(ExitStatus.CONTINUABLE.replaceExitCode("CUSTOM")) + .getExitCode()); + } + public void testAddExitCode() throws Exception { ExitStatus status = ExitStatus.CONTINUABLE.replaceExitCode("FOO"); - assertTrue(ExitStatus.CONTINUABLE!=status); + assertTrue(ExitStatus.CONTINUABLE != status); assertTrue(status.isContinuable()); assertEquals("FOO", status.getExitCode()); } public void testAddExitCodeToExistingStatus() throws Exception { ExitStatus status = ExitStatus.CONTINUABLE.replaceExitCode("FOO").replaceExitCode("BAR"); - assertTrue(ExitStatus.CONTINUABLE!=status); + assertTrue(ExitStatus.CONTINUABLE != status); assertTrue(status.isContinuable()); assertEquals("BAR", status.getExitCode()); } public void testAddExitCodeToSameStatus() throws Exception { ExitStatus status = ExitStatus.CONTINUABLE.replaceExitCode(ExitStatus.CONTINUABLE.getExitCode()); - assertTrue(ExitStatus.CONTINUABLE!=status); + assertTrue(ExitStatus.CONTINUABLE != status); assertTrue(status.isContinuable()); assertEquals(ExitStatus.CONTINUABLE.getExitCode(), status.getExitCode()); } public void testAddExitDescription() throws Exception { ExitStatus status = ExitStatus.CONTINUABLE.addExitDescription("Foo"); - assertTrue(ExitStatus.CONTINUABLE!=status); + assertTrue(ExitStatus.CONTINUABLE != status); assertTrue(status.isContinuable()); assertEquals("Foo", status.getExitDescription()); } public void testAddExitDescriptionToSameStatus() throws Exception { ExitStatus status = ExitStatus.CONTINUABLE.addExitDescription("Foo").addExitDescription("Foo"); - assertTrue(ExitStatus.CONTINUABLE!=status); + assertTrue(ExitStatus.CONTINUABLE != status); assertTrue(status.isContinuable()); assertEquals("Foo", status.getExitDescription()); } @@ -149,12 +188,12 @@ public class ExitStatusTests extends TestCase { assertEquals("FOO", status.getExitCode()); assertEquals("Bar", status.getExitDescription()); } - + public void testRunningIsRunning() throws Exception { assertTrue(ExitStatus.RUNNING.isRunning()); assertTrue(new ExitStatus(true, "RUNNING").isRunning()); } - + public void testUnkownIsRunning() throws Exception { assertTrue(ExitStatus.UNKNOWN.isRunning()); } diff --git a/spring-batch-integration/.classpath b/spring-batch-integration/.classpath index 6ceeb8b67..b6d9ce7c7 100644 --- a/spring-batch-integration/.classpath +++ b/spring-batch-integration/.classpath @@ -1,10 +1,10 @@ - + - + - +