Line endings

This commit is contained in:
Dave Syer
2016-08-22 12:03:50 +01:00
parent 730d93038c
commit 5a70f99738
9 changed files with 1086 additions and 1086 deletions

View File

@@ -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<C, T> implements Classifier<C, T> {
private Classifier<C, String> router;
private Classifier<String, T> 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<C, String> router, Classifier<String, T> 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<String, T> map) {
this.matcher = new PatternMatchingClassifier<T>(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 <code>@Classifier</code> 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<C,String>(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<C, T> implements Classifier<C, T> {
private Classifier<C, String> router;
private Classifier<String, T> 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<C, String> router, Classifier<String, T> 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<String, T> map) {
this.matcher = new PatternMatchingClassifier<T>(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 <code>@Classifier</code> 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<C,String>(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));
}
}

View File

@@ -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<C, T> implements Classifier<C, T> {
private MethodInvoker invoker;
private Classifier<C, T> 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<C, T> delegate) {
classifier = delegate;
}
public void setDelegate(Classifier<C, T> 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.<C, T> 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<C, T> implements Classifier<C, T> {
private MethodInvoker invoker;
private Classifier<C, T> 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<C, T> delegate) {
classifier = delegate;
}
public void setDelegate(Classifier<C, T> 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.<C, T> 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);
}
}

View File

@@ -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<T> implements Classifier<String, T> {
private PatternMatcher<T> 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<String, T>());
}
/**
* 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<String, T> values) {
super();
this.values = new PatternMatcher<T>(values);
}
/**
* A map from pattern to value
* @param values the pattern map to set
*/
public void setPatternMap(Map<String, T> values) {
this.values = new PatternMatcher<T>(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<T> implements Classifier<String, T> {
private PatternMatcher<T> 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<String, T>());
}
/**
* 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<String, T> values) {
super();
this.values = new PatternMatcher<T>(values);
}
/**
* A map from pattern to value
* @param values the pattern map to set
*/
public void setPatternMap(Map<String, T> values) {
this.values = new PatternMatcher<T>(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;
}
}

View File

@@ -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 {
}

View File

@@ -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<? super Throwable, Boolean> 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<? super Throwable, Boolean> 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<Object, Throwable> {
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<Object> {
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<? super Throwable, Boolean> 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<? super Throwable, Boolean> 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<Object, Throwable> {
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<Object> {
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());
}
}
}

View File

@@ -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<String, String> classifier = new BackToBackPatternClassifier<String, String>();
private Map<String, String> map;
@Before
public void createMap() {
map = new HashMap<String, String>();
map.put("foo", "bar");
map.put("*", "spam");
}
@Test(expected = NullPointerException.class)
public void testNoClassifiers() {
classifier.classify("foo");
}
@Test
public void testCreateFromConstructor() {
classifier = new BackToBackPatternClassifier<String, String>(
new PatternMatchingClassifier<String>(Collections.singletonMap(
"oof", "bucket")),
new PatternMatchingClassifier<String>(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<String, String> classifier = new BackToBackPatternClassifier<String, String>();
private Map<String, String> map;
@Before
public void createMap() {
map = new HashMap<String, String>();
map.put("foo", "bar");
map.put("*", "spam");
}
@Test(expected = NullPointerException.class)
public void testNoClassifiers() {
classifier.classify("foo");
}
@Test
public void testCreateFromConstructor() {
classifier = new BackToBackPatternClassifier<String, String>(
new PatternMatchingClassifier<String>(Collections.singletonMap(
"oof", "bucket")),
new PatternMatchingClassifier<String>(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"));
}
}

View File

@@ -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<String, Integer> adapter = new ClassifierAdapter<String, Integer>();
@Test
public void testClassifierAdapterObject() {
adapter = new ClassifierAdapter<String, Integer>(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<String, Integer>(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<String, Integer>(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<String, Integer>(
new org.springframework.classify.Classifier<String, Integer>() {
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<String, Integer>() {
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<String, Integer> adapter = new ClassifierAdapter<String, Integer>();
@Test
public void testClassifierAdapterObject() {
adapter = new ClassifierAdapter<String, Integer>(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<String, Integer>(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<String, Integer>(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<String, Integer>(
new org.springframework.classify.Classifier<String, Integer>() {
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<String, Integer>() {
public Integer classify(String classifiable) {
return Integer.valueOf(classifiable);
}
});
assertEquals(23, adapter.classify("23").intValue());
}
}

View File

@@ -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<String> classifier = new PatternMatchingClassifier<String>();
private Map<String, String> map;
@Before
public void createMap() {
map = new HashMap<String, String>();
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<String>(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<String> classifier = new PatternMatchingClassifier<String>();
private Map<String, String> map;
@Before
public void createMap() {
map = new HashMap<String, String>();
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<String>(map);
assertEquals("bar", classifier.classify("foo"));
assertEquals("spam", classifier.classify("bucket"));
}
}

View File

@@ -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 <T, E extends Throwable> void close(RetryContext context,
RetryCallback<T, E> 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<String> list = new ArrayList<String>();
((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.<Class<? extends Throwable>, 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.<Class<? extends Throwable>, 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<String> 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<Object>() {
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<Collection<String>>() {
public Collection<String> recover(Object[] data, Throwable cause) {
count++;
return Collections.singleton((String) data[0]);
}
});
Collection<String> 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<String> transform(String in) throws Exception;
}
public static class TransformerImpl implements Transformer {
public Collection<String> 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 <T, E extends Throwable> void close(RetryContext context,
RetryCallback<T, E> 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<String> list = new ArrayList<String>();
((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.<Class<? extends Throwable>, 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.<Class<? extends Throwable>, 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<String> 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<Object>() {
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<Collection<String>>() {
public Collection<String> recover(Object[] data, Throwable cause) {
count++;
return Collections.singleton((String) data[0]);
}
});
Collection<String> 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<String> transform(String in) throws Exception;
}
public static class TransformerImpl implements Transformer {
public Collection<String> transform(String in) throws Exception {
count++;
if (count < 2) {
throw new Exception("Not enough calls: " + count);
}
return Collections.singleton(in + ":" + count);
}
}
}