This isn't as nice or as elegant as I'd want it to be, but to get access to the job execution ID so that I might link up that ID with an error that was encountered during a step in that job's execution, I did the following:
- wrote an error row to my DB in onWriteError
- Saved off the ID of the error_row I just wrote...stored that ID in a ThreadLocal ArrayList in my listener
- In the afterStep() method (which does have access to the job execution object), I ran through the accumulated error rows and updated them with the job_execution_id
Here's the code, in case my description doesn't make sense. Not guaranteeing this is bug proof, as I haven't stressed it or reviewed it much yet. It seems to work on first blush, though:
Code:
public class AlertErrorClassifyingStepListener extends StepListenerSupport<Long, Long> {
private CustomerAlertInstanceErrorDAO customerAlertInstanceErrorDAO;
protected final Log log = LogFactory.getLog(this.getClass());
/**
* <p>Using a ThreadLocal array list here because this StepListener is a singleton shared by multiple running Alert Batch
* Jobs in multiple running threads. For now, each job is single-threaded. Thus, any error we detect in the
* onWriteError() method can be added to this thread local list so that when the step is complete we can update the
* database in the afterStep() method to let us know what job execution ID the error happened in.</p>
*
* <p>We want to tie step errors with job execution IDs for display and management purposes. Note that the Long values in
* this array list are the customer_alert_instance_error.id fields, and <strong>not</strong>
* the customer_alert_instance_error.customer_alert_instance_id fields.</p>
*
* <p>This data is garbage collected when the thread running the current job dies.</p>
*/
private ThreadLocal<ArrayList<Long>> alertErrorThreadLocalList = new ThreadLocal<ArrayList<Long>>() {
@Override
protected ArrayList<Long> initialValue() {
return new ArrayList<Long>();
}
};
/**
* <p>Update our customer alert instance error data store with the currently running job's execution ID for
* every error our onWriteError() method saved off to the thread-local alertErrorThreadLocalList array</p>
* @see org.springframework.batch.core.StepExecutionListener#afterStep(org.springframework.batch.core.StepExecution)
*/
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
Long jobExecutionId = stepExecution.getJobExecutionId();
for(Long errorId : alertErrorThreadLocalList.get()) {
customerAlertInstanceErrorDAO.updateErrorWithJobExecution(errorId, jobExecutionId);
}
debug(log, "in afterStep for jobExecution Id %d. New errors in the customer_alert_instance table written during " +
"this step are %s", jobExecutionId, StringUtils.join(alertErrorThreadLocalList.get(), " "));
return super.afterStep(stepExecution);
}
@Override
public ExitStatus onErrorInStep(StepExecution stepExecution, Throwable e) {
return super.onErrorInStep(stepExecution, e);
}
/**
* This is run within the transaction of the failed alert job, which will be rolled back.
* So run this method in a new transaction.
* See http://static.springframework.org/spring-batch/spring-batch-docs/reference/html-single/index.html#d0e4511
* @see org.springframework.batch.core.ItemWriteListener#onWriteError(java.lang.Exception, java.lang.Object)
*/
@Override
@Transactional(propagation = REQUIRES_NEW)
public void onWriteError(Exception ex, List<? extends Long> items) {
CustomerAlertInstanceError.SkipReason skipReason = translateException(ex);
debug(log, "Requesting alertErrorDAO logging of exception of type %s", skipReason.name());
Long caiErrorId = customerAlertInstanceErrorDAO.logError(items.get(0), skipReason, ex);
this.alertErrorThreadLocalList.get().add(caiErrorId);
}
private CustomerAlertInstanceError.SkipReason translateException(final Exception ex) {
// ...does stuff
}
@Autowired
public void setCustomerAlertInstanceErrorDAO(CustomerAlertInstanceErrorDAO customerAlertInstanceErrorDAO) {
this.customerAlertInstanceErrorDAO = customerAlertInstanceErrorDAO;
}
}