OPEN - issue BATCH-40: An application developer must be able to control the 'exit codes' returned by the container

http://opensource.atlassian.com/projects/spring/browse/BATCH-40
This commit is contained in:
lucasward
2007-09-14 00:13:27 +00:00
parent 5a447885f7
commit d224821e52
6 changed files with 281 additions and 63 deletions

View File

@@ -39,16 +39,17 @@ import org.springframework.util.StringUtils;
/**
* Sql implementation of StepDao. Uses Sequences (via Spring's
* @link DataFieldMaxValueIncrementer abstraction) to create all Step and
* StepExecution primary keys before inserting a new row. All objects are
* checked to ensure all fields to be stored are not null. If any are found to
* be null, an IllegalArgumentException will be thrown. This could be left to
* JdbcTemplate, however, the exception will be fairly vague, and fails to
* highlight which field caused the exception.
*
* TODO: JavaDoc should be geared more towards usability, the comments
* above are useful information, and should be there, but needs usability
* stuff. Depends on the step dao java docs as well.
* @link DataFieldMaxValueIncrementer abstraction) to create all Step and
* StepExecution primary keys before inserting a new row. All objects are
* checked to ensure all fields to be stored are not null. If any are
* found to be null, an IllegalArgumentException will be thrown. This
* could be left to JdbcTemplate, however, the exception will be fairly
* vague, and fails to highlight which field caused the exception.
*
* TODO: JavaDoc should be geared more towards usability, the comments above are
* useful information, and should be there, but needs usability stuff. Depends
* on the step dao java docs as well.
*
* @author Lucas Ward
* @see StepDao
@@ -67,16 +68,18 @@ public class SqlStepDao implements StepDao, InitializingBean {
// StepExecution statements
private static final String SAVE_STEP_EXECUTION = "INSERT into %PREFIX%STEP_EXECUTION(ID, VERSION, STEP_ID, JOB_EXECUTION_ID, START_TIME, "
+ "END_TIME, STATUS, COMMIT_COUNT, TASK_COUNT, TASK_STATISTICS, EXIT_CODE) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+ "END_TIME, STATUS, COMMIT_COUNT, TASK_COUNT, TASK_STATISTICS, EXIT_CODE, EXIT_MESSAGE) "
+ "values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
private static final String UPDATE_STEP_EXECUTION = "UPDATE %PREFIX%STEP_EXECUTION set START_TIME = ?, END_TIME = ?, "
+ "STATUS = ?, COMMIT_COUNT = ?, TASK_COUNT = ?, TASK_STATISTICS = ?, EXIT_CODE = ? where ID = ?";
+ "STATUS = ?, COMMIT_COUNT = ?, TASK_COUNT = ?, TASK_STATISTICS = ?, EXIT_CODE = ?, "
+ "EXIT_MESSAGE = ? where ID = ?";
private static final String GET_STEP_EXECUTION_COUNT = "SELECT count(ID) from %PREFIX%STEP_EXECUTION where "
+ "STEP_ID = ?";
private static final String FIND_STEP_EXECUTIONS = "SELECT ID, JOB_EXECUTION_ID, START_TIME, END_TIME, STATUS, COMMIT_COUNT,"
+ " TASK_COUNT, TASK_STATISTICS, EXIT_CODE from %PREFIX%STEP_EXECUTION where STEP_ID = ?";
+ " TASK_COUNT, TASK_STATISTICS, EXIT_CODE, EXIT_MESSAGE from %PREFIX%STEP_EXECUTION where STEP_ID = ?";
private JdbcOperations jdbcTemplate;
@@ -105,8 +108,10 @@ public class SqlStepDao implements StepDao, InitializingBean {
* anymore than one step is found, an exception is thrown.
*
* @see StepDao#findStep(Long, String)
* @throws IllegalArgumentException if job, stepName, or job.id is null.
* @throws NoSuchBatchDomainObjectException if more than one step is found.
* @throws IllegalArgumentException
* if job, stepName, or job.id is null.
* @throws NoSuchBatchDomainObjectException
* if more than one step is found.
*/
public StepInstance findStep(JobInstance job, String stepName) {
@@ -122,30 +127,30 @@ public class SqlStepDao implements StepDao, InitializingBean {
StepInstance step = new StepInstance(new Long(rs.getLong(1)));
step.setStatus(BatchStatus.getStatus(rs.getString(2)));
step.setRestartData(
new GenericRestartData(PropertiesConverter.stringToProperties(rs.getString(3))));
step.setRestartData(new GenericRestartData(PropertiesConverter
.stringToProperties(rs.getString(3))));
return step;
}
};
List steps = jdbcTemplate.query(getQuery(FIND_STEP), parameters, rowMapper);
List steps = jdbcTemplate.query(getQuery(FIND_STEP), parameters,
rowMapper);
if (steps.size() == 0) {
// No step found
return null;
}
else if (steps.size() == 1) {
} else if (steps.size() == 1) {
StepInstance step = (StepInstance) steps.get(0);
step.setName(stepName);
return step;
}
else {
} else {
// This error will likely never be thrown, because there should
// never be two steps with the same name and Job_ID due to database
// constraints.
throw new NoSuchBatchDomainObjectException("Step Invalid, multiple steps found for StepName:" + stepName
+ " and JobId:" + job.getId());
throw new NoSuchBatchDomainObjectException(
"Step Invalid, multiple steps found for StepName:"
+ stepName + " and JobId:" + job.getId());
}
}
@@ -156,7 +161,8 @@ public class SqlStepDao implements StepDao, InitializingBean {
* Sql implementation which uses a RowMapper to populate a list of all rows
* in the step table with the same JOB_ID.
*
* @throws IllegalArgumentException if jobId is null.
* @throws IllegalArgumentException
* if jobId is null.
*/
public List findSteps(Long jobId) {
@@ -172,8 +178,8 @@ public class SqlStepDao implements StepDao, InitializingBean {
step.setName(rs.getString(2));
String status = rs.getString(3);
step.setStatus(BatchStatus.getStatus(status));
step.setRestartData(
new GenericRestartData(PropertiesConverter.stringToProperties(rs.getString(3))));
step.setRestartData(new GenericRestartData(PropertiesConverter
.stringToProperties(rs.getString(3))));
return step;
}
};
@@ -187,7 +193,8 @@ public class SqlStepDao implements StepDao, InitializingBean {
* DataFieldMaxValueIncrementer)
*
* @see StepDao#createStep(JobInstance, String)
* @throws IllegalArgumentException if job or stepName is null.
* @throws IllegalArgumentException
* if job or stepName is null.
*/
public StepInstance createStep(JobInstance job, String stepName) {
@@ -206,7 +213,8 @@ public class SqlStepDao implements StepDao, InitializingBean {
/**
* @see StepDao#update(StepInstance)
* @throws IllegalArgumentException if step, or it's status and id is null.
* @throws IllegalArgumentException
* if step, or it's status and id is null.
*/
public void update(final StepInstance step) {
@@ -219,12 +227,11 @@ public class SqlStepDao implements StepDao, InitializingBean {
if (restartData != null) {
restartProps = restartData.getProperties();
}
Object[] parameters = new Object[]{ step.getStatus().toString(),
Object[] parameters = new Object[] { step.getStatus().toString(),
PropertiesConverter.propertiesToString(restartProps),
step.getId()
};
step.getId() };
jdbcTemplate.update(getQuery(UPDATE_STEP), parameters);
}
@@ -240,10 +247,19 @@ public class SqlStepDao implements StepDao, InitializingBean {
validateStepExecution(stepExecution);
stepExecution.setId(new Long(stepExecutionIncrementer.nextLongValue()));
Object[] parameters = new Object[] { stepExecution.getId(), new Long(0), stepExecution.getStepId(), stepExecution.getJobExecutionId(),
stepExecution.getStartTime(), stepExecution.getEndTime(), stepExecution.getStatus().toString(),
stepExecution.getCommitCount(), stepExecution.getTaskCount(),
PropertiesConverter.propertiesToString(stepExecution.getStatistics()), stepExecution.getExitCode() };
Object[] parameters = new Object[] {
stepExecution.getId(),
new Long(0),
stepExecution.getStepId(),
stepExecution.getJobExecutionId(),
stepExecution.getStartTime(),
stepExecution.getEndTime(),
stepExecution.getStatus().toString(),
stepExecution.getCommitCount(),
stepExecution.getTaskCount(),
PropertiesConverter.propertiesToString(stepExecution
.getStatistics()), stepExecution.getExitCode(),
stepExecution.getExitDescription() };
jdbcTemplate.update(getQuery(SAVE_STEP_EXECUTION), parameters);
}
@@ -254,8 +270,9 @@ public class SqlStepDao implements StepDao, InitializingBean {
public void update(StepExecution stepExecution) {
validateStepExecution(stepExecution);
Assert.notNull(stepExecution.getId(), "StepExecution Id cannot be null. StepExecution must saved"
+ " before it can be updated.");
Assert.notNull(stepExecution.getId(),
"StepExecution Id cannot be null. StepExecution must saved"
+ " before it can be updated.");
// TODO: Not sure if this is a good idea on step execution considering
// it is saved at every commit
@@ -265,10 +282,15 @@ public class SqlStepDao implements StepDao, InitializingBean {
// return; // throw exception?
// }
Object[] parameters = new Object[] { stepExecution.getStartTime(), stepExecution.getEndTime(),
stepExecution.getStatus().toString(), stepExecution.getCommitCount(),
stepExecution.getTaskCount(), PropertiesConverter.propertiesToString(stepExecution.getStatistics()),
stepExecution.getExitCode(),
Object[] parameters = new Object[] {
stepExecution.getStartTime(),
stepExecution.getEndTime(),
stepExecution.getStatus().toString(),
stepExecution.getCommitCount(),
stepExecution.getTaskCount(),
PropertiesConverter.propertiesToString(stepExecution
.getStatistics()), stepExecution.getExitCode(),
stepExecution.getExitDescription(),
stepExecution.getId() };
jdbcTemplate.update(getQuery(UPDATE_STEP_EXECUTION), parameters);
@@ -278,7 +300,8 @@ public class SqlStepDao implements StepDao, InitializingBean {
Object[] parameters = new Object[] { stepId };
return jdbcTemplate.queryForInt(getQuery(GET_STEP_EXECUTION_COUNT), parameters);
return jdbcTemplate.queryForInt(getQuery(GET_STEP_EXECUTION_COUNT),
parameters);
}
/**
@@ -286,9 +309,10 @@ public class SqlStepDao implements StepDao, InitializingBean {
* they will not be returned with reconstituted object.
*
* @see StepDao#getStepExecution(Long)
* @throws IllegalArgumentException if id is null.
* @throws NoSuchBatchDomainObjectException if more than one step execution is
* returned.
* @throws IllegalArgumentException
* if id is null.
* @throws NoSuchBatchDomainObjectException
* if more than one step execution is returned.
*/
public List findStepExecutions(StepInstance step) {
@@ -300,20 +324,24 @@ public class SqlStepDao implements StepDao, InitializingBean {
RowMapper rowMapper = new RowMapper() {
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
StepExecution stepExecution = new StepExecution(stepId, new Long(rs.getLong(2)));
StepExecution stepExecution = new StepExecution(stepId,
new Long(rs.getLong(2)));
stepExecution.setId(new Long(rs.getLong(1)));
stepExecution.setStartTime(rs.getTimestamp(3));
stepExecution.setEndTime(rs.getTimestamp(4));
stepExecution.setStatus(BatchStatus.getStatus(rs.getString(5)));
stepExecution.setCommitCount(rs.getInt(6));
stepExecution.setTaskCount(rs.getInt(7));
stepExecution.setStatistics(PropertiesConverter.stringToProperties(rs.getString(8)));
stepExecution.setStatistics(PropertiesConverter
.stringToProperties(rs.getString(8)));
stepExecution.setExitCode(rs.getString(9));
stepExecution.setExitDescription(rs.getString(10));
return stepExecution;
}
};
return jdbcTemplate.query(getQuery(FIND_STEP_EXECUTIONS), new Object[] { stepId }, rowMapper);
return jdbcTemplate.query(getQuery(FIND_STEP_EXECUTIONS),
new Object[] { stepId }, rowMapper);
}
@@ -325,32 +353,37 @@ public class SqlStepDao implements StepDao, InitializingBean {
this.stepIncrementer = stepIncrementer;
}
public void setStepExecutionIncrementer(DataFieldMaxValueIncrementer stepExecutionIncrementer) {
public void setStepExecutionIncrementer(
DataFieldMaxValueIncrementer stepExecutionIncrementer) {
this.stepExecutionIncrementer = stepExecutionIncrementer;
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(jdbcTemplate, "JdbcTemplate cannot be null.");
Assert.notNull(stepIncrementer, "StepIncrementer cannot be null.");
Assert.notNull(stepExecutionIncrementer, "StepExecutionIncrementer canot be null.");
Assert.notNull(stepExecutionIncrementer,
"StepExecutionIncrementer canot be null.");
}
private String getQuery(String base) {
return StringUtils.replace(base, "%PREFIX%", tablePrefix);
}
/*
* Validate StepExecution. At a minimum, JobId, StartTime, and
* Status cannot be null. EndTime can be null for an unfinished job.
* Validate StepExecution. At a minimum, JobId, StartTime, and Status cannot
* be null. EndTime can be null for an unfinished job.
*
* @param jobExecution @throws IllegalArgumentException
*/
private void validateStepExecution(StepExecution stepExecution) {
Assert.notNull(stepExecution);
Assert.notNull(stepExecution.getStepId(), "StepExecution Step-Id cannot be null.");
Assert.notNull(stepExecution.getStartTime(), "StepExecution start time cannot be null.");
Assert.notNull(stepExecution.getStatus(), "StepExecution status cannot be null.");
Assert.notNull(stepExecution.getStepId(),
"StepExecution Step-Id cannot be null.");
Assert.notNull(stepExecution.getStartTime(),
"StepExecution start time cannot be null.");
Assert.notNull(stepExecution.getStatus(),
"StepExecution status cannot be null.");
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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.execution.step.simple;
import org.springframework.batch.core.executor.ExitCodeExceptionClassifier;
import org.springframework.batch.repeat.ExitStatus;
/**
* Simple implementation of {@link ExitCodeExceptionClassifier} 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 SimpleExitCodeExceptionClassifier implements
ExitCodeExceptionClassifier {
/* (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 = new ExitStatus(false,
FATAL_EXCEPTION, throwable == null ? "" : throwable.getClass().getName());
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);
}
}

View File

@@ -24,6 +24,7 @@ import org.springframework.batch.core.configuration.StepConfiguration;
import org.springframework.batch.core.domain.BatchStatus;
import org.springframework.batch.core.domain.StepExecution;
import org.springframework.batch.core.domain.StepInstance;
import org.springframework.batch.core.executor.ExitCodeExceptionClassifier;
import org.springframework.batch.core.executor.StepExecutor;
import org.springframework.batch.core.executor.StepInterruptedException;
import org.springframework.batch.core.repository.JobRepository;
@@ -94,6 +95,8 @@ public class SimpleStepExecutor implements StepExecutor {
private RepeatOperations stepOperations = new RepeatTemplate();
private JobRepository jobRepository;
private ExitCodeExceptionClassifier exceptionClassifier = new SimpleExitCodeExceptionClassifier();
// default to checking current thread for interruption.
private StepInterruptionPolicy interruptionPolicy = new ThreadStepInterruptionPolicy();
@@ -246,13 +249,13 @@ public class SimpleStepExecutor implements StepExecutor {
});
stepExecution.setExitCode(status.getExitCode());
stepExecution.setExitDescription(status.getExitDescription());
updateStatus(stepExecutionContext, BatchStatus.COMPLETED);
return status;
}
catch (RuntimeException e) {
//classify exception so an exit code can be stored.
status = exceptionClassifier.classifyForExitCode(e);
stepExecution.setException(e);
if (e.getCause() instanceof StepInterruptedException) {
updateStatus(stepExecutionContext, BatchStatus.STOPPED);
@@ -262,8 +265,11 @@ public class SimpleStepExecutor implements StepExecutor {
updateStatus(stepExecutionContext, BatchStatus.FAILED);
throw e;
}
}
finally {
stepExecution.setExitCode(status.getExitCode());
stepExecution.setExitDescription(status.getExitDescription());
stepExecution.setEndTime(new Timestamp(System.currentTimeMillis()));
try {
jobRepository.saveOrUpdate(stepExecution);
@@ -378,4 +384,15 @@ public class SimpleStepExecutor implements StepExecutor {
public void setInterruptionPolicy(StepInterruptionPolicy interruptionPolicy) {
this.interruptionPolicy = interruptionPolicy;
}
/**
* Setter for the {@link ExitCodeExceptionClassifier} that will be used
* to classify any exception that causes a job to fail.
*
* @param exceptionClassifier
*/
public void setExceptionClassifier(
ExitCodeExceptionClassifier exceptionClassifier) {
this.exceptionClassifier = exceptionClassifier;
}
}