Skip to content

Commit c98e4cd

Browse files
authored
GH-2826: Fix CommonDelegatingErrorHandler
Resolves #2826 The `addDelegate` method did not update the classifier, so it failed to work when cause chain traversal is enabled. **cherry-pick to 3.0.x, 2.9.x** * Do not mutate the delegates field until after the validity check.
1 parent 6815803 commit c98e4cd

File tree

2 files changed

+24
-13
lines changed

2 files changed

+24
-13
lines changed

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

+17-12
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,7 @@ public void setErrorHandlers(Map<Class<? extends Throwable>, CommonErrorHandler>
7070
Assert.notNull(delegates, "'delegates' cannot be null");
7171
this.delegates.clear();
7272
this.delegates.putAll(delegates);
73-
checkDelegates();
74-
updateClassifier(delegates);
75-
}
76-
77-
private void updateClassifier(Map<Class<? extends Throwable>, CommonErrorHandler> delegates) {
78-
Map<Class<? extends Throwable>, Boolean> classifications = delegates.keySet().stream()
79-
.map(commonErrorHandler -> Map.entry(commonErrorHandler, true))
80-
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
81-
this.classifier = new BinaryExceptionClassifier(classifications);
73+
checkDelegatesAndUpdateClassifier(this.delegates);
8274
}
8375

8476
/**
@@ -119,12 +111,17 @@ public void setAckAfterHandle(boolean ack) {
119111
* @param handler the handler.
120112
*/
121113
public void addDelegate(Class<? extends Throwable> throwable, CommonErrorHandler handler) {
122-
this.delegates.put(throwable, handler);
123-
checkDelegates();
114+
Map<Class<? extends Throwable>, CommonErrorHandler> delegatesToCheck = new LinkedHashMap<>(this.delegates);
115+
delegatesToCheck.put(throwable, handler);
116+
checkDelegatesAndUpdateClassifier(delegatesToCheck);
117+
this.delegates.clear();
118+
this.delegates.putAll(delegatesToCheck);
124119
}
125120

126121
@SuppressWarnings("deprecation")
127-
private void checkDelegates() {
122+
private void checkDelegatesAndUpdateClassifier(Map<Class<? extends Throwable>,
123+
CommonErrorHandler> delegatesToCheck) {
124+
128125
boolean ackAfterHandle = this.defaultErrorHandler.isAckAfterHandle();
129126
boolean seeksAfterHandling = this.defaultErrorHandler.seeksAfterHandling();
130127
this.delegates.values().forEach(handler -> {
@@ -133,6 +130,14 @@ private void checkDelegates() {
133130
Assert.isTrue(seeksAfterHandling == handler.seeksAfterHandling(),
134131
"All delegates must return the same value when calling 'seeksAfterHandling()'");
135132
});
133+
updateClassifier(delegatesToCheck);
134+
}
135+
136+
private void updateClassifier(Map<Class<? extends Throwable>, CommonErrorHandler> delegates) {
137+
Map<Class<? extends Throwable>, Boolean> classifications = delegates.keySet().stream()
138+
.map(commonErrorHandler -> Map.entry(commonErrorHandler, true))
139+
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
140+
this.classifier = new BinaryExceptionClassifier(classifications);
136141
}
137142

138143
@Override

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

+7-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.kafka.listener;
1818

19+
import static org.assertj.core.api.Assertions.assertThat;
1920
import static org.mockito.ArgumentMatchers.any;
2021
import static org.mockito.Mockito.mock;
2122
import static org.mockito.Mockito.never;
@@ -31,6 +32,7 @@
3132

3233
import org.springframework.kafka.KafkaException;
3334
import org.springframework.kafka.core.KafkaProducerException;
35+
import org.springframework.kafka.test.utils.KafkaTestUtils;
3436

3537
/**
3638
* Tests for {@link CommonDelegatingErrorHandler}.
@@ -134,7 +136,7 @@ void testDelegateForThrowableCauseIsAppliedWhenCauseTraversingIsEnabled() {
134136
}
135137

136138
@Test
137-
@SuppressWarnings("ConstantConditions")
139+
@SuppressWarnings({ "ConstantConditions", "unchecked" })
138140
void testDelegateForClassifiableThrowableCauseIsAppliedWhenCauseTraversingIsEnabled() {
139141
var defaultHandler = mock(CommonErrorHandler.class);
140142

@@ -147,6 +149,10 @@ void testDelegateForClassifiableThrowableCauseIsAppliedWhenCauseTraversingIsEnab
147149
delegatingErrorHandler.setErrorHandlers(Map.of(
148150
KafkaException.class, directCauseErrorHandler
149151
));
152+
delegatingErrorHandler.addDelegate(IllegalStateException.class, mock(CommonErrorHandler.class));
153+
assertThat(KafkaTestUtils.getPropertyValue(delegatingErrorHandler, "classifier.classified", Map.class).keySet())
154+
.contains(IllegalStateException.class);
155+
150156

151157
delegatingErrorHandler.handleRemaining(exc, Collections.emptyList(), mock(Consumer.class),
152158
mock(MessageListenerContainer.class));

0 commit comments

Comments
 (0)