RESOLVED - issue BATCH-467: Remove ExitStatusExceptionClassifier / Rewrite SimpleExitStatusExceptionClassifier as a JobListener
Removed the interface and implementation, checked that defautl behaviour is same using unit test ItemOrientedStepTests
This commit is contained in:
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(), "");
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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=
|
||||
|
||||
Reference in New Issue
Block a user