Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GH-3050: Delegating EH delegates compatibility #3051

Merged
merged 1 commit into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,8 @@ This is to cause the transaction to roll back (if transactions are enabled).
The `CommonDelegatingErrorHandler` can delegate to different error handlers, depending on the exception type.
For example, you may wish to invoke a `DefaultErrorHandler` for most exceptions, or a `CommonContainerStoppingErrorHandler` for others.

All delegates must share the same compatible properties (`ackAfterHandle`, `seekAfterError` ...).

[[log-eh]]
== Logging Error Handler

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021-2023 the original author or authors.
* Copyright 2021-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -38,6 +38,7 @@
*
* @author Gary Russell
* @author Adrian Chlebosz
* @author Antonin Arquey
* @since 2.8
*
*/
Expand Down Expand Up @@ -65,6 +66,7 @@ public CommonDelegatingErrorHandler(CommonErrorHandler defaultErrorHandler) {
* Set the delegate error handlers; a {@link LinkedHashMap} argument is recommended so
* that the delegates are searched in a known order.
* @param delegates the delegates.
* @throws IllegalArgumentException if any of the delegates is not compatible with the default error handler.
*/
public void setErrorHandlers(Map<Class<? extends Throwable>, CommonErrorHandler> delegates) {
Assert.notNull(delegates, "'delegates' cannot be null");
Expand Down Expand Up @@ -109,6 +111,7 @@ public void setAckAfterHandle(boolean ack) {
* Add a delegate to the end of the current collection.
* @param throwable the throwable for this handler.
* @param handler the handler.
* @throws IllegalArgumentException if the handler is not compatible with the default error handler.
*/
public void addDelegate(Class<? extends Throwable> throwable, CommonErrorHandler handler) {
Map<Class<? extends Throwable>, CommonErrorHandler> delegatesToCheck = new LinkedHashMap<>(this.delegates);
Expand All @@ -118,13 +121,12 @@ public void addDelegate(Class<? extends Throwable> throwable, CommonErrorHandler
this.delegates.putAll(delegatesToCheck);
}

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

boolean ackAfterHandle = this.defaultErrorHandler.isAckAfterHandle();
boolean seeksAfterHandling = this.defaultErrorHandler.seeksAfterHandling();
this.delegates.values().forEach(handler -> {
delegatesToCheck.values().forEach(handler -> {
Assert.isTrue(ackAfterHandle == handler.isAckAfterHandle(),
"All delegates must return the same value when calling 'isAckAfterHandle()'");
Assert.isTrue(seeksAfterHandling == handler.seeksAfterHandling(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021-2022 the original author or authors.
* Copyright 2021-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,7 +17,9 @@
package org.springframework.kafka.listener;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
Expand All @@ -39,6 +41,7 @@
*
* @author Gary Russell
* @author Adrian Chlebosz
* @author Antonin Arquey
* @since 2.8
*
*/
Expand Down Expand Up @@ -173,6 +176,48 @@ void testDefaultDelegateIsApplied() {
verify(defaultHandler).handleRemaining(any(), any(), any(), any());
}

@Test
void testAddIncompatibleAckAfterHandleDelegate() {
var defaultHandler = mock(CommonErrorHandler.class);
given(defaultHandler.isAckAfterHandle()).willReturn(true);
var delegatingErrorHandler = new CommonDelegatingErrorHandler(defaultHandler);
var delegate = mock(CommonErrorHandler.class);
given(delegate.isAckAfterHandle()).willReturn(false);

assertThatThrownBy(() -> delegatingErrorHandler.addDelegate(IllegalStateException.class, delegate))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("All delegates must return the same value when calling 'isAckAfterHandle()'");
}

@Test
void testAddIncompatibleSeeksAfterHandlingDelegate() {
var defaultHandler = mock(CommonErrorHandler.class);
given(defaultHandler.seeksAfterHandling()).willReturn(true);
var delegatingErrorHandler = new CommonDelegatingErrorHandler(defaultHandler);
var delegate = mock(CommonErrorHandler.class);
given(delegate.seeksAfterHandling()).willReturn(false);

assertThatThrownBy(() -> delegatingErrorHandler.addDelegate(IllegalStateException.class, delegate))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("All delegates must return the same value when calling 'seeksAfterHandling()'");
}

@Test
void testAddMultipleDelegatesWithOneIncompatible() {
var defaultHandler = mock(CommonErrorHandler.class);
given(defaultHandler.seeksAfterHandling()).willReturn(true);
var delegatingErrorHandler = new CommonDelegatingErrorHandler(defaultHandler);
var one = mock(CommonErrorHandler.class);
given(one.seeksAfterHandling()).willReturn(true);
var two = mock(CommonErrorHandler.class);
given(one.seeksAfterHandling()).willReturn(false);
Map<Class<? extends Throwable>, CommonErrorHandler> delegates = Map.of(IllegalStateException.class, one, IOException.class, two);

assertThatThrownBy(() -> delegatingErrorHandler.setErrorHandlers(delegates))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("All delegates must return the same value when calling 'seeksAfterHandling()'");
}

private Exception wrap(Exception ex) {
return new ListenerExecutionFailedException("test", ex);
}
Expand Down
Loading