Skip to content

Commit

Permalink
ExceptionClassifier Reverse Option
Browse files Browse the repository at this point in the history
Option to not retry by default and retry specific.

* Polishing and Tests

In preparation for spring-projects#2124
  • Loading branch information
garyrussell authored Feb 22, 2022
1 parent b198c5b commit c27de92
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 the original author or authors.
* Copyright 2021-2022 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 @@ -55,6 +55,16 @@ private static ExtendedBinaryExceptionClassifier configureDefaultClassifier() {
return new ExtendedBinaryExceptionClassifier(classified, true);
}

/**
* By default, unmatched types classify as true. Call this method to make the default
* false, and remove types explicitly classified as false. This should be called before
* calling any of the classification modification methods.
* @since 2.8.4
*/
public void defaultFalse() {
this.classifier = new ExtendedBinaryExceptionClassifier(new HashMap<>(), false);
}

/**
* Return the exception classifier.
* @return the classifier.
Expand Down Expand Up @@ -97,20 +107,39 @@ public void setClassifications(Map<Class<? extends Throwable>, Boolean> classifi
* <li>{@link NoSuchMethodException}</li>
* <li>{@link ClassCastException}</li>
* </ul>
* All others will be retried.
* All others will be retried, unless {@link #defaultFalse()} has been called.
* @param exceptionTypes the exception types.
* @see #removeNotRetryableException(Class)
* @see #removeClassification(Class)
* @see #setClassifications(Map, boolean)
*/
@SafeVarargs
@SuppressWarnings("varargs")
public final void addNotRetryableExceptions(Class<? extends Exception>... exceptionTypes) {
add(false, exceptionTypes);
}

/**
* Add exception types that can be retried. Call this after {@link #defaultFalse()} to
* specify those exception types that should be classified as true.
* All others will be retried, unless {@link #defaultFalse()} has been called.
* @param exceptionTypes the exception types.
* @since 2.8.4
* @see #removeClassification(Class)
* @see #setClassifications(Map, boolean)
*/
@SafeVarargs
@SuppressWarnings("varargs")
public final void addRetryableExceptions(Class<? extends Exception>... exceptionTypes) {
add(true, exceptionTypes);
}

private void add(boolean classified, Class<? extends Exception>... exceptionTypes) {
Assert.notNull(exceptionTypes, "'exceptionTypes' cannot be null");
Assert.noNullElements(exceptionTypes, "'exceptionTypes' cannot contain nulls");
for (Class<? extends Exception> exceptionType : exceptionTypes) {
Assert.isTrue(Exception.class.isAssignableFrom(exceptionType),
() -> "exceptionType " + exceptionType + " must be an Exception");
this.classifier.getClassified().put(exceptionType, false);
this.classifier.getClassified().put(exceptionType, classified);
}
}

Expand All @@ -125,13 +154,38 @@ public final void addNotRetryableExceptions(Class<? extends Exception>... except
* <li>{@link NoSuchMethodException}</li>
* <li>{@link ClassCastException}</li>
* </ul>
* All others will be retried.
* All others will be retried, unless {@link #defaultFalse()} has been called.
* @param exceptionType the exception type.
* @return true if the removal was successful.
* @deprecated in favor of {@link #removeClassification(Class)}
* @see #addNotRetryableExceptions(Class...)
* @see #setClassifications(Map, boolean)
* @see #defaultFalse()
*/
@Deprecated
public boolean removeNotRetryableException(Class<? extends Exception> exceptionType) {
return this.removeClassification(exceptionType);
}

/**
* Remove an exception type from the configured list. By default, the following
* exceptions will not be retried:
* <ul>
* <li>{@link DeserializationException}</li>
* <li>{@link MessageConversionException}</li>
* <li>{@link ConversionException}</li>
* <li>{@link MethodArgumentResolutionException}</li>
* <li>{@link NoSuchMethodException}</li>
* <li>{@link ClassCastException}</li>
* </ul>
* All others will be retried, unless {@link #defaultFalse()} has been called.
* @param exceptionType the exception type.
* @return true if the removal was successful.
* @since 2.8.4
* @see #addNotRetryableExceptions(Class...)
* @see #setClassifications(Map, boolean)
*/
public boolean removeClassification(Class<? extends Exception> exceptionType) {
return this.classifier.getClassified().remove(exceptionType);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ protected DeadLetterPublishingRecoverer.HeaderNames getHeaderNames() {
recoverer.setSkipSameTopicFatalExceptions(false);
this.recovererCustomizer.accept(recoverer);
this.fatalExceptions.forEach(recoverer::addNotRetryableExceptions);
this.nonFatalExceptions.forEach(recoverer::removeNotRetryableException);
this.nonFatalExceptions.forEach(recoverer::removeClassification);
return recoverer;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ void noCircularRoutingIfFatal() {
recoverer.addNotRetryableExceptions(IllegalStateException.class);
recoverer.accept(record, new IllegalStateException());
verify(template, never()).send(any(ProducerRecord.class));
recoverer.removeNotRetryableException(IllegalStateException.class);
recoverer.removeClassification(IllegalStateException.class);
recoverer.setFailIfSendResultIsError(false);
recoverer.accept(record, new IllegalStateException());
verify(template).send(any(ProducerRecord.class));
Expand All @@ -580,7 +580,7 @@ void doNotSkipCircularFatalIfSet() {
recoverer.addNotRetryableExceptions(IllegalStateException.class);
recoverer.accept(record, new IllegalStateException());
verify(template, times(2)).send(any(ProducerRecord.class));
recoverer.removeNotRetryableException(IllegalStateException.class);
recoverer.removeClassification(IllegalStateException.class);
recoverer.setFailIfSendResultIsError(false);
recoverer.accept(record, new IllegalStateException());
verify(template, times(3)).send(any(ProducerRecord.class));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.kafka.listener;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;

/**
* @author Gary Russell
* @since 2.8.4
*
*/
public class ExceptionClassifierTests {

@Test
void testDefault() {
ExceptionClassifier ec = new ExceptionClassifier() {
};
assertThat(ec.getClassifier().classify(new Exception())).isTrue();
assertThat(ec.getClassifier().classify(new ClassCastException())).isFalse();
ec.removeClassification(ClassCastException.class);
assertThat(ec.getClassifier().classify(new ClassCastException())).isTrue();
assertThat(ec.getClassifier().classify(new IllegalStateException())).isTrue();
ec.addNotRetryableExceptions(IllegalStateException.class);
assertThat(ec.getClassifier().classify(new IllegalStateException())).isFalse();
}

@Test
void testDefaultFalse() {
ExceptionClassifier ec = new ExceptionClassifier() {
};
assertThat(ec.getClassifier().classify(new Exception())).isTrue();
ec.defaultFalse();
assertThat(ec.getClassifier().classify(new Exception())).isFalse();
assertThat(ec.getClassifier().classify(new IllegalStateException())).isFalse();
ec.addRetryableExceptions(IllegalStateException.class);
assertThat(ec.getClassifier().classify(new IllegalStateException())).isTrue();
ec.removeClassification(IllegalStateException.class);
assertThat(ec.getClassifier().classify(new IllegalStateException())).isFalse();
}

}

0 comments on commit c27de92

Please sign in to comment.