Spring Batch: Listener event when Tasklet throws an exception - spring-batch

I'm using a tasklet and a StepExecutionListener but it seems there's no listener callback for a case where my tasklet throws an exception. For various other listener types – ChunkListener, ItemProcessListener, etc. – there is but none of those listeners work with tasklets.
All I want is an event after my tasklet executes regardless of whether it threw an exception or not. Is it possible to do that? It doesn't appear to be supported in the API.
Edit: Responding to #danidemi I'm registering the listener and tasklet using the programmatic API like this:
steps.get(name)
.listener(listener)
.tasklet(tasklet)
.build()
Where steps is an instance of StepBuilderFactory.

You can
manage exception into tasklet
store error into execution-context/external bean
manage error from stepExecutionListener
or in StepExecutionListener.afterStep(StepExecution stepExecution) lookup into stepExecution.getFailureExceptions()

I guess its too late now. But I recently came across this problem. It is easier to handle exception with ChunkListener, however exception handling can be done in Tasklet's RepeatStatus execute(StepContribution s, ChunkContext chunkContext) method (well, it is the only method that Tasklet interface has ^^). What you need is a try/catch block to catch the exceptions. However you will need to throw the caught exception again in order to roll back the transaction.
Here is a code-snippet. In my case, I had to stop the job if some data could not be read due to Database server shutdown.
#Override
public RepeatStatus execute(StepContribution s, ChunkContext chunkContext){
try{
getAllUsersFromDb(); // some operation that could throw an exception
// doesn't hurt to put all suspicious codes in this block tbh
}catch(Exception e){
if(e instanceof NonSkippableReadException){
chunkContext.getStepContext().getStepExecution().getJobExecution().stop();
}
throw e;
}
return RepeatStatus.FINISHED;
}

Related

Spring Batch skip exception and rollback in Tasklet

I want to achieve a tasklet that can skip exceptions and rollback the transaction properly and I don't see a way to accomplish both things.
My tasklet reads from a queue of ids that gets filled in the constructor. In each invocation of the execute method one id is processed and depending on if the queue still has elements to be processed or not a RepeatStatus.FINISHED or RepeatStatus.CONTINUABLE is returned. I am using a tasklet instead of a chunk because the processing of each element is fairly complicated and implies doing multiple queries, instantiation of a lot of objects that all gets written to the database later.
The main problem is if I define a try/catch block to wrap the implementation, I can skip exceptions without problems and still be able to re-execute the tasklet with the next element in the queue, but the problem is that everything gets saved in the database. On the other hand, even if the processing of an element is done correctly without problems, if the commit fails for whatever reason, as the error occurs outside the reach and control of my code, the exception is not caught by my code and the tasklet execution is finished without the possibility to skip and continue with the next element of the queue.
This is a simplified schema of my tasklet:
public MyTasklet() {
elementsIds = myRepo.findProcessableElements();
}
#Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) {
Long id = elementsIds.remove();
try {
// Business logic
} catch (Exception e) {
// is there a way to tell the framework to rollback ?
LOG.error("error ...", e);
}
if (elementsIds.isEmpty()) {
return RepeatStatus.FINISHED;
} else {
return RepeatStatus.CONTINUABLE;
}
}
Is there a way to achieve these two requirements with tasklets:
To be able to tell the framework to rollback the transaction if an exception is caught in the implementation of the execute method
To continue the execution (consecutive calls) of the tasklet if a commit fails

access chunkContext within a step

I'd like to use the value of
chunkContext.getStepContext().getStepExecution().getReadCount();
, which represents the record # that I'm reading->processing->writing, within my processor.
I'm having trouble getting my chunkContext available. How do I make chunkContext available in my processor?
I assumed it was autowired into the job, but I might not be doing that correctly. I'm new to this sort of advanced java stuff, would appreciate any help (and patience)!
You can add below conde in processor to access StepExecution
#BeforeStep
public void beforeStep(StepExecution stepExecution) {
}

How to check whether SOAP fault is being handled gracefully?

I'm using JUnit and Mockito in order to test whether my SOAP web service handles SOAP faults gracefully and doesn't throw any unwanted exceptions for example.
So up until now, as you can see from the code below, I'm only testing whether a SOAPFaultException is being thrown (of course it does, I threw it). I wonder how I could check though whether any other exception would be thrown when receiving the SOAP fault.
Also is there any way to mock a SOAP fault without throwing an exception (SOAPFaultException)?
public class SOAPFaultsTest {
private MyObj myObj = (MyObj) mock(IMockClass.class);
#Before
public void create() {
SOAPFault soapFault = null;
try {
soapFault = SOAPFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL).createFault();
soapFault.setFaultString("unable to create new native thread");
soapFault.setFaultCode(QName.valueOf("soap:Server"));
} catch (SOAPException e) {
e.printStackTrace();
}
// Define behaviour of myObj mock object
when(myObj.randomMethod(any(RandomClass.class))).thenThrow(new SOAPFaultException(soapFault));
}
// Here I'm testing whether invoking myObj's randomMethod with a RandomClass object as an argument throws a SOAPFaultException.
// It does because this is how I defined its behaviour.
// What I really want to test is whether receiving a SOAP fault at any time is going to cause any trouble.
#Test(expected=SOAPFaultException.class)
public void testSOAPException() throws SOAPFaultException {
RandomClass rc = new RandomClass();
myObj.randomMethod(rc);
}
}
I suggest you go with a full-stack mock (i.e. spawn an Endpoint on a local socket, point the client there). Then create a soap fault and let the mock throw an appropriate exception over the wire. If you're using CXF, I've created a simple JUnit Rule which does this, see the test method SoapServiceRuleTest.processSoapCallWithException().
As a general strategy, I suggest you make an abstract 'happy case' unit test which you then sabotage one call at a time by doing reset on the mock with each test method and adding thenThrow(..) correspondingly.

In spring-batch, how can I get the exception when there is a chunk error?

Spring batch provides a listener for capturing when a chunk error has occurred (either the #AfterChunkError annotation or the ChunkListener.afterChunkError interface). Both receive the ChunkContext and the API says:
Parameters:
context - the chunk context containing the exception that caused the
underlying rollback.
However, I don't see anything on the ChunkContext interface that would get me to the exception. How do I get from the ChunkContext to the relevant exception?
Exception is located in ChunkListener.ROLLBACK_EXCEPTION_KEY attribute:
context.getAttribute(ChunkListener.ROLLBACK_EXCEPTION_KEY)
Instead of implementing a ChunkListener you could implement one or more of ItemReadListener, ItemProcessListener and ItemWriteListener (or more simply ItemListenerSupport).
They respectively give acces to :
void onReadError(java.lang.Exception ex)
void onProcessError(T item, java.lang.Exception e)
void onWriteError(java.lang.Exception exception, java.util.List<? extends S> items)
I know this forces you to implement multiple methods to manage every aspect of a chunk, but you can write a custom method which takes an Exception and call it from these 3 methods.

JAX-RS exception handling

I'm relatively new to REST services in Java. I've created one and everything works fine except error handling. If I make a request with incorrectly formed JSON in it, Jackson JSON processor throws an exception which I unable to catch and I get error 500 in client. Exception follows:
javax.ws.rs.InternalServerErrorException: org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of java.util.HashSet out of VALUE_STRING token
I have no idea how to handle exceptions raised outside my code.
Google suggests using Exception Mappers or Phase Inteceptors. Though I could miss something in search results.
What is the proper way to handle such situations?
Please, advise something. I'm stuck with this problem...
A JAX-RS ExceptionMapper should do the job. Just add a class like below to your code and if you have the exception type right, then you should get the hook to customize the handling.
#Provider
public class MyExceptionMapper implements ExceptionMapper<MyException> {
#Override
public Response toResponse(MyException ex) {
return Response.status(Response.Status.BAD_REQUEST).build();
}
}