Skip to content

Commit 61bec28

Browse files
fmbenhassinemminella
authored andcommitted
BATCH-2213: unwrap exceptions thrown from annotated step listeners
Currently, when a method of an annotated listener throws an exception, the exception is wrapped in a InvocationTargetException (by the reflection API) which in turn is wrapped in a IllegalArgumentException (by Spring Batch). This requires the user to unwrap the original exception from the StepListenerFailedException. This behavior is not consistent with interface based listeners where the original exception is the root cause of StepListenerFailedException. This commit unwraps the original exception and make it the root cause of StepListenerFailedException. Resolves BATCH-2213
1 parent 2913c8d commit 61bec28

File tree

2 files changed

+218
-10
lines changed

2 files changed

+218
-10
lines changed

spring-batch-core/src/main/java/org/springframework/batch/core/listener/MulticasterBatchListener.java

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2006-2013 the original author or authors.
2+
* Copyright 2006-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.batch.core.listener;
1717

18+
import java.lang.reflect.InvocationTargetException;
1819
import java.util.List;
1920

2021
import javax.batch.api.chunk.listener.RetryProcessListener;
@@ -37,6 +38,7 @@
3738
* @author Dave Syer
3839
* @author Michael Minella
3940
* @author Chris Schaefer
41+
* @author Mahmoud Ben Hassine
4042
*/
4143
public class MulticasterBatchListener<T, S> implements StepExecutionListener, ChunkListener, ItemReadListener<T>,
4244
ItemProcessListener<T, S>, ItemWriteListener<S>, SkipListener<T, S>, RetryReadListener, RetryProcessListener, RetryWriteListener {
@@ -133,7 +135,7 @@ public void afterProcess(T item, S result) {
133135
itemProcessListener.afterProcess(item, result);
134136
}
135137
catch (RuntimeException e) {
136-
throw new StepListenerFailedException("Error in afterProcess.", e);
138+
throw new StepListenerFailedException("Error in afterProcess.", getTargetException(e));
137139
}
138140
}
139141

@@ -146,7 +148,7 @@ public void beforeProcess(T item) {
146148
itemProcessListener.beforeProcess(item);
147149
}
148150
catch (RuntimeException e) {
149-
throw new StepListenerFailedException("Error in beforeProcess.", e);
151+
throw new StepListenerFailedException("Error in beforeProcess.", getTargetException(e));
150152
}
151153
}
152154

@@ -199,7 +201,7 @@ public void afterChunk(ChunkContext context) {
199201
chunkListener.afterChunk(context);
200202
}
201203
catch (RuntimeException e) {
202-
throw new StepListenerFailedException("Error in afterChunk.", e);
204+
throw new StepListenerFailedException("Error in afterChunk.", getTargetException(e));
203205
}
204206
}
205207

@@ -212,7 +214,7 @@ public void beforeChunk(ChunkContext context) {
212214
chunkListener.beforeChunk(context);
213215
}
214216
catch (RuntimeException e) {
215-
throw new StepListenerFailedException("Error in beforeChunk.", e);
217+
throw new StepListenerFailedException("Error in beforeChunk.", getTargetException(e));
216218
}
217219
}
218220

@@ -225,7 +227,7 @@ public void afterRead(T item) {
225227
itemReadListener.afterRead(item);
226228
}
227229
catch (RuntimeException e) {
228-
throw new StepListenerFailedException("Error in afterRead.", e);
230+
throw new StepListenerFailedException("Error in afterRead.", getTargetException(e));
229231
}
230232
}
231233

@@ -238,7 +240,7 @@ public void beforeRead() {
238240
itemReadListener.beforeRead();
239241
}
240242
catch (RuntimeException e) {
241-
throw new StepListenerFailedException("Error in beforeRead.", e);
243+
throw new StepListenerFailedException("Error in beforeRead.", getTargetException(e));
242244
}
243245
}
244246

@@ -264,7 +266,7 @@ public void afterWrite(List<? extends S> items) {
264266
itemWriteListener.afterWrite(items);
265267
}
266268
catch (RuntimeException e) {
267-
throw new StepListenerFailedException("Error in afterWrite.", e);
269+
throw new StepListenerFailedException("Error in afterWrite.", getTargetException(e));
268270
}
269271
}
270272

@@ -277,7 +279,7 @@ public void beforeWrite(List<? extends S> items) {
277279
itemWriteListener.beforeWrite(items);
278280
}
279281
catch (RuntimeException e) {
280-
throw new StepListenerFailedException("Error in beforeWrite.", e);
282+
throw new StepListenerFailedException("Error in beforeWrite.", getTargetException(e));
281283
}
282284
}
283285

@@ -356,4 +358,17 @@ public void onRetryWriteException(List<Object> items, Exception ex) throws Excep
356358
throw new BatchRuntimeException(e);
357359
}
358360
}
361+
362+
/**
363+
* Unwrap the target exception from a wrapped {@link InvocationTargetException}.
364+
* @param e the exception to introspect
365+
* @return the target exception if any
366+
*/
367+
private Throwable getTargetException(RuntimeException e) {
368+
Throwable cause = e.getCause();
369+
if (cause != null && cause instanceof InvocationTargetException) {
370+
return ((InvocationTargetException) cause).getTargetException();
371+
}
372+
return e;
373+
}
359374
}

spring-batch-core/src/test/java/org/springframework/batch/core/listener/MulticasterBatchListenerTests.java

Lines changed: 194 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2006-2007 the original author or authors.
2+
* Copyright 2006-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616
package org.springframework.batch.core.listener;
1717

1818
import static org.junit.Assert.assertEquals;
19+
import static org.junit.Assert.assertTrue;
1920
import static org.junit.Assert.fail;
2021

2122
import java.util.Arrays;
@@ -26,10 +27,20 @@
2627
import org.springframework.batch.core.ExitStatus;
2728
import org.springframework.batch.core.JobExecution;
2829
import org.springframework.batch.core.StepExecution;
30+
import org.springframework.batch.core.StepListener;
31+
import org.springframework.batch.core.annotation.AfterChunk;
32+
import org.springframework.batch.core.annotation.AfterProcess;
33+
import org.springframework.batch.core.annotation.AfterRead;
34+
import org.springframework.batch.core.annotation.AfterWrite;
35+
import org.springframework.batch.core.annotation.BeforeChunk;
36+
import org.springframework.batch.core.annotation.BeforeProcess;
37+
import org.springframework.batch.core.annotation.BeforeRead;
38+
import org.springframework.batch.core.annotation.BeforeWrite;
2939
import org.springframework.batch.core.scope.context.ChunkContext;
3040

3141
/**
3242
* @author Dave Syer
43+
* @author Mahmoud Ben Hassine
3344
*
3445
*/
3546
public class MulticasterBatchListenerTests {
@@ -512,6 +523,188 @@ public void onSkipInProcess(Object item, Throwable t) {
512523
assertEquals(1, count);
513524
}
514525

526+
@Test
527+
public void testBeforeReadFails_withAnnotatedListener() {
528+
StepListener listener = StepListenerFactoryBean.getListener(new AnnotationBasedStepListener());
529+
multicast.register(listener);
530+
531+
try {
532+
multicast.beforeRead();
533+
fail("Expected StepListenerFailedException");
534+
} catch (StepListenerFailedException e) {
535+
// expected
536+
Throwable cause = e.getCause();
537+
String message = cause.getMessage();
538+
assertTrue(cause instanceof IllegalStateException);
539+
assertEquals("Wrong message: " + message, "listener error", message);
540+
}
541+
}
542+
543+
@Test
544+
public void testAfterReadFails_withAnnotatedListener() {
545+
StepListener listener = StepListenerFactoryBean.getListener(new AnnotationBasedStepListener());
546+
multicast.register(listener);
547+
548+
try {
549+
multicast.afterRead(null);
550+
fail("Expected StepListenerFailedException");
551+
} catch (StepListenerFailedException e) {
552+
// expected
553+
Throwable cause = e.getCause();
554+
String message = cause.getMessage();
555+
assertTrue(cause instanceof IllegalStateException);
556+
assertEquals("Wrong message: " + message, "listener error", message);
557+
}
558+
}
559+
560+
@Test
561+
public void testBeforeProcessFails_withAnnotatedListener() {
562+
StepListener listener = StepListenerFactoryBean.getListener(new AnnotationBasedStepListener());
563+
multicast.register(listener);
564+
565+
try {
566+
multicast.beforeProcess(null);
567+
fail("Expected StepListenerFailedException");
568+
} catch (StepListenerFailedException e) {
569+
// expected
570+
Throwable cause = e.getCause();
571+
String message = cause.getMessage();
572+
assertTrue(cause instanceof IllegalStateException);
573+
assertEquals("Wrong message: " + message, "listener error", message);
574+
}
575+
}
576+
577+
@Test
578+
public void testAfterProcessFails_withAnnotatedListener() {
579+
StepListener listener = StepListenerFactoryBean.getListener(new AnnotationBasedStepListener());
580+
multicast.register(listener);
581+
582+
try {
583+
multicast.afterProcess(null, null);
584+
fail("Expected StepListenerFailedException");
585+
} catch (StepListenerFailedException e) {
586+
// expected
587+
Throwable cause = e.getCause();
588+
String message = cause.getMessage();
589+
assertTrue(cause instanceof IllegalStateException);
590+
assertEquals("Wrong message: " + message, "listener error", message);
591+
}
592+
}
593+
594+
@Test
595+
public void testBeforeWriteFails_withAnnotatedListener() {
596+
StepListener listener = StepListenerFactoryBean.getListener(new AnnotationBasedStepListener());
597+
multicast.register(listener);
598+
599+
try {
600+
multicast.beforeWrite(null);
601+
fail("Expected StepListenerFailedException");
602+
} catch (StepListenerFailedException e) {
603+
// expected
604+
Throwable cause = e.getCause();
605+
String message = cause.getMessage();
606+
assertTrue(cause instanceof IllegalStateException);
607+
assertEquals("Wrong message: " + message, "listener error", message);
608+
}
609+
}
610+
611+
@Test
612+
public void testAfterWriteFails_withAnnotatedListener() {
613+
StepListener listener = StepListenerFactoryBean.getListener(new AnnotationBasedStepListener());
614+
multicast.register(listener);
615+
616+
try {
617+
multicast.afterWrite(null);
618+
fail("Expected StepListenerFailedException");
619+
} catch (StepListenerFailedException e) {
620+
// expected
621+
Throwable cause = e.getCause();
622+
String message = cause.getMessage();
623+
assertTrue(cause instanceof IllegalStateException);
624+
assertEquals("Wrong message: " + message, "listener error", message);
625+
}
626+
}
627+
628+
@Test
629+
public void testBeforeChunkFails_withAnnotatedListener() {
630+
StepListener listener = StepListenerFactoryBean.getListener(new AnnotationBasedStepListener());
631+
multicast.register(listener);
632+
633+
try {
634+
multicast.beforeChunk(null);
635+
fail("Expected StepListenerFailedException");
636+
} catch (StepListenerFailedException e) {
637+
// expected
638+
Throwable cause = e.getCause();
639+
String message = cause.getMessage();
640+
assertTrue(cause instanceof IllegalStateException);
641+
assertEquals("Wrong message: " + message, "listener error", message);
642+
}
643+
}
644+
645+
@Test
646+
public void testAfterChunkFails_withAnnotatedListener() {
647+
StepListener listener = StepListenerFactoryBean.getListener(new AnnotationBasedStepListener());
648+
multicast.register(listener);
649+
650+
try {
651+
multicast.afterChunk(null);
652+
fail("Expected StepListenerFailedException");
653+
} catch (StepListenerFailedException e) {
654+
// expected
655+
Throwable cause = e.getCause();
656+
String message = cause.getMessage();
657+
assertTrue(cause instanceof IllegalStateException);
658+
assertEquals("Wrong message: " + message, "listener error", message);
659+
}
660+
}
661+
662+
private final class AnnotationBasedStepListener {
663+
664+
private IllegalStateException exception = new IllegalStateException("listener error");
665+
666+
@BeforeRead
667+
public void beforeRead() {
668+
throw exception;
669+
}
670+
671+
@AfterRead
672+
public void afterRead() {
673+
throw exception;
674+
}
675+
676+
@BeforeProcess
677+
public void beforeProcess() {
678+
throw exception;
679+
}
680+
681+
@AfterProcess
682+
public void afterProcess() {
683+
throw exception;
684+
}
685+
686+
@BeforeWrite
687+
public void beforeWrite() {
688+
throw exception;
689+
}
690+
691+
@AfterWrite
692+
public void afterWrite() {
693+
throw exception;
694+
}
695+
696+
@BeforeChunk
697+
public void beforeChunk() {
698+
throw exception;
699+
}
700+
701+
@AfterChunk
702+
public void afterChunk() {
703+
throw exception;
704+
}
705+
706+
}
707+
515708
/**
516709
* @author Dave Syer
517710
*

0 commit comments

Comments
 (0)