diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/ExitStatusExceptionClassifier.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/ExitStatusExceptionClassifier.java deleted file mode 100644 index ba9c9d592..000000000 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/ExitStatusExceptionClassifier.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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; - -import org.springframework.batch.repeat.ExitStatus; -import org.springframework.batch.support.ExceptionClassifier; - -/** - * Extension of the {@link ExceptionClassifier} that explicitly deals with - * returns an {@link ExitStatus}. This is useful for mapping from an exception - * type to an exit status with a customised code or detailed message. - * - * @author Lucas Ward - * - */ -public interface ExitStatusExceptionClassifier extends ExceptionClassifier { - - public static final String FATAL_EXCEPTION = "FATAL_EXCEPTION"; - - public static final String JOB_INTERRUPTED = "JOB_INTERRUPTED"; - - /** - * Typesafe version of classify that explicitly returns an - * {@link ExitStatus} object. - * - * @param throwable - * @return ExitStatus representing the ExitCode and Message for the given - * exception. - */ - public ExitStatus classifyForExitCode(Throwable throwable); -} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/SimpleExitStatusExceptionClassifier.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/SimpleExitStatusExceptionClassifier.java deleted file mode 100644 index 1347d2d9a..000000000 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/SimpleExitStatusExceptionClassifier.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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; - -import java.io.PrintWriter; -import java.io.StringWriter; - -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.launch.support.ExitCodeMapper; -import org.springframework.batch.core.repository.NoSuchJobException; -import org.springframework.batch.repeat.ExitStatus; - -/** - *

- * Simple implementation of {@link ExitStatusExceptionClassifier} that returns - * basic String exit codes, and defaults to the class name of the throwable for - * the message. Most users will want to write their own implementation that - * creates more specific exit codes for different exception types. - *

- * - * @author Lucas Ward - * - */ -public class SimpleExitStatusExceptionClassifier implements - ExitStatusExceptionClassifier { - - /* (non-Javadoc) - * @see org.springframework.batch.core.executor.ExitCodeExceptionClassifier#classifyForExitCode(java.lang.Throwable) - */ - public ExitStatus classifyForExitCode(Throwable throwable) { - return (ExitStatus) classify(throwable); - } - - /* (non-Javadoc) - * @see org.springframework.batch.common.ExceptionClassifier#classify(java.lang.Throwable) - */ - public Object classify(Throwable throwable) { - - ExitStatus exitStatus = ExitStatus.FAILED; - - if (throwable instanceof JobInterruptedException) { - exitStatus = new ExitStatus(false, JOB_INTERRUPTED, - JobInterruptedException.class.getName()); - } else if( throwable instanceof NoSuchJobException ) { - exitStatus = new ExitStatus(false, ExitCodeMapper.NO_SUCH_JOB); - } else { - String message = ""; - if (throwable!=null) { - StringWriter writer = new StringWriter(); - throwable.printStackTrace(new PrintWriter(writer)); - message = writer.toString(); - } - exitStatus = new ExitStatus(false, FATAL_EXCEPTION, message); - } - - return exitStatus; - } - - /* - * (non-Javadoc) - * - * @see org.springframework.batch.common.ExceptionClassifier#getDefault() - */ - public Object getDefault() { - // return without message since we don't know what the exception is - return new ExitStatus(false, FATAL_EXCEPTION); - } - -} 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 d107e4922..02bb53e91 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 @@ -15,6 +15,8 @@ */ package org.springframework.batch.core.step.item; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.Date; import org.apache.commons.logging.Log; @@ -25,11 +27,11 @@ import org.springframework.batch.core.StepContribution; import org.springframework.batch.core.StepExecution; import org.springframework.batch.core.StepListener; import org.springframework.batch.core.UnexpectedJobExecutionException; +import org.springframework.batch.core.launch.support.ExitCodeMapper; import org.springframework.batch.core.listener.CompositeStepListener; import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.repository.NoSuchJobException; import org.springframework.batch.core.step.AbstractStep; -import org.springframework.batch.core.step.ExitStatusExceptionClassifier; -import org.springframework.batch.core.step.SimpleExitStatusExceptionClassifier; import org.springframework.batch.core.step.StepExecutionSynchronizer; import org.springframework.batch.core.step.StepExecutionSyncronizerFactory; import org.springframework.batch.core.step.StepInterruptionPolicy; @@ -71,12 +73,15 @@ public class ItemOrientedStep extends AbstractStep { private static final Log logger = LogFactory.getLog(ItemOrientedStep.class); + /** + * Exit code for interrupted status. + */ + public static final String JOB_INTERRUPTED = "JOB_INTERRUPTED"; + private RepeatOperations chunkOperations = new RepeatTemplate(); private RepeatOperations stepOperations = new RepeatTemplate(); - private ExitStatusExceptionClassifier exceptionClassifier = new SimpleExitStatusExceptionClassifier(); - // default to checking current thread for interruption. private StepInterruptionPolicy interruptionPolicy = new ThreadStepInterruptionPolicy(); @@ -212,16 +217,6 @@ public class ItemOrientedStep extends AbstractStep { this.interruptionPolicy = interruptionPolicy; } - /** - * Setter for the {@link ExitStatusExceptionClassifier} that will be used to - * classify any exception that causes a job to fail. - * - * @param exceptionClassifier - */ - public void setExceptionClassifier(ExitStatusExceptionClassifier exceptionClassifier) { - this.exceptionClassifier = exceptionClassifier; - } - /** * Mostly useful for testing, but could be used to remove dependence on * backport concurrency utilities. Public setter for the @@ -421,20 +416,21 @@ public class ItemOrientedStep extends AbstractStep { } /** - * @param stepExecution - * @param fatalException - * @param e - * @return - * @throws JobInterruptedException + * @param stepExecution the current {@link StepExecution} + * @param fatalException the {@link ExceptionHolder} containing information about failures in meta-data + * @param e the cause of teh failure + * @return an {@link ExitStatus} */ private ExitStatus processFailure(final StepExecution stepExecution, final ExceptionHolder fatalException, - Throwable e) throws JobInterruptedException { - ExitStatus status; - // classify exception so an exit code can be stored. - status = exceptionClassifier.classifyForExitCode(e); + Throwable e) { + + // Default classification marks this as a failure and adds the exception + // type and message + ExitStatus status = getDefaultExitStatusForFailure(e); if (!fatalException.hasException()) { try { + // classify exception so an exit code can be stored. status = status.and(listener.onErrorInStep(stepExecution, e)); } catch (RuntimeException ex) { @@ -448,6 +444,34 @@ public class ItemOrientedStep extends AbstractStep { return status; } + /** + * Default mapping from throwable to {@link ExitStatus}. Clients can modify + * the exit code using a {@link StepListener}. + * + * @param throwable the cause of teh failure + * @return an {@link ExitStatus} + */ + private ExitStatus getDefaultExitStatusForFailure(Throwable throwable) { + ExitStatus exitStatus; + if (throwable instanceof JobInterruptedException) { + exitStatus = new ExitStatus(false, JOB_INTERRUPTED, JobInterruptedException.class.getName()); + } + else if (throwable instanceof NoSuchJobException) { + exitStatus = new ExitStatus(false, ExitCodeMapper.NO_SUCH_JOB); + } + else { + String message = ""; + if (throwable != null) { + StringWriter writer = new StringWriter(); + throwable.printStackTrace(new PrintWriter(writer)); + message = writer.toString(); + } + exitStatus = ExitStatus.FAILED.addExitDescription(message); + } + + return exitStatus; + } + /** * Execute a bunch of identical business logic operations all within a * transaction. The transaction is programmatically started and stopped diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractStepDaoTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractStepDaoTests.java index d087f96e2..915e2e809 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractStepDaoTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractStepDaoTests.java @@ -26,7 +26,6 @@ import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.Step; import org.springframework.batch.core.StepExecution; import org.springframework.batch.core.job.JobSupport; -import org.springframework.batch.core.step.ExitStatusExceptionClassifier; import org.springframework.batch.core.step.StepSupport; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.repeat.ExitStatus; @@ -126,8 +125,7 @@ public abstract class AbstractStepDaoTests extends AbstractTransactionalDataSour StepExecution execution = new StepExecution(step2, jobExecution, null); execution.setStatus(BatchStatus.STARTED); execution.setStartTime(new Date(System.currentTimeMillis())); - execution.setExitStatus(new ExitStatus(false, ExitStatusExceptionClassifier.FATAL_EXCEPTION, - "java.lang.Exception")); + execution.setExitStatus(ExitStatus.FAILED.addExitDescription("java.lang.Exception")); stepExecutionDao.saveStepExecution(execution); StepExecution retrievedExecution = stepExecutionDao.getStepExecution(jobExecution, step2); assertNotNull(retrievedExecution); @@ -140,8 +138,7 @@ public abstract class AbstractStepDaoTests extends AbstractTransactionalDataSour execution.setStatus(BatchStatus.STARTED); execution.setStartTime(new Date(System.currentTimeMillis())); execution.setExecutionContext(executionContext); - execution.setExitStatus(new ExitStatus(false, ExitStatusExceptionClassifier.FATAL_EXCEPTION, - "java.lang.Exception")); + execution.setExitStatus(ExitStatus.FAILED.addExitDescription("java.lang.Exception")); stepExecutionDao.saveStepExecution(execution); stepExecutionDao.saveOrUpdateExecutionContext(execution); StepExecution retrievedExecution = stepExecutionDao.getStepExecution(jobExecution, step2); @@ -159,8 +156,7 @@ public abstract class AbstractStepDaoTests extends AbstractTransactionalDataSour stepExecution.setCommitCount(5); stepExecution.setItemCount(5); stepExecution.setExecutionContext(new ExecutionContext()); - stepExecution.setExitStatus(new ExitStatus(false, ExitStatusExceptionClassifier.FATAL_EXCEPTION, - "java.lang.Exception")); + stepExecution.setExitStatus(ExitStatus.FAILED.addExitDescription("java.lang.Exception")); stepExecutionDao.updateStepExecution(stepExecution); StepExecution retrievedExecution = stepExecutionDao.getStepExecution(jobExecution, step1); assertNotNull(retrievedExecution); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/SimpleExitStatusExceptionClassifierTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/SimpleExitStatusExceptionClassifierTests.java deleted file mode 100644 index be3b58745..000000000 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/SimpleExitStatusExceptionClassifierTests.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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; - - -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.launch.support.ExitCodeMapper; -import org.springframework.batch.core.repository.NoSuchJobException; -import org.springframework.batch.core.step.ExitStatusExceptionClassifier; -import org.springframework.batch.core.step.SimpleExitStatusExceptionClassifier; -import org.springframework.batch.repeat.ExitStatus; - -import junit.framework.TestCase; - -/** - * @author Lucas Ward - * - */ -public class SimpleExitStatusExceptionClassifierTests extends TestCase { - - NullPointerException exception; - - SimpleExitStatusExceptionClassifier classifier = new SimpleExitStatusExceptionClassifier(); - - protected void setUp() throws Exception { - super.setUp(); - exception = new NullPointerException(); - } - - public void testClassifyForExitCode() { - ExitStatus exitStatus = classifier.classifyForExitCode(exception); - assertEquals(exitStatus.getExitCode(), "FATAL_EXCEPTION"); - String description = exitStatus.getExitDescription(); - assertTrue("Description does not contain NullPointerException: "+description, description.indexOf("java.lang.NullPointerException")>=0); - } - - public void testClassify() { - ExitStatus exitStatus = (ExitStatus)classifier.classify(exception); - assertEquals(exitStatus.getExitCode(), "FATAL_EXCEPTION"); - String description = exitStatus.getExitDescription(); - assertTrue("Description does not contain NullPointerException: "+description, description.indexOf("java.lang.NullPointerException")>=0); - } - - public void testGetDefault() { - ExitStatus exitStatus = (ExitStatus)classifier.getDefault(); - assertEquals(exitStatus.getExitCode(), "FATAL_EXCEPTION"); - assertEquals(exitStatus.getExitDescription(), ""); - } - - /* - * Attempting to classify a null throwable should lead to a blank description, not a - * null pointer exception. - */ - public void testClassifyNullThrowable(){ - ExitStatus exitStatus = (ExitStatus)classifier.classify(null); - assertEquals(exitStatus.getExitCode(), "FATAL_EXCEPTION"); - assertEquals(exitStatus.getExitDescription(), ""); - } - - public void testClassifyInterruptedException(){ - ExitStatus exitStatus = (ExitStatus)classifier.classifyForExitCode(new JobInterruptedException("")); - assertEquals(exitStatus.getExitCode(), ExitStatusExceptionClassifier.JOB_INTERRUPTED); - assertEquals(exitStatus.getExitDescription(), - JobInterruptedException.class.getName()); - } - - /** - * a NoSuchJobException should lead to the related constant - */ - public void testClassifyNoSuchJobException() { - ExitStatus exitStatus = (ExitStatus)classifier.classifyForExitCode(new NoSuchJobException("")); - assertEquals(exitStatus.getExitCode(), ExitCodeMapper.NO_SUCH_JOB); - assertEquals(exitStatus.getExitDescription(), ""); - } -} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ItemOrientedStepTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ItemOrientedStepTests.java index 5e4197893..3df28a024 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ItemOrientedStepTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ItemOrientedStepTests.java @@ -23,7 +23,6 @@ import java.util.List; import junit.framework.TestCase; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.UnexpectedJobExecutionException; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobInstance; import org.springframework.batch.core.JobInterruptedException; @@ -31,6 +30,7 @@ import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.StepContribution; import org.springframework.batch.core.StepExecution; import org.springframework.batch.core.StepListener; +import org.springframework.batch.core.UnexpectedJobExecutionException; import org.springframework.batch.core.job.JobSupport; import org.springframework.batch.core.listener.StepListenerSupport; import org.springframework.batch.core.repository.dao.MapJobExecutionDao; @@ -40,18 +40,16 @@ import org.springframework.batch.core.repository.support.SimpleJobRepository; import org.springframework.batch.core.step.AbstractStep; import org.springframework.batch.core.step.JobRepositorySupport; import org.springframework.batch.core.step.StepInterruptionPolicy; -import org.springframework.batch.core.step.item.ItemOrientedStep; -import org.springframework.batch.core.step.item.SimpleItemHandler; import org.springframework.batch.item.AbstractItemReader; import org.springframework.batch.item.AbstractItemWriter; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.ItemStream; +import org.springframework.batch.item.ItemStreamException; import org.springframework.batch.item.ItemStreamSupport; import org.springframework.batch.item.ItemWriter; import org.springframework.batch.item.MarkFailedException; import org.springframework.batch.item.ResetFailedException; -import org.springframework.batch.item.ItemStreamException; import org.springframework.batch.item.support.ListItemReader; import org.springframework.batch.repeat.ExitStatus; import org.springframework.batch.repeat.policy.SimpleCompletionPolicy; @@ -215,6 +213,42 @@ public class ItemOrientedStepTests extends TestCase { } } + public void testExitCodeCustomClassification() throws Exception { + + ItemReader itemReader = new AbstractItemReader() { + + public Object read() throws Exception { + int counter = 0; + counter++; + + if (counter == 1) { + throw new RuntimeException(); + } + + return ExitStatus.CONTINUABLE; + } + + }; + + itemOrientedStep.setItemHandler(new SimpleItemHandler(itemReader, itemWriter)); + itemOrientedStep.registerStepListener(new StepListenerSupport() { + public ExitStatus onErrorInStep(StepExecution stepExecution, Throwable e) { + return ExitStatus.FAILED.addExitDescription("FOO"); + } + }); + JobExecution jobExecutionContext = new JobExecution(jobInstance); + StepExecution stepExecution = new StepExecution(itemOrientedStep, jobExecutionContext); + + try { + itemOrientedStep.execute(stepExecution); + } catch (Exception ex) { + ExitStatus status = stepExecution.getExitStatus(); + assertFalse(status.isContinuable()); + String description = status.getExitDescription(); + assertTrue("Description does not include 'FOO': "+description, description.indexOf("FOO")>=0); + } + } + /* * make sure a job that has never been executed before, but does have saveExecutionAttributes = true, doesn't have * restoreFrom called on it. @@ -586,7 +620,7 @@ public class ItemOrientedStepTests extends TestCase { try { itemOrientedStep.execute(stepExecution); - fail("Expected BatchCriticalException"); + fail("Expected UnexpectedJobExecutionException"); } catch (UnexpectedJobExecutionException ex) { assertEquals(BatchStatus.UNKNOWN, stepExecution.getStatus()); String msg = stepExecution.getExitStatus().getExitDescription(); diff --git a/spring-batch-samples/src/main/resources/batch-derby.properties b/spring-batch-samples/src/main/resources/batch-derby.properties index 4ad5699c7..0f2a0eb60 100644 --- a/spring-batch-samples/src/main/resources/batch-derby.properties +++ b/spring-batch-samples/src/main/resources/batch-derby.properties @@ -1,7 +1,7 @@ # Placeholders batch.* # for Derby: batch.jdbc.driver=org.apache.derby.jdbc.EmbeddedDriver -batch.jdbc.url=jdbc:derby:derby.home/test;create=true +batch.jdbc.url=jdbc:derby:derby-home/test;create=true batch.jdbc.user=sa batch.jdbc.password= batch.schema=