RESOLVED - issue BATCH-546: pull duplicates from TaskletStep and ItemOrientedStep into AbstractStep

This commit is contained in:
robokaso
2008-04-04 14:57:27 +00:00
parent 646965b32d
commit 9a222286b8
6 changed files with 300 additions and 357 deletions

View File

@@ -15,13 +15,24 @@
*/
package org.springframework.batch.core.step;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Date;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobInterruptedException;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepExecutionListener;
import org.springframework.batch.core.UnexpectedJobExecutionException;
import org.springframework.batch.core.launch.support.ExitCodeMapper;
import org.springframework.batch.core.listener.CompositeStepExecutionListener;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.NoSuchJobException;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
@@ -35,6 +46,13 @@ import org.springframework.util.Assert;
*/
public abstract class AbstractStep implements Step, InitializingBean {
/**
* Exit code for interrupted status.
*/
public static final String JOB_INTERRUPTED = "JOB_INTERRUPTED";
private static final Log logger = LogFactory.getLog(AbstractStep.class);
protected String name;
protected int startLimit = Integer.MAX_VALUE;
@@ -42,7 +60,7 @@ public abstract class AbstractStep implements Step, InitializingBean {
protected boolean allowStartIfComplete;
private CompositeStepExecutionListener listener = new CompositeStepExecutionListener();
private JobRepository jobRepository;
/**
@@ -51,13 +69,11 @@ public abstract class AbstractStep implements Step, InitializingBean {
public AbstractStep() {
super();
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(jobRepository, "JobRepository is mandatory");
}
public String getName() {
return this.name;
}
@@ -107,8 +123,118 @@ public abstract class AbstractStep implements Step, InitializingBean {
this.name = name;
}
public abstract void execute(StepExecution stepExecution) throws JobInterruptedException,
UnexpectedJobExecutionException;
protected abstract ExitStatus doExecute(StepExecution stepExecution) throws Exception;
protected abstract void open(ExecutionContext ctx) throws Exception;
protected abstract void close(ExecutionContext ctx) throws Exception;
/**
* Template method for step execution logic - calls abstract methods for
* resource initialization ({@link #open(ExecutionContext)}), execution
* logic ({@link #doExecute(StepExecution)}) and resource closing ({@link #close(ExecutionContext)}).
*/
public void execute(StepExecution stepExecution) throws JobInterruptedException, UnexpectedJobExecutionException {
stepExecution.setStartTime(new Date());
stepExecution.setStatus(BatchStatus.STARTED);
ExitStatus exitStatus = ExitStatus.FAILED;
Exception commitException = null;
try {
getCompositeListener().beforeStep(stepExecution);
try {
open(stepExecution.getExecutionContext());
}
catch (Exception e) {
throw new UnexpectedJobExecutionException("Failed to initialize the step", e);
}
exitStatus = doExecute(stepExecution);
exitStatus = exitStatus.and(getCompositeListener().afterStep(stepExecution));
try {
getJobRepository().saveOrUpdateExecutionContext(stepExecution);
stepExecution.setStatus(BatchStatus.COMPLETED);
}
catch (Exception e) {
commitException = e;
stepExecution.setStatus(BatchStatus.UNKNOWN);
}
}
catch (Throwable e) {
logger.error("Encountered an error executing the step");
stepExecution.setStatus(determineBatchStatus(e));
exitStatus = getDefaultExitStatusForFailure(e);
try {
exitStatus = exitStatus.and(getCompositeListener().onErrorInStep(stepExecution, e));
}
catch (Exception ex) {
logger.error("Encountered an error on listener close.", ex);
}
rethrow(e);
}
finally {
stepExecution.setExitStatus(exitStatus);
stepExecution.setEndTime(new Date());
try {
getJobRepository().saveOrUpdate(stepExecution);
}
catch (Exception e) {
commitException = e;
}
try {
close(stepExecution.getExecutionContext());
}
catch (Exception e) {
logger.error("Exception while closing step's resources", e);
throw new UnexpectedJobExecutionException("Exception while closing step's resources", e);
}
if (commitException != null) {
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);
}
}
}
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(e);
}
/**
* Determine the step status based on the exception.
*/
private static BatchStatus determineBatchStatus(Throwable e) {
if (e instanceof FatalException) {
return BatchStatus.UNKNOWN;
}
else if (e instanceof JobInterruptedException || e.getCause() instanceof JobInterruptedException) {
return BatchStatus.STOPPED;
}
else {
return BatchStatus.FAILED;
}
}
/**
* Register a step listener for callbacks at the appropriate stages in a
@@ -137,7 +263,7 @@ public abstract class AbstractStep implements Step, InitializingBean {
protected StepExecutionListener getCompositeListener() {
return listener;
}
/**
* Public setter for {@link JobRepository}.
*
@@ -150,7 +276,44 @@ public abstract class AbstractStep implements Step, InitializingBean {
protected JobRepository getJobRepository() {
return jobRepository;
}
/**
* Default mapping from throwable to {@link ExitStatus}. Clients can modify
* the exit code using a {@link StepExecutionListener}.
*
* @param ex the cause of the failure
* @return an {@link ExitStatus}
*/
private ExitStatus getDefaultExitStatusForFailure(Throwable ex) {
ExitStatus exitStatus;
if (ex instanceof JobInterruptedException || ex.getCause() instanceof JobInterruptedException) {
exitStatus = new ExitStatus(false, JOB_INTERRUPTED, JobInterruptedException.class.getName());
}
else if (ex instanceof NoSuchJobException || ex.getCause() instanceof NoSuchJobException) {
exitStatus = new ExitStatus(false, ExitCodeMapper.NO_SUCH_JOB);
}
else {
String message = "";
if (ex != null) {
StringWriter writer = new StringWriter();
ex.printStackTrace(new PrintWriter(writer));
message = writer.toString();
}
exitStatus = ExitStatus.FAILED.addExitDescription(message);
}
return exitStatus;
}
/**
* Signals a fatal exception - e.g. unable to persist batch metadata or
* rollback transaction. Throwing this exception will result in storing
* {@link BatchStatus#UNKNOWN} as step's status.
*/
protected class FatalException extends RuntimeException {
public FatalException(String string, Exception e) {
super(string, e);
}
}
}

View File

@@ -15,26 +15,18 @@
*/
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;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobInterruptedException;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepExecutionListener;
import org.springframework.batch.core.UnexpectedJobExecutionException;
import org.springframework.batch.core.launch.support.ExitCodeMapper;
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.StepExecutionSynchronizer;
import org.springframework.batch.core.step.StepExecutionSynchronizerFactory;
import org.springframework.batch.core.step.StepInterruptionPolicy;
import org.springframework.batch.core.step.ThreadStepInterruptionPolicy;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.ItemWriter;
@@ -73,11 +65,6 @@ 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();
@@ -218,231 +205,98 @@ public class ItemOrientedStep extends AbstractStep {
* @throws JobInterruptedException if the step or a chunk is interrupted
* @throws RuntimeException if there is an exception during a chunk
* execution
* @see StepExecutor#execute(StepExecution)
*
*/
public void execute(final StepExecution stepExecution) throws UnexpectedJobExecutionException,
JobInterruptedException {
public ExitStatus doExecute(final StepExecution stepExecution) throws Exception {
stream.update(stepExecution.getExecutionContext());
getJobRepository().saveOrUpdateExecutionContext(stepExecution);
itemHandler.mark();
ExitStatus status = ExitStatus.FAILED;
final ExceptionHolder fatalException = new ExceptionHolder();
try {
return stepOperations.iterate(new RepeatCallback() {
stepExecution.setStartTime(new Date(System.currentTimeMillis()));
// We need to save the step execution right away, before we start
// using its ID. It would be better to make the creation atomic in
// the caller.
fatalException.setException(updateStatus(stepExecution, BatchStatus.STARTED));
public ExitStatus doInIteration(RepeatContext context) throws Exception {
final StepContribution contribution = stepExecution.createStepContribution();
// Before starting a new transaction, check for
// interruption.
if (stepExecution.isTerminateOnly()) {
context.setTerminateOnly();
}
interruptionPolicy.checkInterrupted(stepExecution);
// Execute step level listeners *after* the execution context is
// fixed in the step. E.g. ItemStream instances need the the same
// reference to the ExecutionContext as the step execution.
getCompositeListener().beforeStep(stepExecution);
stream.open(stepExecution.getExecutionContext());
stream.update(stepExecution.getExecutionContext());
getJobRepository().saveOrUpdateExecutionContext(stepExecution);
itemHandler.mark();
ExitStatus exitStatus = ExitStatus.CONTINUABLE;
status = stepOperations.iterate(new RepeatCallback() {
TransactionStatus transaction = transactionManager.getTransaction(new DefaultTransactionDefinition());
public ExitStatus doInIteration(final RepeatContext context) throws Exception {
try {
final StepContribution contribution = stepExecution.createStepContribution();
// Before starting a new transaction, check for
// interruption.
if (stepExecution.isTerminateOnly()) {
context.setTerminateOnly();
}
interruptionPolicy.checkInterrupted(stepExecution);
ExitStatus result = ExitStatus.CONTINUABLE;
TransactionStatus transaction = transactionManager
.getTransaction(new DefaultTransactionDefinition());
exitStatus = processChunk(stepExecution, contribution);
contribution.incrementCommitCount();
// If the step operations are asynchronous then we need
// to synchronize changes to the step execution (at a
// minimum).
try {
result = processChunk(stepExecution, contribution);
contribution.incrementCommitCount();
// If the step operations are asynchronous then we need
// to synchronize changes to the step execution (at a
// minimum).
try {
synchronizer.lock(stepExecution);
}
catch (InterruptedException e) {
stepExecution.setStatus(BatchStatus.STOPPED);
Thread.currentThread().interrupt();
}
// Apply the contribution to the step
// only if chunk was successful
stepExecution.apply(contribution);
// Attempt to flush before the step execution and stream
// state are updated
itemHandler.flush();
stream.update(stepExecution.getExecutionContext());
try {
getJobRepository().saveOrUpdateExecutionContext(stepExecution);
}
catch (Exception e) {
fatalException.setException(e);
stepExecution.setStatus(BatchStatus.UNKNOWN);
throw new CommitFailedException(
"Fatal error detected during save of step execution context", e);
}
try {
itemHandler.mark();
transactionManager.commit(transaction);
}
catch (Exception e) {
fatalException.setException(e);
stepExecution.setStatus(BatchStatus.UNKNOWN);
throw new CommitFailedException("Fatal error detected during commit", e);
}
synchronizer.lock(stepExecution);
}
catch (Error e) {
processRollback(stepExecution, contribution, fatalException, transaction);
throw e;
catch (InterruptedException e) {
stepExecution.setStatus(BatchStatus.STOPPED);
Thread.currentThread().interrupt();
}
// Apply the contribution to the step
// only if chunk was successful
stepExecution.apply(contribution);
// Attempt to flush before the step execution and stream
// state are updated
itemHandler.flush();
stream.update(stepExecution.getExecutionContext());
try {
getJobRepository().saveOrUpdateExecutionContext(stepExecution);
}
catch (Exception e) {
processRollback(stepExecution, contribution, fatalException, transaction);
throw e;
}
finally {
synchronizer.release(stepExecution);
fatalException.setException(e);
stepExecution.setStatus(BatchStatus.UNKNOWN);
throw new FatalException("Fatal error detected during save of step execution context", e);
}
// Check for interruption after transaction as well, so that
// the interrupted exception is correctly propagated up to
// caller
interruptionPolicy.checkInterrupted(stepExecution);
return result;
try {
itemHandler.mark();
transactionManager.commit(transaction);
}
catch (Exception e) {
fatalException.setException(e);
stepExecution.setStatus(BatchStatus.UNKNOWN);
logger.error("Fatal error detected during commit.");
throw new FatalException("Fatal error detected during commit", e);
}
}
});
status = status.and(getCompositeListener().afterStep(stepExecution));
fatalException.setException(updateStatus(stepExecution, BatchStatus.COMPLETED));
}
catch (CommitFailedException e) {
logger.error("Fatal error detected during commit.");
throw e;
}
catch (RuntimeException e) {
status = processFailure(stepExecution, fatalException, e);
if (e.getCause() instanceof JobInterruptedException) {
updateStatus(stepExecution, BatchStatus.STOPPED);
throw (JobInterruptedException) e.getCause();
}
throw e;
}
catch (Error e) {
status = processFailure(stepExecution, fatalException, e);
throw e;
}
finally {
stepExecution.setExitStatus(status);
stepExecution.setEndTime(new Date(System.currentTimeMillis()));
try {
getJobRepository().saveOrUpdate(stepExecution);
}
catch (RuntimeException e) {
String msg = "Fatal error detected during final save of meta data";
logger.error(msg, e);
if (!fatalException.hasException()) {
fatalException.setException(e);
catch (Error e) {
processRollback(stepExecution, contribution, fatalException, transaction);
throw e;
}
throw new UnexpectedJobExecutionException(msg, fatalException.getException());
}
try {
stream.close(stepExecution.getExecutionContext());
}
catch (RuntimeException e) {
String msg = "Fatal error detected during close of streams. "
+ "The job execution completed (possibly unsuccessfully but with consistent meta-data).";
logger.error(msg, e);
if (!fatalException.hasException()) {
fatalException.setException(e);
catch (Exception e) {
processRollback(stepExecution, contribution, fatalException, transaction);
throw e;
}
throw new UnexpectedJobExecutionException(msg, fatalException.getException());
finally {
synchronizer.release(stepExecution);
}
// Check for interruption after transaction as well, so that
// the interrupted exception is correctly propagated up to
// caller
interruptionPolicy.checkInterrupted(stepExecution);
return exitStatus;
}
if (fatalException.hasException()) {
throw new UnexpectedJobExecutionException("Encountered an error saving batch meta data.",
fatalException.getException());
}
});
}
}
/**
* @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) {
// 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(getCompositeListener().onErrorInStep(stepExecution, e));
}
catch (RuntimeException ex) {
logger.error("Unexpected error in listener on error in step.", ex);
}
updateStatus(stepExecution, BatchStatus.FAILED);
}
else {
logger.error("Fatal error detected during rollback caused by underlying exception: ", e);
}
return status;
}
/**
* Default mapping from throwable to {@link ExitStatus}. Clients can modify
* the exit code using a {@link StepExecutionListener}.
*
* @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;
}
/**
@@ -475,25 +329,6 @@ public class ItemOrientedStep extends AbstractStep {
return result;
}
/**
* Convenience method to update the status in all relevant places.
*
* @param stepInstance the current step
* @param stepExecution the current stepExecution
* @param status the status to set
*/
private Exception updateStatus(StepExecution stepExecution, BatchStatus status) {
stepExecution.setStatus(status);
try {
getJobRepository().saveOrUpdate(stepExecution);
return null;
}
catch (Exception e) {
return e;
}
}
/**
* @param stepExecution
* @param contribution
@@ -522,7 +357,7 @@ public class ItemOrientedStep extends AbstractStep {
*/
if (!fatalException.hasException()) {
fatalException.setException(e);
stepExecution.setStatus(BatchStatus.UNKNOWN);
throw new FatalException("Failed while processing rollback", e);
}
}
}
@@ -545,11 +380,11 @@ public class ItemOrientedStep extends AbstractStep {
}
private class CommitFailedException extends RuntimeException {
public CommitFailedException(String string, Exception e) {
super(string, e);
}
protected void close(ExecutionContext ctx) throws Exception {
stream.close(ctx);
}
protected void open(ExecutionContext ctx) throws Exception {
stream.open(ctx);
}
}

View File

@@ -15,21 +15,14 @@
*/
package org.springframework.batch.core.step.tasklet;
import java.util.Date;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.UnexpectedJobExecutionException;
import org.springframework.batch.core.JobInterruptedException;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepExecutionListener;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.AbstractStep;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
/**
@@ -45,9 +38,7 @@ import org.springframework.util.Assert;
* @author Ben Hale
* @author Robert Kasanicky
*/
public class TaskletStep extends AbstractStep implements Step, InitializingBean, BeanNameAware {
private static final Log logger = LogFactory.getLog(TaskletStep.class);
public class TaskletStep extends AbstractStep implements BeanNameAware {
private Tasklet tasklet;
@@ -116,62 +107,21 @@ public class TaskletStep extends AbstractStep implements Step, InitializingBean,
this.tasklet = tasklet;
}
public void execute(StepExecution stepExecution) throws JobInterruptedException, UnexpectedJobExecutionException {
stepExecution.setStartTime(new Date());
stepExecution.setStatus(BatchStatus.STARTED);
ExitStatus exitStatus = ExitStatus.FAILED;
Exception fatalException = null;
try {
getCompositeListener().beforeStep(stepExecution);
exitStatus = tasklet.execute();
exitStatus = exitStatus.and(getCompositeListener().afterStep(stepExecution));
try {
getJobRepository().saveOrUpdateExecutionContext(stepExecution);
stepExecution.setStatus(BatchStatus.COMPLETED);
}
catch (Exception e) {
fatalException = e;
stepExecution.setStatus(BatchStatus.UNKNOWN);
}
}
catch (Exception e) {
logger.error("Encountered an error running the tasklet");
stepExecution.setStatus(BatchStatus.FAILED);
try {
exitStatus = exitStatus.and(getCompositeListener().onErrorInStep(stepExecution, e));
}
catch (Exception ex) {
logger.error("Encountered an error on listener close.", ex);
}
if (e instanceof JobInterruptedException) {
throw (JobInterruptedException) e;
}
else if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw new UnexpectedJobExecutionException(e);
}
finally {
stepExecution.setExitStatus(exitStatus);
stepExecution.setEndTime(new Date());
try {
getJobRepository().saveOrUpdate(stepExecution);
}
catch (Exception e) {
fatalException = e;
}
if (fatalException != null) {
logger.error("Encountered an error saving batch meta data."
+ "This job is now in an unknown state and should not be restarted.", fatalException);
throw new UnexpectedJobExecutionException("Encountered an error saving batch meta data.",
fatalException);
}
}
/**
* Delegate to tasklet.
*/
protected ExitStatus doExecute(StepExecution stepExecution) throws Exception {
return tasklet.execute();
}
protected void close(ExecutionContext ctx) throws Exception {
}
protected void open(ExecutionContext ctx) throws Exception {
}
}