Skip to content

Commit 26135e3

Browse files
spring-projectsGH-3050: Delegating EH delegates compatibility
Fixes: spring-projects#3050 Correct CommonDelegatingErrorHandler validation for delegates compatibility. Add documentation stating that delegates must be compatible with default error handler.
1 parent f25b46f commit 26135e3

File tree

3 files changed

+53
-4
lines changed

3 files changed

+53
-4
lines changed

spring-kafka-docs/src/main/antora/modules/ROOT/pages/kafka/annotation-error-handling.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,8 @@ This is to cause the transaction to roll back (if transactions are enabled).
351351
The `CommonDelegatingErrorHandler` can delegate to different error handlers, depending on the exception type.
352352
For example, you may wish to invoke a `DefaultErrorHandler` for most exceptions, or a `CommonContainerStoppingErrorHandler` for others.
353353

354+
All delegates must share the same compatible properties (`ackAfterHandle`, `seekAfterError` ...).
355+
354356
[[log-eh]]
355357
== Logging Error Handler
356358

spring-kafka/src/main/java/org/springframework/kafka/listener/CommonDelegatingErrorHandler.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021-2023 the original author or authors.
2+
* Copyright 2021-2024 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.
@@ -38,6 +38,7 @@
3838
*
3939
* @author Gary Russell
4040
* @author Adrian Chlebosz
41+
* @author Antonin Arquey
4142
* @since 2.8
4243
*
4344
*/
@@ -65,6 +66,7 @@ public CommonDelegatingErrorHandler(CommonErrorHandler defaultErrorHandler) {
6566
* Set the delegate error handlers; a {@link LinkedHashMap} argument is recommended so
6667
* that the delegates are searched in a known order.
6768
* @param delegates the delegates.
69+
* @throws IllegalArgumentException if any of the delegates is not compatible with the default error handler.
6870
*/
6971
public void setErrorHandlers(Map<Class<? extends Throwable>, CommonErrorHandler> delegates) {
7072
Assert.notNull(delegates, "'delegates' cannot be null");
@@ -109,6 +111,7 @@ public void setAckAfterHandle(boolean ack) {
109111
* Add a delegate to the end of the current collection.
110112
* @param throwable the throwable for this handler.
111113
* @param handler the handler.
114+
* @throws IllegalArgumentException if the handler is not compatible with the default error handler.
112115
*/
113116
public void addDelegate(Class<? extends Throwable> throwable, CommonErrorHandler handler) {
114117
Map<Class<? extends Throwable>, CommonErrorHandler> delegatesToCheck = new LinkedHashMap<>(this.delegates);
@@ -118,13 +121,12 @@ public void addDelegate(Class<? extends Throwable> throwable, CommonErrorHandler
118121
this.delegates.putAll(delegatesToCheck);
119122
}
120123

121-
@SuppressWarnings("deprecation")
122124
private void checkDelegatesAndUpdateClassifier(Map<Class<? extends Throwable>,
123125
CommonErrorHandler> delegatesToCheck) {
124126

125127
boolean ackAfterHandle = this.defaultErrorHandler.isAckAfterHandle();
126128
boolean seeksAfterHandling = this.defaultErrorHandler.seeksAfterHandling();
127-
this.delegates.values().forEach(handler -> {
129+
delegatesToCheck.values().forEach(handler -> {
128130
Assert.isTrue(ackAfterHandle == handler.isAckAfterHandle(),
129131
"All delegates must return the same value when calling 'isAckAfterHandle()'");
130132
Assert.isTrue(seeksAfterHandling == handler.seeksAfterHandling(),

spring-kafka/src/test/java/org/springframework/kafka/listener/CommonDelegatingErrorHandlerTests.java

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021-2022 the original author or authors.
2+
* Copyright 2021-2024 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.
@@ -17,7 +17,9 @@
1717
package org.springframework.kafka.listener;
1818

1919
import static org.assertj.core.api.Assertions.assertThat;
20+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2021
import static org.mockito.ArgumentMatchers.any;
22+
import static org.mockito.BDDMockito.given;
2123
import static org.mockito.Mockito.mock;
2224
import static org.mockito.Mockito.never;
2325
import static org.mockito.Mockito.verify;
@@ -39,6 +41,7 @@
3941
*
4042
* @author Gary Russell
4143
* @author Adrian Chlebosz
44+
* @author Antonin Arquey
4245
* @since 2.8
4346
*
4447
*/
@@ -173,6 +176,48 @@ void testDefaultDelegateIsApplied() {
173176
verify(defaultHandler).handleRemaining(any(), any(), any(), any());
174177
}
175178

179+
@Test
180+
void testAddIncompatibleAckAfterHandleDelegate() {
181+
var defaultHandler = mock(CommonErrorHandler.class);
182+
given(defaultHandler.isAckAfterHandle()).willReturn(true);
183+
var delegatingErrorHandler = new CommonDelegatingErrorHandler(defaultHandler);
184+
var delegate = mock(CommonErrorHandler.class);
185+
given(delegate.isAckAfterHandle()).willReturn(false);
186+
187+
assertThatThrownBy(() -> delegatingErrorHandler.addDelegate(IllegalStateException.class, delegate))
188+
.isInstanceOf(IllegalArgumentException.class)
189+
.hasMessage("All delegates must return the same value when calling 'isAckAfterHandle()'");
190+
}
191+
192+
@Test
193+
void testAddIncompatibleSeeksAfterHandlingDelegate() {
194+
var defaultHandler = mock(CommonErrorHandler.class);
195+
given(defaultHandler.seeksAfterHandling()).willReturn(true);
196+
var delegatingErrorHandler = new CommonDelegatingErrorHandler(defaultHandler);
197+
var delegate = mock(CommonErrorHandler.class);
198+
given(delegate.seeksAfterHandling()).willReturn(false);
199+
200+
assertThatThrownBy(() -> delegatingErrorHandler.addDelegate(IllegalStateException.class, delegate))
201+
.isInstanceOf(IllegalArgumentException.class)
202+
.hasMessage("All delegates must return the same value when calling 'seeksAfterHandling()'");
203+
}
204+
205+
@Test
206+
void testAddMultipleDelegatesWithOneIncompatible() {
207+
var defaultHandler = mock(CommonErrorHandler.class);
208+
given(defaultHandler.seeksAfterHandling()).willReturn(true);
209+
var delegatingErrorHandler = new CommonDelegatingErrorHandler(defaultHandler);
210+
var one = mock(CommonErrorHandler.class);
211+
given(one.seeksAfterHandling()).willReturn(true);
212+
var two = mock(CommonErrorHandler.class);
213+
given(one.seeksAfterHandling()).willReturn(false);
214+
Map<Class<? extends Throwable>, CommonErrorHandler> delegates = Map.of(IllegalStateException.class, one, IOException.class, two);
215+
216+
assertThatThrownBy(() -> delegatingErrorHandler.setErrorHandlers(delegates))
217+
.isInstanceOf(IllegalArgumentException.class)
218+
.hasMessage("All delegates must return the same value when calling 'seeksAfterHandling()'");
219+
}
220+
176221
private Exception wrap(Exception ex) {
177222
return new ListenerExecutionFailedException("test", ex);
178223
}

0 commit comments

Comments
 (0)