diff --git a/src/main/java/org/springframework/classify/BinaryExceptionClassifier.java b/src/main/java/org/springframework/classify/BinaryExceptionClassifier.java index edd6ab1..a326f76 100644 --- a/src/main/java/org/springframework/classify/BinaryExceptionClassifier.java +++ b/src/main/java/org/springframework/classify/BinaryExceptionClassifier.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2007 the original author or authors. + * Copyright 2006-2013 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. @@ -25,17 +25,20 @@ import java.util.Map; * supplied types. If the object to be classified is one of the provided types, * or is a subclass of one of the types, then the non-default value is returned * (usually true). - * + * * @see SubclassClassifier - * + * * @author Dave Syer - * + * @author Gary Russell + * */ public class BinaryExceptionClassifier extends SubclassClassifier { + private boolean traverseCauses; + /** * Create a binary exception classifier with the provided default value. - * + * * @param defaultValue defaults to false */ public BinaryExceptionClassifier(boolean defaultValue) { @@ -46,7 +49,7 @@ public class BinaryExceptionClassifier extends SubclassClassifier> exceptionClasses, boolean value) { @@ -71,7 +74,7 @@ public class BinaryExceptionClassifier extends SubclassClassifier, Boolean> typeMap) { @@ -81,11 +84,42 @@ public class BinaryExceptionClassifier extends SubclassClassifier, Boolean> typeMap, boolean defaultValue) { super(typeMap, defaultValue); } + + public void setTraverseCauses(boolean traverseCauses) { + this.traverseCauses = traverseCauses; + } + + @Override + public Boolean classify(Throwable classifiable) { + Boolean classified = super.classify(classifiable); + if (!this.traverseCauses) { + return classified; + } + + /* + * If the result is the default, we need to find out if it was by default + * or so configured; if default, try the cause(es). + */ + if (classified.equals(this.getDefault())) { + Throwable cause = classifiable; + do { + if (this.getClassified().containsKey(classifiable.getClass())) { + return classified; // non-default classification + } + cause = cause.getCause(); + classified = super.classify(cause); + } + while (cause != null && classified.equals(this.getDefault())); + } + + return classified; + } + } diff --git a/src/main/java/org/springframework/classify/SubclassClassifier.java b/src/main/java/org/springframework/classify/SubclassClassifier.java index dd7cce2..4045fad 100644 --- a/src/main/java/org/springframework/classify/SubclassClassifier.java +++ b/src/main/java/org/springframework/classify/SubclassClassifier.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2007 the original author or authors. + * Copyright 2006-2013 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. @@ -31,9 +31,10 @@ import java.util.concurrent.ConcurrentMap; * map, or is a subclass of one of the keys, then the map entry value for that * key is returned. Otherwise returns the default value which is null by * default. - * + * * @author Dave Syer - * + * @author Gary Russell + * */ public class SubclassClassifier implements Classifier { @@ -43,7 +44,7 @@ public class SubclassClassifier implements Classifier { /** * Create a {@link SubclassClassifier} with null default value. - * + * */ public SubclassClassifier() { this(null); @@ -51,7 +52,7 @@ public class SubclassClassifier implements Classifier { /** * Create a {@link SubclassClassifier} with supplied default value. - * + * * @param defaultValue */ public SubclassClassifier(C defaultValue) { @@ -60,7 +61,7 @@ public class SubclassClassifier implements Classifier { /** * Create a {@link SubclassClassifier} with supplied default value. - * + * * @param defaultValue */ public SubclassClassifier(Map, C> typeMap, C defaultValue) { @@ -72,7 +73,7 @@ public class SubclassClassifier implements Classifier { /** * Public setter for the default value for mapping keys that are not found * in the map (or their subclasses). Defaults to false. - * + * * @param defaultValue the default value to set */ public void setDefaultValue(C defaultValue) { @@ -83,7 +84,7 @@ public class SubclassClassifier implements Classifier { * Set the classifications up as a map. The keys are types and these will be * mapped along with all their subclasses to the corresponding value. The * most specific types will match first. - * + * * @param map a map from type to class */ public void setTypeMap(Map, C> map) { @@ -93,7 +94,7 @@ public class SubclassClassifier implements Classifier { /** * Return the value from the type map whose key is the class of the given * Throwable, or its nearest ancestor if a subclass. - * + * */ public C classify(T classifiable) { @@ -128,11 +129,15 @@ public class SubclassClassifier implements Classifier { return defaultValue; } + protected Map, C> getClassified() { + return classified; + } + /** * Comparator for classes to order by inheritance. - * + * * @author Dave Syer - * + * */ @SuppressWarnings("serial") private static class ClassComparator implements Comparator>, Serializable { diff --git a/src/main/java/org/springframework/retry/policy/SimpleRetryPolicy.java b/src/main/java/org/springframework/retry/policy/SimpleRetryPolicy.java index ed2f727..b6d2f73 100644 --- a/src/main/java/org/springframework/retry/policy/SimpleRetryPolicy.java +++ b/src/main/java/org/springframework/retry/policy/SimpleRetryPolicy.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2007 the original author or authors. + * Copyright 2006-2013 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. @@ -26,21 +26,22 @@ import org.springframework.retry.context.RetryContextSupport; import org.springframework.util.ClassUtils; /** - * + * * Simple retry policy that retries a fixed number of times for a set of named * exceptions (and subclasses). The number of attempts includes the initial try, * so e.g. - * + * *
  * retryTemplate = new RetryTemplate(new SimpleRetryPolicy(3));
  * retryTemplate.execute(callback);
  * 
- * + * * will execute the callback at least once, and as many as 3 times. - * + * * @author Dave Syer * @author Rob Harrop - * + * @author Gary Russell + * */ public class SimpleRetryPolicy implements RetryPolicy { @@ -65,19 +66,34 @@ public class SimpleRetryPolicy implements RetryPolicy { /** * Create a {@link SimpleRetryPolicy} with the specified number of retry * attempts. - * + * * @param maxAttempts * @param retryableExceptions */ public SimpleRetryPolicy(int maxAttempts, Map, Boolean> retryableExceptions) { + this(maxAttempts, retryableExceptions, false); + } + + /** + * Create a {@link SimpleRetryPolicy} with the specified number of retry + * attempts. If traverseCauses is true, the exception causes will be traversed until + * a match is found. + * + * @param maxAttempts + * @param retryableExceptions + * @param traverseCauses + */ + public SimpleRetryPolicy(int maxAttempts, Map, Boolean> retryableExceptions, + boolean traverseCauses) { super(); this.maxAttempts = maxAttempts; this.retryableClassifier = new BinaryExceptionClassifier(retryableExceptions); + this.retryableClassifier.setTraverseCauses(traverseCauses); } /** * Setter for retry attempts. - * + * * @param retryAttempts the number of attempts before a retry becomes * impossible. */ @@ -87,7 +103,7 @@ public class SimpleRetryPolicy implements RetryPolicy { /** * The maximum number of retry attempts before failure. - * + * * @return the maximum number of attempts */ public int getMaxAttempts() { @@ -96,9 +112,9 @@ public class SimpleRetryPolicy implements RetryPolicy { /** * Test for retryable operation based on the status. - * + * * @see org.springframework.retry.RetryPolicy#canRetry(org.springframework.retry.RetryContext) - * + * * @return true if the last exception was retryable and the number of * attempts so far is less than the limit. */ @@ -115,7 +131,7 @@ public class SimpleRetryPolicy implements RetryPolicy { /** * Update the status with another attempted retry and the latest exception. - * + * * @see RetryPolicy#registerThrowable(RetryContext, Throwable) */ public void registerThrowable(RetryContext context, Throwable throwable) { @@ -127,7 +143,7 @@ public class SimpleRetryPolicy implements RetryPolicy { * Get a status object that can be used to track the current operation * according to this policy. Has to be aware of the latest exception and the * number of attempts. - * + * * @see org.springframework.retry.RetryPolicy#open(RetryContext) */ public RetryContext open(RetryContext parent) { @@ -143,7 +159,7 @@ public class SimpleRetryPolicy implements RetryPolicy { /** * Delegates to an exception classifier. - * + * * @param ex * @return true if this exception or its ancestors have been registered as * retryable. @@ -152,7 +168,8 @@ public class SimpleRetryPolicy implements RetryPolicy { return retryableClassifier.classify(ex); } - public String toString() { - return ClassUtils.getShortName(getClass()) + "[maxAttempts=" + maxAttempts + "]"; - } + @Override + public String toString() { + return ClassUtils.getShortName(getClass()) + "[maxAttempts=" + maxAttempts + "]"; + } } diff --git a/src/test/java/org/springframework/classify/BinaryExceptionClassifierTests.java b/src/test/java/org/springframework/classify/BinaryExceptionClassifierTests.java index 906f590..b99103f 100644 --- a/src/test/java/org/springframework/classify/BinaryExceptionClassifierTests.java +++ b/src/test/java/org/springframework/classify/BinaryExceptionClassifierTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2007 the original author or authors. + * Copyright 2006-2013 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. @@ -56,6 +56,15 @@ public class BinaryExceptionClassifierTests { assertTrue(new BinaryExceptionClassifier(set).classify(new IllegalStateException("Foo"))); } + @Test + public void testClassifyExactMatchInCause() { + Collection> set = Collections + .> singleton(IllegalStateException.class); + BinaryExceptionClassifier binaryExceptionClassifier = new BinaryExceptionClassifier(set); + binaryExceptionClassifier.setTraverseCauses(true); + assertTrue(binaryExceptionClassifier.classify(new RuntimeException(new IllegalStateException("Foo")))); + } + @Test public void testTypesProvidedInConstructor() { classifier = new BinaryExceptionClassifier(Collections @@ -69,4 +78,12 @@ public class BinaryExceptionClassifierTests { .> singleton(IllegalStateException.class), false); assertFalse(classifier.classify(new IllegalStateException("Foo"))); } + + @Test + public void testTypesProvidedInConstructorWithNonDefaultInCause() { + classifier = new BinaryExceptionClassifier(Collections + .> singleton(IllegalStateException.class), false); + classifier.setTraverseCauses(true); + assertFalse(classifier.classify(new RuntimeException(new RuntimeException(new IllegalStateException("Foo"))))); + } } diff --git a/src/test/java/org/springframework/retry/policy/SimpleRetryPolicyTests.java b/src/test/java/org/springframework/retry/policy/SimpleRetryPolicyTests.java index e48cd94..dc33e69 100644 --- a/src/test/java/org/springframework/retry/policy/SimpleRetryPolicyTests.java +++ b/src/test/java/org/springframework/retry/policy/SimpleRetryPolicyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2007 the original author or authors. + * Copyright 2006-2013 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. @@ -28,6 +28,7 @@ import java.util.HashMap; import java.util.Map; import org.junit.Test; + import org.springframework.retry.RetryContext; public class SimpleRetryPolicyTests { @@ -98,6 +99,17 @@ public class SimpleRetryPolicyTests { assertTrue(policy.canRetry(context)); } + @Test + public void testRetryableWithCause() throws Exception { + Map, Boolean> map = new HashMap, Boolean>(); + map.put(RuntimeException.class, true); + SimpleRetryPolicy policy = new SimpleRetryPolicy(3, map, true); + RetryContext context = policy.open(null); + assertNotNull(context); + policy.registerThrowable(context, new Exception(new RuntimeException("foo"))); + assertTrue(policy.canRetry(context)); + } + @Test public void testParent() throws Exception { SimpleRetryPolicy policy = new SimpleRetryPolicy();