From 5a70f997381b51c79bec05dda2ff647f821ef5b7 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Mon, 22 Aug 2016 12:03:50 +0100 Subject: [PATCH] Line endings --- .../classify/BackToBackPatternClassifier.java | 166 ++--- .../classify/ClassifierAdapter.java | 202 +++--- .../classify/PatternMatchingClassifier.java | 150 ++--- .../classify/annotation/Classifier.java | 76 +-- .../StatefulRetryOperationsInterceptor.java | 468 +++++++------- .../BackToBackPatternClassifierTests.java | 142 ++-- .../classify/ClassifierAdapterTests.java | 248 +++---- .../PatternMatchingClassifierTests.java | 114 ++-- ...atefulRetryOperationsInterceptorTests.java | 606 +++++++++--------- 9 files changed, 1086 insertions(+), 1086 deletions(-) diff --git a/src/main/java/org/springframework/classify/BackToBackPatternClassifier.java b/src/main/java/org/springframework/classify/BackToBackPatternClassifier.java index cfc3ca2..4acd93d 100644 --- a/src/main/java/org/springframework/classify/BackToBackPatternClassifier.java +++ b/src/main/java/org/springframework/classify/BackToBackPatternClassifier.java @@ -1,83 +1,83 @@ -/* - * Copyright 2006-2007 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 - * - * http://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.classify; - -import java.util.Map; - -/** - * A special purpose {@link Classifier} with easy configuration options for - * mapping from one arbitrary type of object to another via a pattern matcher. - * - * @author Dave Syer - * - */ -public class BackToBackPatternClassifier implements Classifier { - - private Classifier router; - - private Classifier matcher; - - /** - * Default constructor, provided as a convenience for people using setter - * injection. - */ - public BackToBackPatternClassifier() { - } - - /** - * Set up a classifier with input to the router and output from the matcher. - * - * @param router see {@link #setRouterDelegate(Object)} - * @param matcher see {@link #setMatcherMap(Map)} - */ - public BackToBackPatternClassifier(Classifier router, Classifier matcher) { - super(); - this.router = router; - this.matcher = matcher; - } - - /** - * A convenience method for creating a pattern matching classifier for the - * matcher component. - * - * @param map maps pattern keys with wildcards to output values - */ - public void setMatcherMap(Map map) { - this.matcher = new PatternMatchingClassifier(map); - } - - /** - * A convenience method of creating a router classifier based on a plain old - * Java Object. The object provided must have precisely one public method - * that either has the @Classifier annotation or accepts a single argument - * and outputs a String. This will be used to create an input classifier for - * the router component. - * - * @param delegate the delegate object used to create a router classifier - */ - public void setRouterDelegate(Object delegate) { - this.router = new ClassifierAdapter(delegate); - } - - /** - * Classify the input and map to a String, then take that and put it into a - * pattern matcher to match to an output value. - */ - public T classify(C classifiable) { - return matcher.classify(router.classify(classifiable)); - } - -} +/* + * Copyright 2006-2007 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 + * + * http://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.classify; + +import java.util.Map; + +/** + * A special purpose {@link Classifier} with easy configuration options for + * mapping from one arbitrary type of object to another via a pattern matcher. + * + * @author Dave Syer + * + */ +public class BackToBackPatternClassifier implements Classifier { + + private Classifier router; + + private Classifier matcher; + + /** + * Default constructor, provided as a convenience for people using setter + * injection. + */ + public BackToBackPatternClassifier() { + } + + /** + * Set up a classifier with input to the router and output from the matcher. + * + * @param router see {@link #setRouterDelegate(Object)} + * @param matcher see {@link #setMatcherMap(Map)} + */ + public BackToBackPatternClassifier(Classifier router, Classifier matcher) { + super(); + this.router = router; + this.matcher = matcher; + } + + /** + * A convenience method for creating a pattern matching classifier for the + * matcher component. + * + * @param map maps pattern keys with wildcards to output values + */ + public void setMatcherMap(Map map) { + this.matcher = new PatternMatchingClassifier(map); + } + + /** + * A convenience method of creating a router classifier based on a plain old + * Java Object. The object provided must have precisely one public method + * that either has the @Classifier annotation or accepts a single argument + * and outputs a String. This will be used to create an input classifier for + * the router component. + * + * @param delegate the delegate object used to create a router classifier + */ + public void setRouterDelegate(Object delegate) { + this.router = new ClassifierAdapter(delegate); + } + + /** + * Classify the input and map to a String, then take that and put it into a + * pattern matcher to match to an output value. + */ + public T classify(C classifiable) { + return matcher.classify(router.classify(classifiable)); + } + +} diff --git a/src/main/java/org/springframework/classify/ClassifierAdapter.java b/src/main/java/org/springframework/classify/ClassifierAdapter.java index c5767a9..feb2a2d 100644 --- a/src/main/java/org/springframework/classify/ClassifierAdapter.java +++ b/src/main/java/org/springframework/classify/ClassifierAdapter.java @@ -1,101 +1,101 @@ -/* - * Copyright 2006-2007 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 - * - * http://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.classify; - -import org.springframework.classify.util.MethodInvoker; -import org.springframework.classify.util.MethodInvokerUtils; -import org.springframework.util.Assert; - -/** - * Wrapper for an object to adapt it to the {@link Classifier} interface. - * - * @author Dave Syer - * - */ -public class ClassifierAdapter implements Classifier { - - private MethodInvoker invoker; - - private Classifier classifier; - - /** - * Default constructor for use with setter injection. - */ - public ClassifierAdapter() { - super(); - } - - /** - * Create a new {@link Classifier} from the delegate provided. Use the - * constructor as an alternative to the {@link #setDelegate(Object)} method. - * - * @param delegate the delegate - */ - public ClassifierAdapter(Object delegate) { - setDelegate(delegate); - } - - /** - * Create a new {@link Classifier} from the delegate provided. Use the - * constructor as an alternative to the {@link #setDelegate(Classifier)} - * method. - * - * @param delegate the classifier to delegate to - */ - public ClassifierAdapter(Classifier delegate) { - classifier = delegate; - } - - public void setDelegate(Classifier delegate) { - classifier = delegate; - invoker = null; - } - - /** - * Search for the - * {@link org.springframework.classify.annotation.Classifier - * Classifier} annotation on a method in the supplied delegate and use that - * to create a {@link Classifier} from the parameter type to the return - * type. If the annotation is not found a unique non-void method with a - * single parameter will be used, if it exists. The signature of the method - * cannot be checked here, so might be a runtime exception when the method - * is invoked if the signature doesn't match the classifier types. - * - * @param delegate an object with an annotated method - */ - public final void setDelegate(Object delegate) { - classifier = null; - invoker = MethodInvokerUtils.getMethodInvokerByAnnotation( - org.springframework.classify.annotation.Classifier.class, delegate); - if (invoker == null) { - invoker = MethodInvokerUtils. getMethodInvokerForSingleArgument(delegate); - } - Assert.state(invoker != null, "No single argument public method with or without " - + "@Classifier was found in delegate of type " + delegate.getClass()); - } - - /** - * {@inheritDoc} - */ - @SuppressWarnings("unchecked") - public T classify(C classifiable) { - if (classifier != null) { - return classifier.classify(classifiable); - } - return (T) invoker.invokeMethod(classifiable); - } - -} +/* + * Copyright 2006-2007 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 + * + * http://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.classify; + +import org.springframework.classify.util.MethodInvoker; +import org.springframework.classify.util.MethodInvokerUtils; +import org.springframework.util.Assert; + +/** + * Wrapper for an object to adapt it to the {@link Classifier} interface. + * + * @author Dave Syer + * + */ +public class ClassifierAdapter implements Classifier { + + private MethodInvoker invoker; + + private Classifier classifier; + + /** + * Default constructor for use with setter injection. + */ + public ClassifierAdapter() { + super(); + } + + /** + * Create a new {@link Classifier} from the delegate provided. Use the + * constructor as an alternative to the {@link #setDelegate(Object)} method. + * + * @param delegate the delegate + */ + public ClassifierAdapter(Object delegate) { + setDelegate(delegate); + } + + /** + * Create a new {@link Classifier} from the delegate provided. Use the + * constructor as an alternative to the {@link #setDelegate(Classifier)} + * method. + * + * @param delegate the classifier to delegate to + */ + public ClassifierAdapter(Classifier delegate) { + classifier = delegate; + } + + public void setDelegate(Classifier delegate) { + classifier = delegate; + invoker = null; + } + + /** + * Search for the + * {@link org.springframework.classify.annotation.Classifier + * Classifier} annotation on a method in the supplied delegate and use that + * to create a {@link Classifier} from the parameter type to the return + * type. If the annotation is not found a unique non-void method with a + * single parameter will be used, if it exists. The signature of the method + * cannot be checked here, so might be a runtime exception when the method + * is invoked if the signature doesn't match the classifier types. + * + * @param delegate an object with an annotated method + */ + public final void setDelegate(Object delegate) { + classifier = null; + invoker = MethodInvokerUtils.getMethodInvokerByAnnotation( + org.springframework.classify.annotation.Classifier.class, delegate); + if (invoker == null) { + invoker = MethodInvokerUtils. getMethodInvokerForSingleArgument(delegate); + } + Assert.state(invoker != null, "No single argument public method with or without " + + "@Classifier was found in delegate of type " + delegate.getClass()); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public T classify(C classifiable) { + if (classifier != null) { + return classifier.classify(classifiable); + } + return (T) invoker.invokeMethod(classifiable); + } + +} diff --git a/src/main/java/org/springframework/classify/PatternMatchingClassifier.java b/src/main/java/org/springframework/classify/PatternMatchingClassifier.java index e9ba899..b020047 100644 --- a/src/main/java/org/springframework/classify/PatternMatchingClassifier.java +++ b/src/main/java/org/springframework/classify/PatternMatchingClassifier.java @@ -1,75 +1,75 @@ -/* - * Copyright 2006-2007 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 - * - * http://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.classify; - -import java.util.HashMap; -import java.util.Map; - -/** - * A {@link Classifier} that maps from String patterns with wildcards to a set - * of values of a given type. An input String is matched with the most specific - * pattern possible to the corresponding value in an input map. A default value - * should be specified with a pattern key of "*". - * - * @author Dave Syer - * - */ -public class PatternMatchingClassifier implements Classifier { - - private PatternMatcher values; - - /** - * Default constructor. Use the setter or the other constructor to create a - * sensible classifier, otherwise all inputs will cause an exception. - */ - public PatternMatchingClassifier() { - this(new HashMap()); - } - - /** - * Create a classifier from the provided map. The keys are patterns, using - * '?' as a single character and '*' as multi-character wildcard. - * - * @param values the values to use in the {@link PatternMatcher} - */ - public PatternMatchingClassifier(Map values) { - super(); - this.values = new PatternMatcher(values); - } - - /** - * A map from pattern to value - * @param values the pattern map to set - */ - public void setPatternMap(Map values) { - this.values = new PatternMatcher(values); - } - - /** - * Classify the input by matching it against the patterns provided in - * {@link #setPatternMap(Map)}. The most specific pattern that matches will - * be used to locate a value. - * - * @return the value matching the most specific pattern possible - * - * @throws IllegalStateException if no matching value is found. - */ - public T classify(String classifiable) { - T value = values.match(classifiable); - return value; - } - -} +/* + * Copyright 2006-2007 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 + * + * http://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.classify; + +import java.util.HashMap; +import java.util.Map; + +/** + * A {@link Classifier} that maps from String patterns with wildcards to a set + * of values of a given type. An input String is matched with the most specific + * pattern possible to the corresponding value in an input map. A default value + * should be specified with a pattern key of "*". + * + * @author Dave Syer + * + */ +public class PatternMatchingClassifier implements Classifier { + + private PatternMatcher values; + + /** + * Default constructor. Use the setter or the other constructor to create a + * sensible classifier, otherwise all inputs will cause an exception. + */ + public PatternMatchingClassifier() { + this(new HashMap()); + } + + /** + * Create a classifier from the provided map. The keys are patterns, using + * '?' as a single character and '*' as multi-character wildcard. + * + * @param values the values to use in the {@link PatternMatcher} + */ + public PatternMatchingClassifier(Map values) { + super(); + this.values = new PatternMatcher(values); + } + + /** + * A map from pattern to value + * @param values the pattern map to set + */ + public void setPatternMap(Map values) { + this.values = new PatternMatcher(values); + } + + /** + * Classify the input by matching it against the patterns provided in + * {@link #setPatternMap(Map)}. The most specific pattern that matches will + * be used to locate a value. + * + * @return the value matching the most specific pattern possible + * + * @throws IllegalStateException if no matching value is found. + */ + public T classify(String classifiable) { + T value = values.match(classifiable); + return value; + } + +} diff --git a/src/main/java/org/springframework/classify/annotation/Classifier.java b/src/main/java/org/springframework/classify/annotation/Classifier.java index b661329..fdd612b 100644 --- a/src/main/java/org/springframework/classify/annotation/Classifier.java +++ b/src/main/java/org/springframework/classify/annotation/Classifier.java @@ -1,38 +1,38 @@ -/* - * Copyright 2006-2007 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 - * - * http://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.classify.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Mark a method as capable of classifying its input to an instance of its - * output. Should only be used on non-void methods with one parameter. - * - * @author Dave Syer - * - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -@Inherited -@Documented -public @interface Classifier { - -} +/* + * Copyright 2006-2007 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 + * + * http://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.classify.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Mark a method as capable of classifying its input to an instance of its + * output. Should only be used on non-void methods with one parameter. + * + * @author Dave Syer + * + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +public @interface Classifier { + +} diff --git a/src/main/java/org/springframework/retry/interceptor/StatefulRetryOperationsInterceptor.java b/src/main/java/org/springframework/retry/interceptor/StatefulRetryOperationsInterceptor.java index a5cc01d..8789a73 100644 --- a/src/main/java/org/springframework/retry/interceptor/StatefulRetryOperationsInterceptor.java +++ b/src/main/java/org/springframework/retry/interceptor/StatefulRetryOperationsInterceptor.java @@ -1,234 +1,234 @@ -/* - * Copyright 2006-2007 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 - * - * http://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.retry.interceptor; - -import java.util.Arrays; - -import org.aopalliance.intercept.MethodInterceptor; -import org.aopalliance.intercept.MethodInvocation; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.classify.Classifier; -import org.springframework.retry.RecoveryCallback; -import org.springframework.retry.RetryCallback; -import org.springframework.retry.RetryContext; -import org.springframework.retry.RetryOperations; -import org.springframework.retry.RetryState; -import org.springframework.retry.policy.NeverRetryPolicy; -import org.springframework.retry.support.DefaultRetryState; -import org.springframework.retry.support.RetryTemplate; -import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; - -/** - * A {@link MethodInterceptor} that can be used to automatically retry calls to a method - * on a service if it fails. The argument to the service method is treated as an item to - * be remembered in case the call fails. So the retry operation is stateful, and the item - * that failed is tracked by its unique key (via {@link MethodArgumentsKeyGenerator}) - * until the retry is exhausted, at which point the {@link MethodInvocationRecoverer} is - * called. - * - * The main use case for this is where the service is transactional, via a transaction - * interceptor on the interceptor chain. In this case the retry (and recovery on - * exhausted) always happens in a new transaction. - * - * The injected {@link RetryOperations} is used to control the number of retries. By - * default it will retry a fixed number of times, according to the defaults in - * {@link RetryTemplate}. - * - * @author Dave Syer - */ -public class StatefulRetryOperationsInterceptor implements MethodInterceptor { - - private transient final Log logger = LogFactory.getLog(getClass()); - - private MethodArgumentsKeyGenerator keyGenerator; - - private MethodInvocationRecoverer recoverer; - - private NewMethodArgumentsIdentifier newMethodArgumentsIdentifier; - - private RetryOperations retryOperations; - - private String label; - - private Classifier rollbackClassifier; - - public StatefulRetryOperationsInterceptor() { - RetryTemplate retryTemplate = new RetryTemplate(); - retryTemplate.setRetryPolicy(new NeverRetryPolicy()); - this.retryOperations = retryTemplate; - } - - public void setRetryOperations(RetryOperations retryTemplate) { - Assert.notNull(retryTemplate, "'retryOperations' cannot be null."); - this.retryOperations = retryTemplate; - } - - /** - * Public setter for the {@link MethodInvocationRecoverer} to use if the retry is - * exhausted. The recoverer should be able to return an object of the same type as the - * target object because its return value will be used to return to the caller in the - * case of a recovery. - * @param recoverer the {@link MethodInvocationRecoverer} to set - */ - public void setRecoverer(MethodInvocationRecoverer recoverer) { - this.recoverer = recoverer; - } - - /** - * Rollback classifier for the retry state. Default to null (meaning rollback - * for all). - * - * @param rollbackClassifier the rollbackClassifier to set - */ - public void setRollbackClassifier( - Classifier rollbackClassifier) { - this.rollbackClassifier = rollbackClassifier; - } - - public void setKeyGenerator(MethodArgumentsKeyGenerator keyGenerator) { - this.keyGenerator = keyGenerator; - } - - public void setLabel(String label) { - this.label = label; - } - - /** - * Public setter for the {@link NewMethodArgumentsIdentifier}. Only set this if the - * arguments to the intercepted method can be inspected to find out if they have never - * been processed before. - * @param newMethodArgumentsIdentifier the {@link NewMethodArgumentsIdentifier} to set - */ - public void setNewItemIdentifier( - NewMethodArgumentsIdentifier newMethodArgumentsIdentifier) { - this.newMethodArgumentsIdentifier = newMethodArgumentsIdentifier; - } - - /** - * Wrap the method invocation in a stateful retry with the policy and other helpers - * provided. If there is a failure the exception will generally be re-thrown. The only - * time it is not re-thrown is when retry is exhausted and the recovery path is taken - * (though the {@link MethodInvocationRecoverer} provided if there is one). In that - * case the value returned from the method invocation will be the value returned by - * the recoverer (so the return type for that should be the same as the intercepted - * method). - * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) - * @see MethodInvocationRecoverer#recover(Object[], Throwable) - * - */ - @Override - public Object invoke(final MethodInvocation invocation) throws Throwable { - - if (this.logger.isDebugEnabled()) { - this.logger.debug("Executing proxied method in stateful retry: " - + invocation.getStaticPart() + "(" - + ObjectUtils.getIdentityHexString(invocation) + ")"); - } - - Object[] args = invocation.getArguments(); - Object arg = args; - if (args.length == 1) { - arg = args[0]; - } - final Object item = arg; - - RetryState retryState = new DefaultRetryState( - this.keyGenerator != null ? this.keyGenerator.getKey(args) : item, - this.newMethodArgumentsIdentifier != null - && this.newMethodArgumentsIdentifier.isNew(args), - this.rollbackClassifier); - - Object result = this.retryOperations.execute( - new MethodInvocationRetryCallback(invocation, label), this.recoverer != null - ? new ItemRecovererCallback(args, this.recoverer) : null, - retryState); - - if (this.logger.isDebugEnabled()) { - this.logger.debug("Exiting proxied method in stateful retry with result: (" - + result + ")"); - } - - return result; - - } - - /** - * @author Dave Syer - * - */ - private static final class MethodInvocationRetryCallback - implements RetryCallback { - - private final MethodInvocation invocation; - private String label; - - private MethodInvocationRetryCallback(MethodInvocation invocation, String label) { - this.invocation = invocation; - if (label!=null) { - this.label = label; - } else { - this.label = invocation.getMethod().toGenericString(); - } - } - - @Override - public Object doWithRetry(RetryContext context) throws Exception { - context.setAttribute(RetryContext.NAME, label); - try { - return this.invocation.proceed(); - } - catch (Exception e) { - throw e; - } - catch (Error e) { - throw e; - } - catch (Throwable e) { - throw new IllegalStateException(e); - } - } - } - - /** - * @author Dave Syer - * - */ - private static final class ItemRecovererCallback implements RecoveryCallback { - - private final Object[] args; - - private final MethodInvocationRecoverer recoverer; - - /** - * @param args the item that failed. - */ - private ItemRecovererCallback(Object[] args, - MethodInvocationRecoverer recoverer) { - this.args = Arrays.asList(args).toArray(); - this.recoverer = recoverer; - } - - @Override - public Object recover(RetryContext context) { - return this.recoverer.recover(this.args, context.getLastThrowable()); - } - - } - -} +/* + * Copyright 2006-2007 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 + * + * http://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.retry.interceptor; + +import java.util.Arrays; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.classify.Classifier; +import org.springframework.retry.RecoveryCallback; +import org.springframework.retry.RetryCallback; +import org.springframework.retry.RetryContext; +import org.springframework.retry.RetryOperations; +import org.springframework.retry.RetryState; +import org.springframework.retry.policy.NeverRetryPolicy; +import org.springframework.retry.support.DefaultRetryState; +import org.springframework.retry.support.RetryTemplate; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * A {@link MethodInterceptor} that can be used to automatically retry calls to a method + * on a service if it fails. The argument to the service method is treated as an item to + * be remembered in case the call fails. So the retry operation is stateful, and the item + * that failed is tracked by its unique key (via {@link MethodArgumentsKeyGenerator}) + * until the retry is exhausted, at which point the {@link MethodInvocationRecoverer} is + * called. + * + * The main use case for this is where the service is transactional, via a transaction + * interceptor on the interceptor chain. In this case the retry (and recovery on + * exhausted) always happens in a new transaction. + * + * The injected {@link RetryOperations} is used to control the number of retries. By + * default it will retry a fixed number of times, according to the defaults in + * {@link RetryTemplate}. + * + * @author Dave Syer + */ +public class StatefulRetryOperationsInterceptor implements MethodInterceptor { + + private transient final Log logger = LogFactory.getLog(getClass()); + + private MethodArgumentsKeyGenerator keyGenerator; + + private MethodInvocationRecoverer recoverer; + + private NewMethodArgumentsIdentifier newMethodArgumentsIdentifier; + + private RetryOperations retryOperations; + + private String label; + + private Classifier rollbackClassifier; + + public StatefulRetryOperationsInterceptor() { + RetryTemplate retryTemplate = new RetryTemplate(); + retryTemplate.setRetryPolicy(new NeverRetryPolicy()); + this.retryOperations = retryTemplate; + } + + public void setRetryOperations(RetryOperations retryTemplate) { + Assert.notNull(retryTemplate, "'retryOperations' cannot be null."); + this.retryOperations = retryTemplate; + } + + /** + * Public setter for the {@link MethodInvocationRecoverer} to use if the retry is + * exhausted. The recoverer should be able to return an object of the same type as the + * target object because its return value will be used to return to the caller in the + * case of a recovery. + * @param recoverer the {@link MethodInvocationRecoverer} to set + */ + public void setRecoverer(MethodInvocationRecoverer recoverer) { + this.recoverer = recoverer; + } + + /** + * Rollback classifier for the retry state. Default to null (meaning rollback + * for all). + * + * @param rollbackClassifier the rollbackClassifier to set + */ + public void setRollbackClassifier( + Classifier rollbackClassifier) { + this.rollbackClassifier = rollbackClassifier; + } + + public void setKeyGenerator(MethodArgumentsKeyGenerator keyGenerator) { + this.keyGenerator = keyGenerator; + } + + public void setLabel(String label) { + this.label = label; + } + + /** + * Public setter for the {@link NewMethodArgumentsIdentifier}. Only set this if the + * arguments to the intercepted method can be inspected to find out if they have never + * been processed before. + * @param newMethodArgumentsIdentifier the {@link NewMethodArgumentsIdentifier} to set + */ + public void setNewItemIdentifier( + NewMethodArgumentsIdentifier newMethodArgumentsIdentifier) { + this.newMethodArgumentsIdentifier = newMethodArgumentsIdentifier; + } + + /** + * Wrap the method invocation in a stateful retry with the policy and other helpers + * provided. If there is a failure the exception will generally be re-thrown. The only + * time it is not re-thrown is when retry is exhausted and the recovery path is taken + * (though the {@link MethodInvocationRecoverer} provided if there is one). In that + * case the value returned from the method invocation will be the value returned by + * the recoverer (so the return type for that should be the same as the intercepted + * method). + * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) + * @see MethodInvocationRecoverer#recover(Object[], Throwable) + * + */ + @Override + public Object invoke(final MethodInvocation invocation) throws Throwable { + + if (this.logger.isDebugEnabled()) { + this.logger.debug("Executing proxied method in stateful retry: " + + invocation.getStaticPart() + "(" + + ObjectUtils.getIdentityHexString(invocation) + ")"); + } + + Object[] args = invocation.getArguments(); + Object arg = args; + if (args.length == 1) { + arg = args[0]; + } + final Object item = arg; + + RetryState retryState = new DefaultRetryState( + this.keyGenerator != null ? this.keyGenerator.getKey(args) : item, + this.newMethodArgumentsIdentifier != null + && this.newMethodArgumentsIdentifier.isNew(args), + this.rollbackClassifier); + + Object result = this.retryOperations.execute( + new MethodInvocationRetryCallback(invocation, label), this.recoverer != null + ? new ItemRecovererCallback(args, this.recoverer) : null, + retryState); + + if (this.logger.isDebugEnabled()) { + this.logger.debug("Exiting proxied method in stateful retry with result: (" + + result + ")"); + } + + return result; + + } + + /** + * @author Dave Syer + * + */ + private static final class MethodInvocationRetryCallback + implements RetryCallback { + + private final MethodInvocation invocation; + private String label; + + private MethodInvocationRetryCallback(MethodInvocation invocation, String label) { + this.invocation = invocation; + if (label!=null) { + this.label = label; + } else { + this.label = invocation.getMethod().toGenericString(); + } + } + + @Override + public Object doWithRetry(RetryContext context) throws Exception { + context.setAttribute(RetryContext.NAME, label); + try { + return this.invocation.proceed(); + } + catch (Exception e) { + throw e; + } + catch (Error e) { + throw e; + } + catch (Throwable e) { + throw new IllegalStateException(e); + } + } + } + + /** + * @author Dave Syer + * + */ + private static final class ItemRecovererCallback implements RecoveryCallback { + + private final Object[] args; + + private final MethodInvocationRecoverer recoverer; + + /** + * @param args the item that failed. + */ + private ItemRecovererCallback(Object[] args, + MethodInvocationRecoverer recoverer) { + this.args = Arrays.asList(args).toArray(); + this.recoverer = recoverer; + } + + @Override + public Object recover(RetryContext context) { + return this.recoverer.recover(this.args, context.getLastThrowable()); + } + + } + +} diff --git a/src/test/java/org/springframework/classify/BackToBackPatternClassifierTests.java b/src/test/java/org/springframework/classify/BackToBackPatternClassifierTests.java index 934164c..aeeaaae 100644 --- a/src/test/java/org/springframework/classify/BackToBackPatternClassifierTests.java +++ b/src/test/java/org/springframework/classify/BackToBackPatternClassifierTests.java @@ -1,71 +1,71 @@ -/* - * Copyright 2006-2007 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 - * - * http://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.classify; - -import static org.junit.Assert.assertEquals; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import org.junit.Before; -import org.junit.Test; -import org.springframework.classify.annotation.Classifier; - -/** - * @author Dave Syer - * - */ -public class BackToBackPatternClassifierTests { - - private BackToBackPatternClassifier classifier = new BackToBackPatternClassifier(); - - private Map map; - - @Before - public void createMap() { - map = new HashMap(); - map.put("foo", "bar"); - map.put("*", "spam"); - } - - @Test(expected = NullPointerException.class) - public void testNoClassifiers() { - classifier.classify("foo"); - } - - @Test - public void testCreateFromConstructor() { - classifier = new BackToBackPatternClassifier( - new PatternMatchingClassifier(Collections.singletonMap( - "oof", "bucket")), - new PatternMatchingClassifier(map)); - assertEquals("spam", classifier.classify("oof")); - } - - @Test - public void testSetRouterDelegate() { - classifier.setRouterDelegate(new Object() { - @Classifier - public String convert(String value) { - return "bucket"; - } - }); - classifier.setMatcherMap(map); - assertEquals("spam", classifier.classify("oof")); - } - -} +/* + * Copyright 2006-2007 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 + * + * http://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.classify; + +import static org.junit.Assert.assertEquals; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.classify.annotation.Classifier; + +/** + * @author Dave Syer + * + */ +public class BackToBackPatternClassifierTests { + + private BackToBackPatternClassifier classifier = new BackToBackPatternClassifier(); + + private Map map; + + @Before + public void createMap() { + map = new HashMap(); + map.put("foo", "bar"); + map.put("*", "spam"); + } + + @Test(expected = NullPointerException.class) + public void testNoClassifiers() { + classifier.classify("foo"); + } + + @Test + public void testCreateFromConstructor() { + classifier = new BackToBackPatternClassifier( + new PatternMatchingClassifier(Collections.singletonMap( + "oof", "bucket")), + new PatternMatchingClassifier(map)); + assertEquals("spam", classifier.classify("oof")); + } + + @Test + public void testSetRouterDelegate() { + classifier.setRouterDelegate(new Object() { + @Classifier + public String convert(String value) { + return "bucket"; + } + }); + classifier.setMatcherMap(map); + assertEquals("spam", classifier.classify("oof")); + } + +} diff --git a/src/test/java/org/springframework/classify/ClassifierAdapterTests.java b/src/test/java/org/springframework/classify/ClassifierAdapterTests.java index c0cb71d..83c46f8 100644 --- a/src/test/java/org/springframework/classify/ClassifierAdapterTests.java +++ b/src/test/java/org/springframework/classify/ClassifierAdapterTests.java @@ -1,124 +1,124 @@ -/* - * Copyright 2006-2007 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 - * - * http://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.classify; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; -import org.springframework.classify.annotation.Classifier; - -/** - * @author Dave Syer - * - */ -public class ClassifierAdapterTests { - - private ClassifierAdapter adapter = new ClassifierAdapter(); - - @Test - public void testClassifierAdapterObject() { - adapter = new ClassifierAdapter(new Object() { - @Classifier - public Integer getValue(String key) { - return Integer.parseInt(key); - } - - @SuppressWarnings("unused") - public Integer getAnother(String key) { - throw new UnsupportedOperationException("Not allowed"); - } - }); - assertEquals(23, adapter.classify("23").intValue()); - } - - @Test(expected = IllegalStateException.class) - public void testClassifierAdapterObjectWithNoAnnotation() { - adapter = new ClassifierAdapter(new Object() { - @SuppressWarnings("unused") - public Integer getValue(String key) { - return Integer.parseInt(key); - } - - @SuppressWarnings("unused") - public Integer getAnother(String key) { - throw new UnsupportedOperationException("Not allowed"); - } - }); - assertEquals(23, adapter.classify("23").intValue()); - } - - @Test - public void testClassifierAdapterObjectSingleMethodWithNoAnnotation() { - adapter = new ClassifierAdapter(new Object() { - @SuppressWarnings("unused") - public Integer getValue(String key) { - return Integer.parseInt(key); - } - @SuppressWarnings("unused") - public void doNothing(String key) { - } - @SuppressWarnings("unused") - public String doNothing(String key, int value) { - return "foo"; - } - }); - assertEquals(23, adapter.classify("23").intValue()); - } - - @Test - public void testClassifierAdapterClassifier() { - adapter = new ClassifierAdapter( - new org.springframework.classify.Classifier() { - public Integer classify(String classifiable) { - return Integer.valueOf(classifiable); - } - }); - assertEquals(23, adapter.classify("23").intValue()); - } - - @Test - public void testClassifyWithSetter() { - adapter.setDelegate(new Object() { - @Classifier - public Integer getValue(String key) { - return Integer.parseInt(key); - } - }); - assertEquals(23, adapter.classify("23").intValue()); - } - - @Test(expected=IllegalArgumentException.class) - public void testClassifyWithWrongType() { - adapter.setDelegate(new Object() { - @Classifier - public String getValue(Integer key) { - return key.toString(); - } - }); - assertEquals(23, adapter.classify("23").intValue()); - } - - @Test - public void testClassifyWithClassifier() { - adapter.setDelegate(new org.springframework.classify.Classifier() { - public Integer classify(String classifiable) { - return Integer.valueOf(classifiable); - } - }); - assertEquals(23, adapter.classify("23").intValue()); - } - -} +/* + * Copyright 2006-2007 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 + * + * http://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.classify; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.springframework.classify.annotation.Classifier; + +/** + * @author Dave Syer + * + */ +public class ClassifierAdapterTests { + + private ClassifierAdapter adapter = new ClassifierAdapter(); + + @Test + public void testClassifierAdapterObject() { + adapter = new ClassifierAdapter(new Object() { + @Classifier + public Integer getValue(String key) { + return Integer.parseInt(key); + } + + @SuppressWarnings("unused") + public Integer getAnother(String key) { + throw new UnsupportedOperationException("Not allowed"); + } + }); + assertEquals(23, adapter.classify("23").intValue()); + } + + @Test(expected = IllegalStateException.class) + public void testClassifierAdapterObjectWithNoAnnotation() { + adapter = new ClassifierAdapter(new Object() { + @SuppressWarnings("unused") + public Integer getValue(String key) { + return Integer.parseInt(key); + } + + @SuppressWarnings("unused") + public Integer getAnother(String key) { + throw new UnsupportedOperationException("Not allowed"); + } + }); + assertEquals(23, adapter.classify("23").intValue()); + } + + @Test + public void testClassifierAdapterObjectSingleMethodWithNoAnnotation() { + adapter = new ClassifierAdapter(new Object() { + @SuppressWarnings("unused") + public Integer getValue(String key) { + return Integer.parseInt(key); + } + @SuppressWarnings("unused") + public void doNothing(String key) { + } + @SuppressWarnings("unused") + public String doNothing(String key, int value) { + return "foo"; + } + }); + assertEquals(23, adapter.classify("23").intValue()); + } + + @Test + public void testClassifierAdapterClassifier() { + adapter = new ClassifierAdapter( + new org.springframework.classify.Classifier() { + public Integer classify(String classifiable) { + return Integer.valueOf(classifiable); + } + }); + assertEquals(23, adapter.classify("23").intValue()); + } + + @Test + public void testClassifyWithSetter() { + adapter.setDelegate(new Object() { + @Classifier + public Integer getValue(String key) { + return Integer.parseInt(key); + } + }); + assertEquals(23, adapter.classify("23").intValue()); + } + + @Test(expected=IllegalArgumentException.class) + public void testClassifyWithWrongType() { + adapter.setDelegate(new Object() { + @Classifier + public String getValue(Integer key) { + return key.toString(); + } + }); + assertEquals(23, adapter.classify("23").intValue()); + } + + @Test + public void testClassifyWithClassifier() { + adapter.setDelegate(new org.springframework.classify.Classifier() { + public Integer classify(String classifiable) { + return Integer.valueOf(classifiable); + } + }); + assertEquals(23, adapter.classify("23").intValue()); + } + +} diff --git a/src/test/java/org/springframework/classify/PatternMatchingClassifierTests.java b/src/test/java/org/springframework/classify/PatternMatchingClassifierTests.java index bfb0f8d..c254660 100644 --- a/src/test/java/org/springframework/classify/PatternMatchingClassifierTests.java +++ b/src/test/java/org/springframework/classify/PatternMatchingClassifierTests.java @@ -1,58 +1,58 @@ -/* - * Copyright 2006-2007 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 - * - * http://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.classify; - -import static org.junit.Assert.*; - -import java.util.HashMap; -import java.util.Map; - -import org.junit.Before; -import org.junit.Test; -import org.springframework.classify.PatternMatchingClassifier; - -/** - * @author Dave Syer - * - */ -public class PatternMatchingClassifierTests { - - private PatternMatchingClassifier classifier = new PatternMatchingClassifier(); - - private Map map; - - @Before - public void createMap() { - map = new HashMap(); - map.put("foo", "bar"); - map.put("*", "spam"); - } - - @Test - public void testSetPatternMap() { - classifier.setPatternMap(map); - assertEquals("bar", classifier.classify("foo")); - assertEquals("spam", classifier.classify("bucket")); - } - - @Test - public void testCreateFromMap() { - classifier = new PatternMatchingClassifier(map); - assertEquals("bar", classifier.classify("foo")); - assertEquals("spam", classifier.classify("bucket")); - } - +/* + * Copyright 2006-2007 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 + * + * http://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.classify; + +import static org.junit.Assert.*; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.classify.PatternMatchingClassifier; + +/** + * @author Dave Syer + * + */ +public class PatternMatchingClassifierTests { + + private PatternMatchingClassifier classifier = new PatternMatchingClassifier(); + + private Map map; + + @Before + public void createMap() { + map = new HashMap(); + map.put("foo", "bar"); + map.put("*", "spam"); + } + + @Test + public void testSetPatternMap() { + classifier.setPatternMap(map); + assertEquals("bar", classifier.classify("foo")); + assertEquals("spam", classifier.classify("bucket")); + } + + @Test + public void testCreateFromMap() { + classifier = new PatternMatchingClassifier(map); + assertEquals("bar", classifier.classify("foo")); + assertEquals("spam", classifier.classify("bucket")); + } + } \ No newline at end of file diff --git a/src/test/java/org/springframework/retry/interceptor/StatefulRetryOperationsInterceptorTests.java b/src/test/java/org/springframework/retry/interceptor/StatefulRetryOperationsInterceptorTests.java index 3a40839..3f55764 100644 --- a/src/test/java/org/springframework/retry/interceptor/StatefulRetryOperationsInterceptorTests.java +++ b/src/test/java/org/springframework/retry/interceptor/StatefulRetryOperationsInterceptorTests.java @@ -1,303 +1,303 @@ -/* - * Copyright 2006-2007 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 - * - * http://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.retry.interceptor; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import org.aopalliance.intercept.MethodInterceptor; -import org.aopalliance.intercept.MethodInvocation; -import org.junit.Before; -import org.junit.Test; -import org.springframework.aop.framework.Advised; -import org.springframework.aop.framework.ProxyFactory; -import org.springframework.aop.target.SingletonTargetSource; -import org.springframework.retry.ExhaustedRetryException; -import org.springframework.retry.RetryCallback; -import org.springframework.retry.RetryContext; -import org.springframework.retry.listener.RetryListenerSupport; -import org.springframework.retry.policy.AlwaysRetryPolicy; -import org.springframework.retry.policy.NeverRetryPolicy; -import org.springframework.retry.policy.SimpleRetryPolicy; -import org.springframework.retry.support.RetryTemplate; - -/** - * @author Dave Syer - * - */ -public class StatefulRetryOperationsInterceptorTests { - - private StatefulRetryOperationsInterceptor interceptor; - - private RetryTemplate retryTemplate = new RetryTemplate(); - - private Service service; - - private Transformer transformer; - - private RetryContext context; - - private static int count; - - @Before - public void setUp() throws Exception { - interceptor = new StatefulRetryOperationsInterceptor(); - retryTemplate.registerListener(new RetryListenerSupport() { - @Override - public void close(RetryContext context, - RetryCallback callback, Throwable throwable) { - StatefulRetryOperationsInterceptorTests.this.context = context; - } - }); - interceptor.setRetryOperations(retryTemplate); - service = (Service) ProxyFactory.getProxy(Service.class, - new SingletonTargetSource(new ServiceImpl())); - transformer = (Transformer) ProxyFactory.getProxy(Transformer.class, - new SingletonTargetSource(new TransformerImpl())); - count = 0; - } - - @Test - public void testDefaultInterceptorSunnyDay() throws Exception { - ((Advised) service).addAdvice(interceptor); - try { - service.service("foo"); - fail("Expected Exception."); - } - catch (Exception e) { - String message = e.getMessage(); - assertTrue("Wrong message: " + message, - message.startsWith("Not enough calls")); - } - assertEquals(1, count); - } - - @Test - public void testDefaultInterceptorWithLabel() throws Exception { - interceptor.setLabel("FOO"); - ((Advised) service).addAdvice(interceptor); - try { - service.service("foo"); - fail("Expected Exception."); - } - catch (Exception e) { - String message = e.getMessage(); - assertTrue("Wrong message: " + message, - message.startsWith("Not enough calls")); - } - assertEquals(1, count); - assertEquals("FOO", context.getAttribute(RetryContext.NAME)); - } - - @Test - public void testDefaultTransformerInterceptorSunnyDay() throws Exception { - ((Advised) transformer).addAdvice(interceptor); - try { - transformer.transform("foo"); - fail("Expected Exception."); - } - catch (Exception e) { - String message = e.getMessage(); - assertTrue("Wrong message: " + message, - message.startsWith("Not enough calls")); - } - assertEquals(1, count); - } - - @Test - public void testDefaultInterceptorAlwaysRetry() throws Exception { - retryTemplate.setRetryPolicy(new AlwaysRetryPolicy()); - interceptor.setRetryOperations(retryTemplate); - ((Advised) service).addAdvice(interceptor); - try { - service.service("foo"); - fail("Expected Exception."); - } - catch (Exception e) { - String message = e.getMessage(); - assertTrue("Wrong message: " + message, - message.startsWith("Not enough calls")); - } - assertEquals(1, count); - } - - @Test - public void testInterceptorChainWithRetry() throws Exception { - ((Advised) service).addAdvice(interceptor); - final List list = new ArrayList(); - ((Advised) service).addAdvice(new MethodInterceptor() { - public Object invoke(MethodInvocation invocation) throws Throwable { - list.add("chain"); - return invocation.proceed(); - } - }); - interceptor.setRetryOperations(retryTemplate); - retryTemplate.setRetryPolicy(new SimpleRetryPolicy(2, - Collections., Boolean>singletonMap( - Exception.class, true))); - try { - service.service("foo"); - fail("Expected Exception."); - } - catch (Exception e) { - String message = e.getMessage(); - assertTrue("Wrong message: " + message, - message.startsWith("Not enough calls")); - } - assertEquals(1, count); - service.service("foo"); - assertEquals(2, count); - assertEquals(2, list.size()); - } - - @Test - public void testTransformerWithSuccessfulRetry() throws Exception { - ((Advised) transformer).addAdvice(interceptor); - interceptor.setRetryOperations(retryTemplate); - retryTemplate.setRetryPolicy(new SimpleRetryPolicy(2, - Collections., Boolean>singletonMap( - Exception.class, true))); - try { - transformer.transform("foo"); - fail("Expected Exception."); - } - catch (Exception e) { - String message = e.getMessage(); - assertTrue("Wrong message: " + message, - message.startsWith("Not enough calls")); - } - assertEquals(1, count); - Collection result = transformer.transform("foo"); - assertEquals(2, count); - assertEquals(1, result.size()); - } - - @Test - public void testRetryExceptionAfterTooManyAttemptsWithNoRecovery() throws Exception { - ((Advised) service).addAdvice(interceptor); - interceptor.setRetryOperations(retryTemplate); - retryTemplate.setRetryPolicy(new NeverRetryPolicy()); - try { - service.service("foo"); - fail("Expected Exception."); - } - catch (Exception e) { - String message = e.getMessage(); - assertTrue("Wrong message: " + message, - message.startsWith("Not enough calls")); - } - assertEquals(1, count); - try { - service.service("foo"); - fail("Expected ExhaustedRetryException"); - } - catch (ExhaustedRetryException e) { - // expected - String message = e.getMessage(); - assertTrue("Wrong message: " + message, - message.startsWith("Retry exhausted")); - } - assertEquals(1, count); - } - - @Test - public void testRecoveryAfterTooManyAttempts() throws Exception { - ((Advised) service).addAdvice(interceptor); - interceptor.setRetryOperations(retryTemplate); - retryTemplate.setRetryPolicy(new NeverRetryPolicy()); - try { - service.service("foo"); - fail("Expected Exception."); - } - catch (Exception e) { - String message = e.getMessage(); - assertTrue("Wrong message: " + message, - message.startsWith("Not enough calls")); - } - assertEquals(1, count); - interceptor.setRecoverer(new MethodInvocationRecoverer() { - public Object recover(Object[] data, Throwable cause) { - count++; - return null; - } - }); - service.service("foo"); - assertEquals(2, count); - } - - @Test - public void testTransformerRecoveryAfterTooManyAttempts() throws Exception { - ((Advised) transformer).addAdvice(interceptor); - interceptor.setRetryOperations(retryTemplate); - retryTemplate.setRetryPolicy(new NeverRetryPolicy()); - try { - transformer.transform("foo"); - fail("Expected Exception."); - } - catch (Exception e) { - String message = e.getMessage(); - assertTrue("Wrong message: " + message, - message.startsWith("Not enough calls")); - } - assertEquals(1, count); - interceptor.setRecoverer(new MethodInvocationRecoverer>() { - public Collection recover(Object[] data, Throwable cause) { - count++; - return Collections.singleton((String) data[0]); - } - }); - Collection result = transformer.transform("foo"); - assertEquals(2, count); - assertEquals(1, result.size()); - } - - public static interface Service { - void service(String in) throws Exception; - } - - public static class ServiceImpl implements Service { - - public void service(String in) throws Exception { - count++; - if (count < 2) { - throw new Exception("Not enough calls: " + count); - } - } - - } - - public static interface Transformer { - Collection transform(String in) throws Exception; - } - - public static class TransformerImpl implements Transformer { - - public Collection transform(String in) throws Exception { - count++; - if (count < 2) { - throw new Exception("Not enough calls: " + count); - } - return Collections.singleton(in + ":" + count); - } - - } -} +/* + * Copyright 2006-2007 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 + * + * http://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.retry.interceptor; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.junit.Before; +import org.junit.Test; +import org.springframework.aop.framework.Advised; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.target.SingletonTargetSource; +import org.springframework.retry.ExhaustedRetryException; +import org.springframework.retry.RetryCallback; +import org.springframework.retry.RetryContext; +import org.springframework.retry.listener.RetryListenerSupport; +import org.springframework.retry.policy.AlwaysRetryPolicy; +import org.springframework.retry.policy.NeverRetryPolicy; +import org.springframework.retry.policy.SimpleRetryPolicy; +import org.springframework.retry.support.RetryTemplate; + +/** + * @author Dave Syer + * + */ +public class StatefulRetryOperationsInterceptorTests { + + private StatefulRetryOperationsInterceptor interceptor; + + private RetryTemplate retryTemplate = new RetryTemplate(); + + private Service service; + + private Transformer transformer; + + private RetryContext context; + + private static int count; + + @Before + public void setUp() throws Exception { + interceptor = new StatefulRetryOperationsInterceptor(); + retryTemplate.registerListener(new RetryListenerSupport() { + @Override + public void close(RetryContext context, + RetryCallback callback, Throwable throwable) { + StatefulRetryOperationsInterceptorTests.this.context = context; + } + }); + interceptor.setRetryOperations(retryTemplate); + service = (Service) ProxyFactory.getProxy(Service.class, + new SingletonTargetSource(new ServiceImpl())); + transformer = (Transformer) ProxyFactory.getProxy(Transformer.class, + new SingletonTargetSource(new TransformerImpl())); + count = 0; + } + + @Test + public void testDefaultInterceptorSunnyDay() throws Exception { + ((Advised) service).addAdvice(interceptor); + try { + service.service("foo"); + fail("Expected Exception."); + } + catch (Exception e) { + String message = e.getMessage(); + assertTrue("Wrong message: " + message, + message.startsWith("Not enough calls")); + } + assertEquals(1, count); + } + + @Test + public void testDefaultInterceptorWithLabel() throws Exception { + interceptor.setLabel("FOO"); + ((Advised) service).addAdvice(interceptor); + try { + service.service("foo"); + fail("Expected Exception."); + } + catch (Exception e) { + String message = e.getMessage(); + assertTrue("Wrong message: " + message, + message.startsWith("Not enough calls")); + } + assertEquals(1, count); + assertEquals("FOO", context.getAttribute(RetryContext.NAME)); + } + + @Test + public void testDefaultTransformerInterceptorSunnyDay() throws Exception { + ((Advised) transformer).addAdvice(interceptor); + try { + transformer.transform("foo"); + fail("Expected Exception."); + } + catch (Exception e) { + String message = e.getMessage(); + assertTrue("Wrong message: " + message, + message.startsWith("Not enough calls")); + } + assertEquals(1, count); + } + + @Test + public void testDefaultInterceptorAlwaysRetry() throws Exception { + retryTemplate.setRetryPolicy(new AlwaysRetryPolicy()); + interceptor.setRetryOperations(retryTemplate); + ((Advised) service).addAdvice(interceptor); + try { + service.service("foo"); + fail("Expected Exception."); + } + catch (Exception e) { + String message = e.getMessage(); + assertTrue("Wrong message: " + message, + message.startsWith("Not enough calls")); + } + assertEquals(1, count); + } + + @Test + public void testInterceptorChainWithRetry() throws Exception { + ((Advised) service).addAdvice(interceptor); + final List list = new ArrayList(); + ((Advised) service).addAdvice(new MethodInterceptor() { + public Object invoke(MethodInvocation invocation) throws Throwable { + list.add("chain"); + return invocation.proceed(); + } + }); + interceptor.setRetryOperations(retryTemplate); + retryTemplate.setRetryPolicy(new SimpleRetryPolicy(2, + Collections., Boolean>singletonMap( + Exception.class, true))); + try { + service.service("foo"); + fail("Expected Exception."); + } + catch (Exception e) { + String message = e.getMessage(); + assertTrue("Wrong message: " + message, + message.startsWith("Not enough calls")); + } + assertEquals(1, count); + service.service("foo"); + assertEquals(2, count); + assertEquals(2, list.size()); + } + + @Test + public void testTransformerWithSuccessfulRetry() throws Exception { + ((Advised) transformer).addAdvice(interceptor); + interceptor.setRetryOperations(retryTemplate); + retryTemplate.setRetryPolicy(new SimpleRetryPolicy(2, + Collections., Boolean>singletonMap( + Exception.class, true))); + try { + transformer.transform("foo"); + fail("Expected Exception."); + } + catch (Exception e) { + String message = e.getMessage(); + assertTrue("Wrong message: " + message, + message.startsWith("Not enough calls")); + } + assertEquals(1, count); + Collection result = transformer.transform("foo"); + assertEquals(2, count); + assertEquals(1, result.size()); + } + + @Test + public void testRetryExceptionAfterTooManyAttemptsWithNoRecovery() throws Exception { + ((Advised) service).addAdvice(interceptor); + interceptor.setRetryOperations(retryTemplate); + retryTemplate.setRetryPolicy(new NeverRetryPolicy()); + try { + service.service("foo"); + fail("Expected Exception."); + } + catch (Exception e) { + String message = e.getMessage(); + assertTrue("Wrong message: " + message, + message.startsWith("Not enough calls")); + } + assertEquals(1, count); + try { + service.service("foo"); + fail("Expected ExhaustedRetryException"); + } + catch (ExhaustedRetryException e) { + // expected + String message = e.getMessage(); + assertTrue("Wrong message: " + message, + message.startsWith("Retry exhausted")); + } + assertEquals(1, count); + } + + @Test + public void testRecoveryAfterTooManyAttempts() throws Exception { + ((Advised) service).addAdvice(interceptor); + interceptor.setRetryOperations(retryTemplate); + retryTemplate.setRetryPolicy(new NeverRetryPolicy()); + try { + service.service("foo"); + fail("Expected Exception."); + } + catch (Exception e) { + String message = e.getMessage(); + assertTrue("Wrong message: " + message, + message.startsWith("Not enough calls")); + } + assertEquals(1, count); + interceptor.setRecoverer(new MethodInvocationRecoverer() { + public Object recover(Object[] data, Throwable cause) { + count++; + return null; + } + }); + service.service("foo"); + assertEquals(2, count); + } + + @Test + public void testTransformerRecoveryAfterTooManyAttempts() throws Exception { + ((Advised) transformer).addAdvice(interceptor); + interceptor.setRetryOperations(retryTemplate); + retryTemplate.setRetryPolicy(new NeverRetryPolicy()); + try { + transformer.transform("foo"); + fail("Expected Exception."); + } + catch (Exception e) { + String message = e.getMessage(); + assertTrue("Wrong message: " + message, + message.startsWith("Not enough calls")); + } + assertEquals(1, count); + interceptor.setRecoverer(new MethodInvocationRecoverer>() { + public Collection recover(Object[] data, Throwable cause) { + count++; + return Collections.singleton((String) data[0]); + } + }); + Collection result = transformer.transform("foo"); + assertEquals(2, count); + assertEquals(1, result.size()); + } + + public static interface Service { + void service(String in) throws Exception; + } + + public static class ServiceImpl implements Service { + + public void service(String in) throws Exception { + count++; + if (count < 2) { + throw new Exception("Not enough calls: " + count); + } + } + + } + + public static interface Transformer { + Collection transform(String in) throws Exception; + } + + public static class TransformerImpl implements Transformer { + + public Collection transform(String in) throws Exception { + count++; + if (count < 2) { + throw new Exception("Not enough calls: " + count); + } + return Collections.singleton(in + ":" + count); + } + + } +}