Synthesize annotation from a map of attributes

Spring Framework 4.2 RC1 introduced support for synthesizing an
annotation from an existing annotation in order to provide additional
functionality above and beyond that provided by Java. Specifically,
such synthesized annotations provide support for @AliasFor semantics.
As luck would have it, the same principle can be used to synthesize an
annotation from any map of attributes, and in particular, from an
instance of AnnotationAttributes.

The following highlight the major changes in this commit toward
achieving this goal.

- Introduced AnnotationAttributeExtractor abstraction and refactored
  SynthesizedAnnotationInvocationHandler to delegate to an
  AnnotationAttributeExtractor.

- Extracted code from SynthesizedAnnotationInvocationHandler into new
  AbstractAliasAwareAnnotationAttributeExtractor and
  DefaultAnnotationAttributeExtractor implementation classes.

- Introduced MapAnnotationAttributeExtractor for synthesizing an
  annotation that is backed by a map or AnnotationAttributes instance.

- Introduced a variant of synthesizeAnnotation() in AnnotationUtils
  that accepts a map.

- Introduced findAnnotation(*) methods in AnnotatedElementUtils that
  synthesize merged AnnotationAttributes back into an annotation of the
  target type.

The following classes have been refactored to use the new support for
synthesizing AnnotationAttributes back into an annotation.

- ApplicationListenerMethodAdapter
- TestAnnotationUtils
- AbstractTestContextBootstrapper
- ActiveProfilesUtils
- ContextLoaderUtils
- DefaultActiveProfilesResolver
- DirtiesContextTestExecutionListener
- TestPropertySourceAttributes
- TestPropertySourceUtils
- TransactionalTestExecutionListener
- MetaAnnotationUtils
- MvcUriComponentsBuilder
- RequestMappingHandlerMapping

In addition, this commit also includes changes to ensure that arrays
returned by synthesized annotations are properly cloned first.

Issue: SPR-13067
This commit is contained in:
Sam Brannen
2015-05-25 16:58:18 +02:00
parent f41de12cf6
commit e30c9b2ef3
25 changed files with 881 additions and 358 deletions

View File

@@ -32,7 +32,6 @@ import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.core.BridgeMethodResolver; import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationContext;
@@ -248,11 +247,9 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
*/ */
protected String getCondition() { protected String getCondition() {
if (this.condition == null) { if (this.condition == null) {
AnnotationAttributes annotationAttributes = AnnotatedElementUtils EventListener eventListener = AnnotatedElementUtils.findAnnotation(this.method, EventListener.class);
.findAnnotationAttributes(this.method, EventListener.class); if (eventListener != null) {
if (annotationAttributes != null) { this.condition = eventListener.condition();
String value = annotationAttributes.getString("condition");
this.condition = (value != null ? value : "");
} }
} }
return this.condition; return this.condition;

View File

@@ -0,0 +1,135 @@
/*
* Copyright 2002-2015 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.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Map;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* Abstract base class for {@link AnnotationAttributeExtractor} implementations
* that transparently enforce attribute alias semantics for annotation
* attributes that are annotated with {@link AliasFor @AliasFor}.
*
* @author Sam Brannen
* @since 4.2
* @see Annotation
* @see AliasFor
* @see AnnotationUtils#synthesizeAnnotation(Annotation, AnnotatedElement)
*/
abstract class AbstractAliasAwareAnnotationAttributeExtractor implements AnnotationAttributeExtractor {
private final Class<? extends Annotation> annotationType;
private final AnnotatedElement annotatedElement;
private final Object source;
private final Map<String, String> attributeAliasMap;
/**
* Construct a new {@code AbstractAliasAwareAnnotationAttributeExtractor}.
* @param annotationType the annotation type to synthesize; never {@code null}
* @param annotatedElement the element that is annotated with the annotation
* of the supplied type; may be {@code null} if unknown
* @param source the underlying source of annotation attributes; never {@code null}
*/
AbstractAliasAwareAnnotationAttributeExtractor(Class<? extends Annotation> annotationType,
AnnotatedElement annotatedElement, Object source) {
Assert.notNull(annotationType, "annotationType must not be null");
Assert.notNull(source, "source must not be null");
this.annotationType = annotationType;
this.annotatedElement = annotatedElement;
this.source = source;
this.attributeAliasMap = AnnotationUtils.getAttributeAliasMap(annotationType);
}
@Override
public final Class<? extends Annotation> getAnnotationType() {
return this.annotationType;
}
@Override
public final AnnotatedElement getAnnotatedElement() {
return this.annotatedElement;
}
@Override
public Object getSource() {
return this.source;
}
@Override
public final Object getAttributeValue(Method attributeMethod) {
String attributeName = attributeMethod.getName();
Object attributeValue = getRawAttributeValue(attributeMethod);
String aliasName = this.attributeAliasMap.get(attributeName);
if ((aliasName != null)) {
Object aliasValue = getRawAttributeValue(aliasName);
Object defaultValue = AnnotationUtils.getDefaultValue(getAnnotationType(), attributeName);
if (!nullSafeEquals(attributeValue, aliasValue) && !nullSafeEquals(attributeValue, defaultValue)
&& !nullSafeEquals(aliasValue, defaultValue)) {
String elementName = (getAnnotatedElement() == null ? "unknown element"
: getAnnotatedElement().toString());
String msg = String.format("In annotation [%s] declared on [%s] and synthesized from [%s], "
+ "attribute [%s] and its alias [%s] are present with values of [%s] and [%s], "
+ "but only one is permitted.", getAnnotationType().getName(), elementName, getSource(),
attributeName, aliasName, nullSafeToString(attributeValue), nullSafeToString(aliasValue));
throw new AnnotationConfigurationException(msg);
}
// If the user didn't declare the annotation with an explicit value,
// return the value of the alias.
if (nullSafeEquals(attributeValue, defaultValue)) {
attributeValue = aliasValue;
}
}
return attributeValue;
}
/**
* Get the raw, unmodified attribute value from the underlying
* {@linkplain #getSource source} that corresponds to the supplied
* attribute method.
*/
protected abstract Object getRawAttributeValue(Method attributeMethod);
/**
* Get the raw, unmodified attribute value from the underlying
* {@linkplain #getSource source} that corresponds to the supplied
* attribute name.
*/
protected abstract Object getRawAttributeValue(String attributeName);
private static boolean nullSafeEquals(Object o1, Object o2) {
return ObjectUtils.nullSafeEquals(o1, o2);
}
private static String nullSafeToString(Object obj) {
return ObjectUtils.nullSafeToString(obj);
}
}

View File

@@ -48,8 +48,8 @@ import org.springframework.util.StringUtils;
* <h3>Annotation Attribute Overrides</h3> * <h3>Annotation Attribute Overrides</h3>
* <p>Support for meta-annotations with <em>attribute overrides</em> in * <p>Support for meta-annotations with <em>attribute overrides</em> in
* <em>composed annotations</em> is provided by all variants of the * <em>composed annotations</em> is provided by all variants of the
* {@code getAnnotationAttributes()} and {@code findAnnotationAttributes()} * {@code getAnnotationAttributes()}, {@code findAnnotation()}, and
* methods. * {@code findAnnotationAttributes()} methods.
* *
* <h3>Find vs. Get Semantics</h3> * <h3>Find vs. Get Semantics</h3>
* <p>The search algorithms used by methods in this class follow either * <p>The search algorithms used by methods in this class follow either
@@ -224,6 +224,9 @@ public class AnnotatedElementUtils {
* merge that annotation's attributes with <em>matching</em> attributes from * merge that annotation's attributes with <em>matching</em> attributes from
* annotations in lower levels of the annotation hierarchy. * annotations in lower levels of the annotation hierarchy.
* *
* <p>{@link AliasFor @AliasFor} semantics are fully supported, both
* within a single annotation and within the annotation hierarchy.
*
* <p>This method delegates to {@link #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean)}, * <p>This method delegates to {@link #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean)},
* supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap}. * supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap}.
* *
@@ -233,8 +236,8 @@ public class AnnotatedElementUtils {
* @return the merged {@code AnnotationAttributes}, or {@code null} if * @return the merged {@code AnnotationAttributes}, or {@code null} if
* not found * not found
* @see #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean) * @see #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
* @see #findAnnotationAttributes(AnnotatedElement, Class) * @see #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
* @see #findAnnotationAttributes(AnnotatedElement, String) * @see #findAnnotation(AnnotatedElement, Class)
* @see #getAllAnnotationAttributes(AnnotatedElement, String) * @see #getAllAnnotationAttributes(AnnotatedElement, String)
*/ */
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType) { public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType) {
@@ -248,7 +251,9 @@ public class AnnotatedElementUtils {
* annotations in lower levels of the annotation hierarchy. * annotations in lower levels of the annotation hierarchy.
* *
* <p>Attributes from lower levels in the annotation hierarchy override * <p>Attributes from lower levels in the annotation hierarchy override
* attributes of the same name from higher levels. * attributes of the same name from higher levels, and
* {@link AliasFor @AliasFor} semantics are fully supported, both
* within a single annotation and within the annotation hierarchy.
* *
* <p>In contrast to {@link #getAllAnnotationAttributes}, the search * <p>In contrast to {@link #getAllAnnotationAttributes}, the search
* algorithm used by this method will stop searching the annotation * algorithm used by this method will stop searching the annotation
@@ -269,8 +274,7 @@ public class AnnotatedElementUtils {
* as Annotation instances * as Annotation instances
* @return the merged {@code AnnotationAttributes}, or {@code null} if * @return the merged {@code AnnotationAttributes}, or {@code null} if
* not found * not found
* @see #findAnnotationAttributes(AnnotatedElement, Class) * @see #findAnnotation(AnnotatedElement, Class)
* @see #findAnnotationAttributes(AnnotatedElement, String)
* @see #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean) * @see #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
* @see #getAllAnnotationAttributes(AnnotatedElement, String, boolean, boolean) * @see #getAllAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
*/ */
@@ -286,47 +290,59 @@ public class AnnotatedElementUtils {
/** /**
* Find the first annotation of the specified {@code annotationType} within * Find the first annotation of the specified {@code annotationType} within
* the annotation hierarchy <em>above</em> the supplied {@code element} and * the annotation hierarchy <em>above</em> the supplied {@code element},
* merge that annotation's attributes with <em>matching</em> attributes from * merge that annotation's attributes with <em>matching</em> attributes from
* annotations in lower levels of the annotation hierarchy. * annotations in lower levels of the annotation hierarchy, and synthesize
* the result back into an annotation of the specified {@code annotationType}.
*
* <p>{@link AliasFor @AliasFor} semantics are fully supported, both
* within a single annotation and within the annotation hierarchy.
* *
* <p>This method delegates to {@link #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean)} * <p>This method delegates to {@link #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean)}
* supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap}. * (supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap})
* and {@link AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)}.
* *
* @param element the annotated element; never {@code null} * @param element the annotated element; never {@code null}
* @param annotationType the annotation type to find; never {@code null} * @param annotationType the annotation type to find; never {@code null}
* @return the merged {@code AnnotationAttributes}, or {@code null} if * @return the merged, synthesized {@code Annotation}, or {@code null} if not found
* not found
* @since 4.2 * @since 4.2
* @see #findAnnotationAttributes(AnnotatedElement, String) * @see #findAnnotation(AnnotatedElement, String)
* @see #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean) * @see #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
* @see AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)
*/ */
public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, public static <A extends Annotation> A findAnnotation(AnnotatedElement element, Class<A> annotationType) {
Class<? extends Annotation> annotationType) {
Assert.notNull(annotationType, "annotationType must not be null"); Assert.notNull(annotationType, "annotationType must not be null");
return findAnnotationAttributes(element, annotationType.getName()); return findAnnotation(element, annotationType.getName());
} }
/** /**
* Find the first annotation of the specified {@code annotationType} within * Find the first annotation of the specified {@code annotationType} within
* the annotation hierarchy <em>above</em> the supplied {@code element} and * the annotation hierarchy <em>above</em> the supplied {@code element},
* merge that annotation's attributes with <em>matching</em> attributes from * merge that annotation's attributes with <em>matching</em> attributes from
* annotations in lower levels of the annotation hierarchy. * annotations in lower levels of the annotation hierarchy, and synthesize
* the result back into an annotation of the specified {@code annotationType}.
*
* <p>{@link AliasFor @AliasFor} semantics are fully supported, both
* within a single annotation and within the annotation hierarchy.
* *
* <p>This method delegates to {@link #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean)} * <p>This method delegates to {@link #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean)}
* supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap}. * (supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap})
* and {@link AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)}.
* *
* @param element the annotated element; never {@code null} * @param element the annotated element; never {@code null}
* @param annotationType the fully qualified class name of the annotation * @param annotationType the fully qualified class name of the annotation
* type to find; never {@code null} or empty * type to find; never {@code null} or empty
* @return the merged {@code AnnotationAttributes}, or {@code null} if * @return the merged, synthesized {@code Annotation}, or {@code null} if not found
* not found
* @since 4.2 * @since 4.2
* @see #findAnnotationAttributes(AnnotatedElement, Class) * @see #findAnnotation(AnnotatedElement, Class)
* @see #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean) * @see #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
* @see AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)
*/ */
public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, String annotationType) { @SuppressWarnings("unchecked")
return findAnnotationAttributes(element, annotationType, false, false); public static <A extends Annotation> A findAnnotation(AnnotatedElement element, String annotationType) {
AnnotationAttributes attributes = findAnnotationAttributes(element, annotationType, false, false);
return ((attributes != null) ? AnnotationUtils.synthesizeAnnotation(attributes,
(Class<A>) attributes.annotationType(), element) : null);
} }
/** /**
@@ -336,7 +352,9 @@ public class AnnotatedElementUtils {
* annotations in lower levels of the annotation hierarchy. * annotations in lower levels of the annotation hierarchy.
* *
* <p>Attributes from lower levels in the annotation hierarchy override * <p>Attributes from lower levels in the annotation hierarchy override
* attributes of the same name from higher levels. * attributes of the same name from higher levels, and
* {@link AliasFor @AliasFor} semantics are fully supported, both
* within a single annotation and within the annotation hierarchy.
* *
* <p>In contrast to {@link #getAllAnnotationAttributes}, the search * <p>In contrast to {@link #getAllAnnotationAttributes}, the search
* algorithm used by this method will stop searching the annotation * algorithm used by this method will stop searching the annotation
@@ -358,8 +376,7 @@ public class AnnotatedElementUtils {
* @return the merged {@code AnnotationAttributes}, or {@code null} if * @return the merged {@code AnnotationAttributes}, or {@code null} if
* not found * not found
* @since 4.2 * @since 4.2
* @see #findAnnotationAttributes(AnnotatedElement, Class) * @see #findAnnotation(AnnotatedElement, Class)
* @see #findAnnotationAttributes(AnnotatedElement, String)
* @see #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean) * @see #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
*/ */
public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, String annotationType, public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, String annotationType,

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2002-2015 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.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
/**
* An {@code AnnotationAttributeExtractor} is responsible for
* {@linkplain #getAttributeValue extracting} annotation attribute values
* from an underlying {@linkplain #getSource source} such as an
* {@code Annotation} or a {@code Map}.
*
* @author Sam Brannen
* @since 4.2
* @see SynthesizedAnnotationInvocationHandler
*/
interface AnnotationAttributeExtractor {
/**
* Get the type of annotation that this extractor extracts attribute
* values for.
*/
Class<? extends Annotation> getAnnotationType();
/**
* Get the element that is annotated with an annotation of the annotation
* type supported by this extractor.
* @return the annotated element, or {@code null} if unknown
*/
AnnotatedElement getAnnotatedElement();
/**
* Get the underlying source of annotation attributes.
*/
Object getSource();
/**
* Get the attribute value from the underlying {@linkplain #getSource source}
* that corresponds to the supplied attribute method.
* @param attributeMethod an attribute method from the annotation type
* supported by this extractor
* @return the value of the annotation attribute
*/
Object getAttributeValue(Method attributeMethod);
}

View File

@@ -809,8 +809,10 @@ public abstract class AnnotationUtils {
* merging attributes within an annotation hierarchy. When running in <em>merge mode</em>, * merging attributes within an annotation hierarchy. When running in <em>merge mode</em>,
* the following special rules apply: * the following special rules apply:
* <ol> * <ol>
* <li>The supplied annotation will <strong>not</strong> be * <li>The supplied annotation will <em>not</em> be
* {@linkplain #synthesizeAnnotation synthesized} before retrieving its attributes.</li> * {@linkplain #synthesizeAnnotation synthesized} before retrieving its attributes;
* however, nested annotations and arrays of nested annotations <em>will</em> be
* synthesized.</li>
* <li>Default values will be replaced with {@link #DEFAULT_VALUE_PLACEHOLDER}.</li> * <li>Default values will be replaced with {@link #DEFAULT_VALUE_PLACEHOLDER}.</li>
* <li>The resulting, merged annotation attributes should eventually be * <li>The resulting, merged annotation attributes should eventually be
* {@linkplain #postProcessAnnotationAttributes post-processed} in order to * {@linkplain #postProcessAnnotationAttributes post-processed} in order to
@@ -1051,12 +1053,13 @@ public abstract class AnnotationUtils {
* @param annotation the annotation to synthesize * @param annotation the annotation to synthesize
* @param annotatedElement the element that is annotated with the supplied * @param annotatedElement the element that is annotated with the supplied
* annotation; may be {@code null} if unknown * annotation; may be {@code null} if unknown
* @return the synthesized annotation, if the supplied annotation is * @return the synthesized annotation if the supplied annotation is
* <em>synthesizable</em>; {@code null} if the supplied annotation is * <em>synthesizable</em>; {@code null} if the supplied annotation is
* {@code null}; otherwise, the supplied annotation unmodified * {@code null}; otherwise the supplied annotation unmodified
* @throws AnnotationConfigurationException if invalid configuration of * @throws AnnotationConfigurationException if invalid configuration of
* {@code @AliasFor} is detected * {@code @AliasFor} is detected
* @since 4.2 * @since 4.2
* @see #synthesizeAnnotation(Map, Class, AnnotatedElement)
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <A extends Annotation> A synthesizeAnnotation(A annotation, AnnotatedElement annotatedElement) { public static <A extends Annotation> A synthesizeAnnotation(A annotation, AnnotatedElement annotatedElement) {
@@ -1068,20 +1071,58 @@ public abstract class AnnotationUtils {
} }
Class<? extends Annotation> annotationType = annotation.annotationType(); Class<? extends Annotation> annotationType = annotation.annotationType();
// No need to synthesize?
if (!isSynthesizable(annotationType)) { if (!isSynthesizable(annotationType)) {
return annotation; return annotation;
} }
InvocationHandler handler = new SynthesizedAnnotationInvocationHandler(annotation, annotatedElement, AnnotationAttributeExtractor attributeExtractor = new DefaultAnnotationAttributeExtractor(annotation,
getAttributeAliasMap(annotationType)); annotatedElement);
InvocationHandler handler = new SynthesizedAnnotationInvocationHandler(attributeExtractor);
A synthesizedAnnotation = (A) Proxy.newProxyInstance(ClassUtils.getDefaultClassLoader(), new Class<?>[] { A synthesizedAnnotation = (A) Proxy.newProxyInstance(ClassUtils.getDefaultClassLoader(), new Class<?>[] {
(Class<A>) annotationType, SynthesizedAnnotation.class }, handler); (Class<A>) annotationType, SynthesizedAnnotation.class }, handler);
return synthesizedAnnotation; return synthesizedAnnotation;
} }
/**
* <em>Synthesize</em> the supplied map of annotation attributes by
* wrapping it in a dynamic proxy that implements an annotation of type
* {@code annotationType} and transparently enforces <em>attribute alias</em>
* semantics for annotation attributes that are annotated with
* {@link AliasFor @AliasFor}.
* <p>The supplied map must contain key-value pairs for every attribute
* defined by the supplied {@code annotationType}.
* <p>Note that {@link AnnotationAttributes} is a specialized type of
* {@link Map} that is a suitable candidate for this method's
* {@code attributes} argument.
*
* @param attributes the map of annotation attributes to synthesize
* @param annotationType the type of annotation to synthesize; never {@code null}
* @param annotatedElement the element that is annotated with the annotation
* corresponding to the supplied attributes; may be {@code null} if unknown
* @return the synthesized annotation, or {@code null} if the supplied attributes
* map is {@code null}
* @throws AnnotationConfigurationException if invalid configuration is detected
* @since 4.2
* @see #synthesizeAnnotation(Annotation, AnnotatedElement)
*/
@SuppressWarnings("unchecked")
public static <A extends Annotation> A synthesizeAnnotation(Map<String, Object> attributes,
Class<A> annotationType, AnnotatedElement annotatedElement) {
Assert.notNull(annotationType, "annotationType must not be null");
if (attributes == null) {
return null;
}
AnnotationAttributeExtractor attributeExtractor = new MapAnnotationAttributeExtractor(attributes,
annotationType, annotatedElement);
InvocationHandler handler = new SynthesizedAnnotationInvocationHandler(attributeExtractor);
A synthesizedAnnotation = (A) Proxy.newProxyInstance(ClassUtils.getDefaultClassLoader(), new Class<?>[] {
annotationType, SynthesizedAnnotation.class }, handler);
return synthesizedAnnotation;
}
/** /**
* Get a map of all attribute alias pairs, declared via {@code @AliasFor} * Get a map of all attribute alias pairs, declared via {@code @AliasFor}
@@ -1098,7 +1139,7 @@ public abstract class AnnotationUtils {
* @return a map containing attribute alias pairs; never {@code null} * @return a map containing attribute alias pairs; never {@code null}
* @since 4.2 * @since 4.2
*/ */
private static Map<String, String> getAttributeAliasMap(Class<? extends Annotation> annotationType) { static Map<String, String> getAttributeAliasMap(Class<? extends Annotation> annotationType) {
if (annotationType == null) { if (annotationType == null) {
return Collections.emptyMap(); return Collections.emptyMap();
} }
@@ -1334,7 +1375,7 @@ public abstract class AnnotationUtils {
methods = new ArrayList<Method>(); methods = new ArrayList<Method>();
for (Method method : annotationType.getDeclaredMethods()) { for (Method method : annotationType.getDeclaredMethods()) {
if ((method.getParameterTypes().length == 0) && (method.getReturnType() != void.class)) { if (isAttributeMethod(method)) {
ReflectionUtils.makeAccessible(method); ReflectionUtils.makeAccessible(method);
methods.add(method); methods.add(method);
} }
@@ -1345,6 +1386,24 @@ public abstract class AnnotationUtils {
return methods; return methods;
} }
/**
* Determine if the supplied {@code method} is an annotation attribute method.
* @param method the method to check
* @return {@code true} if the method is an attribute method
*/
static boolean isAttributeMethod(Method method) {
return ((method != null) && (method.getParameterTypes().length == 0) && (method.getReturnType() != void.class));
}
/**
* Determine if the supplied method is an "annotationType" method.
* @return {@code true} if the method is an "annotationType" method
* @see Annotation#annotationType()
*/
static boolean isAnnotationTypeMethod(Method method) {
return ((method != null) && method.getName().equals("annotationType") && (method.getParameterTypes().length == 0));
}
/** /**
* Post-process the supplied {@link AnnotationAttributes}. * Post-process the supplied {@link AnnotationAttributes}.
* *

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2002-2015 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.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import org.springframework.util.ReflectionUtils;
/**
* Default implementation of the {@link AnnotationAttributeExtractor} strategy
* that is backed by an {@link Annotation}.
*
* @author Sam Brannen
* @since 4.2
* @see Annotation
* @see AliasFor
* @see AbstractAliasAwareAnnotationAttributeExtractor
* @see MapAnnotationAttributeExtractor
* @see AnnotationUtils#synthesizeAnnotation(Annotation, AnnotatedElement)
*/
class DefaultAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttributeExtractor {
/**
* Construct a new {@code DefaultAnnotationAttributeExtractor}.
* @param annotation the annotation to synthesize; never {@code null}
* @param annotatedElement the element that is annotated with the supplied
* annotation; may be {@code null} if unknown
*/
DefaultAnnotationAttributeExtractor(Annotation annotation, AnnotatedElement annotatedElement) {
super(annotation.annotationType(), annotatedElement, annotation);
}
@Override
protected Object getRawAttributeValue(Method attributeMethod) {
ReflectionUtils.makeAccessible(attributeMethod);
return ReflectionUtils.invokeMethod(attributeMethod, getAnnotation());
}
@Override
protected Object getRawAttributeValue(String attributeName) {
Method attributeMethod = ReflectionUtils.findMethod(getAnnotation().annotationType(), attributeName);
return getRawAttributeValue(attributeMethod);
}
private Annotation getAnnotation() {
return (Annotation) getSource();
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2002-2015 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.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import org.springframework.util.ClassUtils;
import static org.springframework.core.annotation.AnnotationUtils.*;
/**
* Implementation of the {@link AnnotationAttributeExtractor} strategy that
* is backed by a {@link Map}.
*
* @author Sam Brannen
* @since 4.2
* @see Annotation
* @see AliasFor
* @see AbstractAliasAwareAnnotationAttributeExtractor
* @see DefaultAnnotationAttributeExtractor
* @see AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)
*/
class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttributeExtractor {
/**
* Construct a new {@code MapAnnotationAttributeExtractor}.
* <p>The supplied map must contain key-value pairs for every attribute
* defined in the supplied {@code annotationType}.
* @param attributes the map of annotation attributes; never {@code null}
* @param annotationType the type of annotation to synthesize; never {@code null}
* @param annotatedElement the element that is annotated with the annotation
* of the supplied type; may be {@code null} if unknown
*/
MapAnnotationAttributeExtractor(Map<String, Object> attributes, Class<? extends Annotation> annotationType,
AnnotatedElement annotatedElement) {
super(annotationType, annotatedElement, new HashMap<String, Object>(attributes));
validateAttributes(attributes, annotationType);
}
@Override
protected Object getRawAttributeValue(Method attributeMethod) {
return getMap().get(attributeMethod.getName());
}
@Override
protected Object getRawAttributeValue(String attributeName) {
return getMap().get(attributeName);
}
@SuppressWarnings("unchecked")
private Map<String, Object> getMap() {
return (Map<String, Object>) getSource();
}
/**
* Validate the supplied {@code attributes} map by verifying that it
* contains a non-null entry for each annotation attribute in the specified
* {@code annotationType} and that the type of the entry matches the
* return type for the corresponding annotation attribute.
*/
private static void validateAttributes(Map<String, Object> attributes, Class<? extends Annotation> annotationType) {
for (Method attributeMethod : getAttributeMethods(annotationType)) {
String attributeName = attributeMethod.getName();
Object attributeValue = attributes.get(attributeName);
if (attributeValue == null) {
throw new IllegalArgumentException(String.format(
"Attributes map [%s] returned null for required attribute [%s] defined by annotation type [%s].",
attributes, attributeName, annotationType.getName()));
}
Class<?> returnType = attributeMethod.getReturnType();
if (!ClassUtils.isAssignable(returnType, attributeValue.getClass())) {
throw new IllegalArgumentException(String.format(
"Attributes map [%s] returned a value of type [%s] for attribute [%s], "
+ "but a value of type [%s] is required as defined by annotation type [%s].", attributes,
attributeValue.getClass().getName(), attributeName, returnType.getName(), annotationType.getName()));
}
}
}
}

View File

@@ -18,6 +18,7 @@ package org.springframework.core.annotation;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement; import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays; import java.util.Arrays;
@@ -25,6 +26,7 @@ import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@@ -36,150 +38,141 @@ import static org.springframework.util.ReflectionUtils.*;
* <em>synthesized</em> (i.e., wrapped in a dynamic proxy) with additional * <em>synthesized</em> (i.e., wrapped in a dynamic proxy) with additional
* functionality. * functionality.
* *
* <p>{@code SynthesizedAnnotationInvocationHandler} transparently enforces
* attribute alias semantics for annotation attributes that are annotated
* with {@link AliasFor @AliasFor}. In addition, nested annotations and
* arrays of nested annotations will be synthesized upon first access (i.e.,
* <em>lazily</em>).
*
* @author Sam Brannen * @author Sam Brannen
* @since 4.2 * @since 4.2
* @see Annotation * @see Annotation
* @see AliasFor * @see AnnotationAttributeExtractor
* @see AnnotationUtils#synthesizeAnnotation(Annotation, AnnotatedElement) * @see AnnotationUtils#synthesizeAnnotation(Annotation, AnnotatedElement)
*/ */
class SynthesizedAnnotationInvocationHandler implements InvocationHandler { class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
private final AnnotatedElement annotatedElement; private final AnnotationAttributeExtractor attributeExtractor;
private final Annotation annotation; private final Map<String, Object> valueCache = new ConcurrentHashMap<String, Object>(8);
private final Class<? extends Annotation> annotationType;
private final Map<String, String> aliasMap;
private final Map<String, Object> computedValueCache;
/** /**
* Construct a new {@code SynthesizedAnnotationInvocationHandler}. * Construct a new {@code SynthesizedAnnotationInvocationHandler} for
* * the supplied {@link AnnotationAttributeExtractor}.
* @param annotation the annotation to synthesize * @param attributeExtractor the extractor to delegate to
* @param annotatedElement the element that is annotated with the supplied
* annotation; may be {@code null} if unknown
* @param aliasMap the map of attribute alias pairs, declared via
* {@code @AliasFor} in the supplied annotation
*/ */
SynthesizedAnnotationInvocationHandler(Annotation annotation, AnnotatedElement annotatedElement, SynthesizedAnnotationInvocationHandler(AnnotationAttributeExtractor attributeExtractor) {
Map<String, String> aliasMap) { Assert.notNull(attributeExtractor, "AnnotationAttributeExtractor must not be null");
this.annotatedElement = annotatedElement; this.attributeExtractor = attributeExtractor;
this.annotation = annotation;
this.annotationType = annotation.annotationType();
this.aliasMap = aliasMap;
this.computedValueCache = new ConcurrentHashMap<String, Object>(aliasMap.size());
} }
@Override @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (isEqualsMethod(method)) { if (isEqualsMethod(method)) {
return equals(proxy, args[0]); return annotationEquals(args[0]);
} }
if (isHashCodeMethod(method)) { if (isHashCodeMethod(method)) {
return hashCode(proxy); return annotationHashCode();
} }
if (isToStringMethod(method)) { if (isToStringMethod(method)) {
return toString(proxy); return annotationToString();
} }
if (isAnnotationTypeMethod(method)) {
String methodName = method.getName(); return annotationType();
Class<?> returnType = method.getReturnType();
boolean nestedAnnotation = (Annotation[].class.isAssignableFrom(returnType) || Annotation.class.isAssignableFrom(returnType));
String aliasedAttributeName = aliasMap.get(methodName);
boolean aliasPresent = (aliasedAttributeName != null);
makeAccessible(method);
// No custom processing necessary?
if (!aliasPresent && !nestedAnnotation) {
return invokeMethod(method, this.annotation, args);
} }
if (!isAttributeMethod(method)) {
Object cachedValue = this.computedValueCache.get(methodName); String msg = String.format("Method [%s] is unsupported for synthesized annotation type [%s]", method,
if (cachedValue != null) { annotationType());
return cachedValue; throw new AnnotationConfigurationException(msg);
} }
return getAttributeValue(method);
}
Object value = invokeMethod(method, this.annotation, args); private Class<? extends Annotation> annotationType() {
return this.attributeExtractor.getAnnotationType();
}
if (aliasPresent) { private Object getAttributeValue(Method attributeMethod) {
Method aliasedMethod = null; String attributeName = attributeMethod.getName();
try { Object value = this.valueCache.get(attributeName);
aliasedMethod = this.annotationType.getDeclaredMethod(aliasedAttributeName); if (value == null) {
} value = this.attributeExtractor.getAttributeValue(attributeMethod);
catch (NoSuchMethodException e) { if (value == null) {
String msg = String.format("In annotation [%s], attribute [%s] is declared as an @AliasFor [%s], " throw new IllegalStateException(String.format(
+ "but attribute [%s] does not exist.", this.annotationType.getName(), methodName, "%s returned null for attribute name [%s] from attribute source [%s]",
aliasedAttributeName, aliasedAttributeName); this.attributeExtractor.getClass().getName(), attributeName, this.attributeExtractor.getSource()));
throw new AnnotationConfigurationException(msg);
} }
makeAccessible(aliasedMethod); // Synthesize nested annotations before returning them.
Object aliasedValue = invokeMethod(aliasedMethod, this.annotation); if (value instanceof Annotation) {
Object defaultValue = getDefaultValue(this.annotation, methodName); value = synthesizeAnnotation((Annotation) value, this.attributeExtractor.getAnnotatedElement());
}
if (!nullSafeEquals(value, aliasedValue) && !nullSafeEquals(value, defaultValue) else if (value instanceof Annotation[]) {
&& !nullSafeEquals(aliasedValue, defaultValue)) { Annotation[] orig = (Annotation[]) value;
String elementName = (this.annotatedElement == null ? "unknown element" Annotation[] clone = (Annotation[]) Array.newInstance(orig.getClass().getComponentType(), orig.length);
: this.annotatedElement.toString()); for (int i = 0; i < orig.length; i++) {
String msg = String.format( clone[i] = synthesizeAnnotation(orig[i], this.attributeExtractor.getAnnotatedElement());
"In annotation [%s] declared on [%s], attribute [%s] and its alias [%s] are " }
+ "declared with values of [%s] and [%s], but only one declaration is permitted.", value = clone;
this.annotationType.getName(), elementName, methodName, aliasedAttributeName,
nullSafeToString(value), nullSafeToString(aliasedValue));
throw new AnnotationConfigurationException(msg);
} }
// If the user didn't declare the annotation with an explicit value, return this.valueCache.put(attributeName, value);
// the value of the alias.
if (nullSafeEquals(value, defaultValue)) {
value = aliasedValue;
}
} }
// Synthesize nested annotations before returning them. // Clone arrays so that users cannot alter the contents of values in our cache.
if (value instanceof Annotation) { if (value.getClass().isArray()) {
value = synthesizeAnnotation((Annotation) value, this.annotatedElement); value = cloneArray(value);
} }
else if (value instanceof Annotation[]) {
Annotation[] annotations = (Annotation[]) value;
for (int i = 0; i < annotations.length; i++) {
annotations[i] = synthesizeAnnotation(annotations[i], this.annotatedElement);
}
}
this.computedValueCache.put(methodName, value);
return value; return value;
} }
/**
* Clone the provided array, ensuring that original component type is
* retained.
* @param array the array to clone
*/
private Object cloneArray(Object array) {
if (array instanceof boolean[]) {
return ((boolean[]) array).clone();
}
if (array instanceof byte[]) {
return ((byte[]) array).clone();
}
if (array instanceof char[]) {
return ((char[]) array).clone();
}
if (array instanceof double[]) {
return ((double[]) array).clone();
}
if (array instanceof float[]) {
return ((float[]) array).clone();
}
if (array instanceof int[]) {
return ((int[]) array).clone();
}
if (array instanceof long[]) {
return ((long[]) array).clone();
}
if (array instanceof short[]) {
return ((short[]) array).clone();
}
// else
return ((Object[]) array).clone();
}
/** /**
* See {@link Annotation#equals(Object)} for a definition of the required algorithm. * See {@link Annotation#equals(Object)} for a definition of the required algorithm.
*
* @param proxy the synthesized annotation
* @param other the other object to compare against * @param other the other object to compare against
*/ */
private boolean equals(Object proxy, Object other) { private boolean annotationEquals(Object other) {
if (this == other) { if (this == other) {
return true; return true;
} }
if (!this.annotationType.isInstance(other)) { if (!annotationType().isInstance(other)) {
return false; return false;
} }
for (Method attributeMethod : getAttributeMethods(this.annotationType)) { for (Method attributeMethod : getAttributeMethods(annotationType())) {
Object thisValue = invokeMethod(attributeMethod, proxy); Object thisValue = getAttributeValue(attributeMethod);
Object otherValue = invokeMethod(attributeMethod, other); Object otherValue = invokeMethod(attributeMethod, other);
if (!nullSafeEquals(thisValue, otherValue)) { if (!ObjectUtils.nullSafeEquals(thisValue, otherValue)) {
return false; return false;
} }
} }
@@ -189,14 +182,12 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
/** /**
* See {@link Annotation#hashCode()} for a definition of the required algorithm. * See {@link Annotation#hashCode()} for a definition of the required algorithm.
*
* @param proxy the synthesized annotation
*/ */
private int hashCode(Object proxy) { private int annotationHashCode() {
int result = 0; int result = 0;
for (Method attributeMethod : getAttributeMethods(this.annotationType)) { for (Method attributeMethod : getAttributeMethods(annotationType())) {
Object value = invokeMethod(attributeMethod, proxy); Object value = getAttributeValue(attributeMethod);
int hashCode; int hashCode;
if (value.getClass().isArray()) { if (value.getClass().isArray()) {
hashCode = hashCodeForArray(value); hashCode = hashCodeForArray(value);
@@ -250,39 +241,27 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
/** /**
* See {@link Annotation#toString()} for guidelines on the recommended format. * See {@link Annotation#toString()} for guidelines on the recommended format.
*
* @param proxy the synthesized annotation
*/ */
private String toString(Object proxy) { private String annotationToString() {
StringBuilder sb = new StringBuilder("@").append(annotationType.getName()).append("("); StringBuilder sb = new StringBuilder("@").append(annotationType().getName()).append("(");
Iterator<Method> iterator = getAttributeMethods(this.annotationType).iterator(); Iterator<Method> iterator = getAttributeMethods(annotationType()).iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {
Method attributeMethod = iterator.next(); Method attributeMethod = iterator.next();
sb.append(attributeMethod.getName()); sb.append(attributeMethod.getName());
sb.append('='); sb.append('=');
sb.append(valueToString(invokeMethod(attributeMethod, proxy))); sb.append(attributeValueToString(getAttributeValue(attributeMethod)));
sb.append(iterator.hasNext() ? ", " : ""); sb.append(iterator.hasNext() ? ", " : "");
} }
return sb.append(")").toString(); return sb.append(")").toString();
} }
private String valueToString(Object value) { private String attributeValueToString(Object value) {
if (value instanceof Object[]) { if (value instanceof Object[]) {
return "[" + StringUtils.arrayToDelimitedString((Object[]) value, ", ") + "]"; return "[" + StringUtils.arrayToDelimitedString((Object[]) value, ", ") + "]";
} }
// else
return String.valueOf(value); return String.valueOf(value);
} }
private static boolean nullSafeEquals(Object o1, Object o2) {
return ObjectUtils.nullSafeEquals(o1, o2);
}
private static String nullSafeToString(Object obj) {
return ObjectUtils.nullSafeToString(obj);
}
} }

View File

@@ -16,11 +16,13 @@
package org.springframework.core.annotation; package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited; import java.lang.annotation.Inherited;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@@ -30,6 +32,7 @@ import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import static java.util.Arrays.*; import static java.util.Arrays.*;
@@ -450,14 +453,33 @@ public class AnnotatedElementUtilsTests {
} }
@Test @Test
public void findAnnotationAttributesOnClassWithAttributeAliasesInTargetAnnotation() { public void findAndSynthesizeAnnotationAttributesOnClassWithAttributeAliasesInTargetAnnotation() {
String qualifier = "aliasForQualifier";
// 1) Find and merge AnnotationAttributes from the annotation hierarchy
AnnotationAttributes attributes = findAnnotationAttributes(AliasedTransactionalComponentClass.class, AnnotationAttributes attributes = findAnnotationAttributes(AliasedTransactionalComponentClass.class,
AliasedTransactional.class); AliasedTransactional.class);
assertNotNull("Should find @AliasedTransactional on AliasedTransactionalComponentClass", attributes); assertNotNull("@AliasedTransactional on AliasedTransactionalComponentClass.", attributes);
assertEquals("TX value for AliasedTransactionalComponentClass.", "aliasForQualifier",
attributes.getString("value")); // 2) Synthesize the AnnotationAttributes back into the target annotation
assertEquals("TX qualifier for AliasedTransactionalComponentClass.", "aliasForQualifier", AliasedTransactional annotation = AnnotationUtils.synthesizeAnnotation(attributes,
attributes.getString("qualifier")); AliasedTransactional.class, AliasedTransactionalComponentClass.class);
assertNotNull(annotation);
// 3) Verify that the AnnotationAttributes and synthesized annotation are equivalent
assertEquals("TX value via attributes.", qualifier, attributes.getString("value"));
assertEquals("TX value via synthesized annotation.", qualifier, annotation.value());
assertEquals("TX qualifier via attributes.", qualifier, attributes.getString("qualifier"));
assertEquals("TX qualifier via synthesized annotation.", qualifier, annotation.qualifier());
}
@Test
public void findAnnotationWithAttributeAliasesInTargetAnnotation() {
Class<?> element = AliasedTransactionalComponentClass.class;
AliasedTransactional annotation = findAnnotation(element, AliasedTransactional.class);
assertNotNull("@AliasedTransactional on " + element, annotation);
assertEquals("TX value via synthesized annotation.", "aliasForQualifier", annotation.value());
assertEquals("TX qualifier via synthesized annotation.", "aliasForQualifier", annotation.qualifier());
} }
@Test @Test
@@ -480,6 +502,12 @@ public class AnnotatedElementUtilsTests {
} }
static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, Class<? extends Annotation> annotationType) {
Assert.notNull(annotationType, "annotationType must not be null");
return AnnotatedElementUtils.findAnnotationAttributes(element, annotationType.getName(), false, false);
}
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@MetaCycle3 @MetaCycle3

View File

@@ -24,7 +24,9 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import org.junit.Rule; import org.junit.Rule;
@@ -436,7 +438,7 @@ public class AnnotationUtilsTests {
exception.expect(AnnotationConfigurationException.class); exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(containsString("attribute [value] and its alias [path]")); exception.expectMessage(containsString("attribute [value] and its alias [path]"));
exception.expectMessage(containsString("values of [/enigma] and [/test]")); exception.expectMessage(containsString("values of [/enigma] and [/test]"));
exception.expectMessage(containsString("but only one declaration is permitted")); exception.expectMessage(containsString("but only one is permitted"));
getAnnotationAttributes(webMapping); getAnnotationAttributes(webMapping);
} }
@@ -504,7 +506,8 @@ public class AnnotationUtilsTests {
@Test @Test
public void getRepeatableWithAttributeAliases() throws Exception { public void getRepeatableWithAttributeAliases() throws Exception {
Set<ContextConfig> annotations = getRepeatableAnnotation(TestCase.class, Hierarchy.class, ContextConfig.class); Set<ContextConfig> annotations = getRepeatableAnnotation(ConfigHierarchyTestCase.class, Hierarchy.class,
ContextConfig.class);
assertNotNull(annotations); assertNotNull(annotations);
List<String> locations = annotations.stream().map(ContextConfig::locations).collect(toList()); List<String> locations = annotations.stream().map(ContextConfig::locations).collect(toList());
@@ -522,7 +525,7 @@ public class AnnotationUtilsTests {
@Test @Test
public void synthesizeAnnotationWithoutAttributeAliases() throws Exception { public void synthesizeAnnotationWithoutAttributeAliases() throws Exception {
Component component = findAnnotation(WebController.class, Component.class); Component component = WebController.class.getAnnotation(Component.class);
assertNotNull(component); assertNotNull(component);
Component synthesizedComponent = synthesizeAnnotation(component); Component synthesizedComponent = synthesizeAnnotation(component);
assertNotNull(synthesizedComponent); assertNotNull(synthesizedComponent);
@@ -530,6 +533,29 @@ public class AnnotationUtilsTests {
assertEquals("value attribute: ", "webController", synthesizedComponent.value()); assertEquals("value attribute: ", "webController", synthesizedComponent.value());
} }
@Test
public void synthesizeAnnotationsFromNullSources() throws Exception {
assertNull("null annotation", synthesizeAnnotation(null, null));
assertNull("null map", synthesizeAnnotation(null, WebMapping.class, null));
}
@Test
public void synthesizeAlreadySynthesizedAnnotation() throws Exception {
Method method = WebController.class.getMethod("handleMappedWithValueAttribute");
WebMapping webMapping = method.getAnnotation(WebMapping.class);
assertNotNull(webMapping);
WebMapping synthesizedWebMapping = synthesizeAnnotation(webMapping);
assertNotSame(webMapping, synthesizedWebMapping);
WebMapping synthesizedAgainWebMapping = synthesizeAnnotation(synthesizedWebMapping);
assertSame(synthesizedWebMapping, synthesizedAgainWebMapping);
assertThat(synthesizedAgainWebMapping, instanceOf(SynthesizedAnnotation.class));
assertNotNull(synthesizedAgainWebMapping);
assertEquals("name attribute: ", "foo", synthesizedAgainWebMapping.name());
assertEquals("aliased path attribute: ", "/test", synthesizedAgainWebMapping.path());
assertEquals("actual value attribute: ", "/test", synthesizedAgainWebMapping.value());
}
@Test @Test
public void synthesizeAnnotationWithAttributeAliasForNonexistentAttribute() throws Exception { public void synthesizeAnnotationWithAttributeAliasForNonexistentAttribute() throws Exception {
AliasForNonexistentAttribute annotation = AliasForNonexistentAttributeClass.class.getAnnotation(AliasForNonexistentAttribute.class); AliasForNonexistentAttribute annotation = AliasForNonexistentAttributeClass.class.getAnnotation(AliasForNonexistentAttribute.class);
@@ -638,6 +664,78 @@ public class AnnotationUtilsTests {
assertEquals("actual value attribute: ", "/test", synthesizedWebMapping2.value()); assertEquals("actual value attribute: ", "/test", synthesizedWebMapping2.value());
} }
@Test
public void synthesizeAnnotationFromMapWithoutAttributeAliases() throws Exception {
Component component = WebController.class.getAnnotation(Component.class);
assertNotNull(component);
Map<String, Object> map = new HashMap<String, Object>();
map.put(VALUE, "webController");
Component synthesizedComponent = synthesizeAnnotation(map, Component.class, WebController.class);
assertNotNull(synthesizedComponent);
assertNotSame(component, synthesizedComponent);
assertEquals("value from component: ", "webController", component.value());
assertEquals("value from synthesized component: ", "webController", synthesizedComponent.value());
}
@Test
public void synthesizeAnnotationFromMapWithMissingAttributeValue() throws Exception {
exception.expect(IllegalArgumentException.class);
exception.expectMessage(startsWith("Attributes map"));
exception.expectMessage(containsString("returned null for required attribute [value]"));
exception.expectMessage(containsString("defined by annotation type [" + Component.class.getName() + "]"));
synthesizeAnnotation(new HashMap<String, Object>(), Component.class, null);
}
@Test
public void synthesizeAnnotationFromMapWithNullAttributeValue() throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
map.put(VALUE, null);
exception.expect(IllegalArgumentException.class);
exception.expectMessage(startsWith("Attributes map"));
exception.expectMessage(containsString("returned null for required attribute [value]"));
exception.expectMessage(containsString("defined by annotation type [" + Component.class.getName() + "]"));
synthesizeAnnotation(map, Component.class, null);
}
@Test
public void synthesizeAnnotationFromMapWithAttributeOfIncorrectType() throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
map.put(VALUE, 42L);
exception.expect(IllegalArgumentException.class);
exception.expectMessage(startsWith("Attributes map"));
exception.expectMessage(containsString("returned a value of type [java.lang.Long]"));
exception.expectMessage(containsString("for attribute [value]"));
exception.expectMessage(containsString("but a value of type [java.lang.String] is required"));
exception.expectMessage(containsString("as defined by annotation type [" + Component.class.getName() + "]"));
synthesizeAnnotation(map, Component.class, null);
}
@Test
public void synthesizeAnnotationFromAnnotationAttributesWithoutAttributeAliases() throws Exception {
// 1) Get an annotation
Component component = WebController.class.getAnnotation(Component.class);
assertNotNull(component);
// 2) Convert the annotation into AnnotationAttributes
AnnotationAttributes attributes = getAnnotationAttributes(WebController.class, component);
assertNotNull(attributes);
// 3) Synthesize the AnnotationAttributes back into an annotation
Component synthesizedComponent = synthesizeAnnotation(attributes, Component.class, WebController.class);
assertNotNull(synthesizedComponent);
// 4) Verify that the original and synthesized annotations are equivalent
assertNotSame(component, synthesizedComponent);
assertEquals(component, synthesizedComponent);
assertEquals("value from component: ", "webController", component.value());
assertEquals("value from synthesized component: ", "webController", synthesizedComponent.value());
}
@Test @Test
public void toStringForSynthesizedAnnotations() throws Exception { public void toStringForSynthesizedAnnotations() throws Exception {
Method methodWithPath = WebController.class.getMethod("handleMappedWithPathAttribute"); Method methodWithPath = WebController.class.getMethod("handleMappedWithPathAttribute");
@@ -670,7 +768,7 @@ public class AnnotationUtilsTests {
assertThat(string, containsString("path=/test")); assertThat(string, containsString("path=/test"));
assertThat(string, containsString("name=bar")); assertThat(string, containsString("name=bar"));
assertThat(string, containsString("method=")); assertThat(string, containsString("method="));
assertThat(string, either(containsString("[GET, POST]")).or(containsString("[POST, GET]"))); assertThat(string, containsString("[GET, POST]"));
assertThat(string, endsWith(")")); assertThat(string, endsWith(")"));
} }
@@ -778,7 +876,7 @@ public class AnnotationUtilsTests {
@Test @Test
public void synthesizeAnnotationWithAttributeAliasesInNestedAnnotations() throws Exception { public void synthesizeAnnotationWithAttributeAliasesInNestedAnnotations() throws Exception {
Hierarchy hierarchy = TestCase.class.getAnnotation(Hierarchy.class); Hierarchy hierarchy = ConfigHierarchyTestCase.class.getAnnotation(Hierarchy.class);
assertNotNull(hierarchy); assertNotNull(hierarchy);
Hierarchy synthesizedHierarchy = synthesizeAnnotation(hierarchy); Hierarchy synthesizedHierarchy = synthesizeAnnotation(hierarchy);
assertNotSame(hierarchy, synthesizedHierarchy); assertNotSame(hierarchy, synthesizedHierarchy);
@@ -797,20 +895,44 @@ public class AnnotationUtilsTests {
} }
@Test @Test
public void synthesizeAlreadySynthesizedAnnotation() throws Exception { public void synthesizeAnnotationWithArrayOfAnnotations() throws Exception {
Method method = WebController.class.getMethod("handleMappedWithValueAttribute"); Hierarchy hierarchy = ConfigHierarchyTestCase.class.getAnnotation(Hierarchy.class);
WebMapping webMapping = method.getAnnotation(WebMapping.class); assertNotNull(hierarchy);
assertNotNull(webMapping); Hierarchy synthesizedHierarchy = synthesizeAnnotation(hierarchy);
WebMapping synthesizedWebMapping = synthesizeAnnotation(webMapping); assertThat(synthesizedHierarchy, instanceOf(SynthesizedAnnotation.class));
assertNotSame(webMapping, synthesizedWebMapping);
WebMapping synthesizedAgainWebMapping = synthesizeAnnotation(synthesizedWebMapping);
assertSame(synthesizedWebMapping, synthesizedAgainWebMapping);
assertThat(synthesizedAgainWebMapping, instanceOf(SynthesizedAnnotation.class));
assertNotNull(synthesizedAgainWebMapping); ContextConfig contextConfig = SimpleConfigTestCase.class.getAnnotation(ContextConfig.class);
assertEquals("name attribute: ", "foo", synthesizedAgainWebMapping.name()); assertNotNull(contextConfig);
assertEquals("aliased path attribute: ", "/test", synthesizedAgainWebMapping.path());
assertEquals("actual value attribute: ", "/test", synthesizedAgainWebMapping.value()); ContextConfig[] configs = synthesizedHierarchy.value();
List<String> locations = Arrays.stream(configs).map(ContextConfig::locations).collect(toList());
assertThat(locations, equalTo(Arrays.asList("A", "B")));
// Alter array returned from synthesized annotation
configs[0] = contextConfig;
// Re-retrieve the array from the synthesized annotation
configs = synthesizedHierarchy.value();
List<String> values = Arrays.stream(configs).map(ContextConfig::value).collect(toList());
assertThat(values, equalTo(Arrays.asList("A", "B")));
}
@Test
public void synthesizeAnnotationWithArrayOfChars() throws Exception {
CharsContainer charsContainer = GroupOfCharsClass.class.getAnnotation(CharsContainer.class);
assertNotNull(charsContainer);
CharsContainer synthesizedCharsContainer = synthesizeAnnotation(charsContainer);
assertThat(synthesizedCharsContainer, instanceOf(SynthesizedAnnotation.class));
char[] chars = synthesizedCharsContainer.chars();
assertArrayEquals(new char[] { 'x', 'y', 'z' }, chars);
// Alter array returned from synthesized annotation
chars[0] = '?';
// Re-retrieve the array from the synthesized annotation
chars = synthesizedCharsContainer.chars();
assertArrayEquals(new char[] { 'x', 'y', 'z' }, chars);
} }
@@ -1149,9 +1271,28 @@ public class AnnotationUtilsTests {
} }
@Hierarchy({ @ContextConfig("A"), @ContextConfig(locations = "B") }) @Hierarchy({ @ContextConfig("A"), @ContextConfig(locations = "B") })
static class TestCase { static class ConfigHierarchyTestCase {
} }
@ContextConfig("simple.xml")
static class SimpleConfigTestCase {
}
@Retention(RetentionPolicy.RUNTIME)
@interface CharsContainer {
@AliasFor(attribute = "chars")
char[] value() default {};
@AliasFor(attribute = "value")
char[] chars() default {};
}
@CharsContainer(chars = { 'x', 'y', 'z' })
static class GroupOfCharsClass {
}
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@interface AliasForNonexistentAttribute { @interface AliasForNonexistentAttribute {

View File

@@ -19,7 +19,6 @@ package org.springframework.test.annotation;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
/** /**
@@ -38,13 +37,12 @@ public class TestAnnotationUtils {
* annotated with {@code @Timed} * annotated with {@code @Timed}
*/ */
public static long getTimeout(Method method) { public static long getTimeout(Method method) {
AnnotationAttributes attributes = AnnotatedElementUtils.findAnnotationAttributes(method, Timed.class.getName()); Timed timed = AnnotatedElementUtils.findAnnotation(method, Timed.class);
if (attributes == null) { if (timed == null) {
return 0; return 0;
} }
else { else {
long millis = attributes.<Long> getNumber("millis").longValue(); return Math.max(0, timed.millis());
return Math.max(0, millis);
} }
} }

View File

@@ -87,7 +87,7 @@ class MergedSqlConfig {
// Get global attributes, if any. // Get global attributes, if any.
AnnotationAttributes attributes = AnnotatedElementUtils.findAnnotationAttributes(testClass, AnnotationAttributes attributes = AnnotatedElementUtils.findAnnotationAttributes(testClass,
SqlConfig.class.getName()); SqlConfig.class.getName(), false, false);
// Override global attributes with local attributes. // Override global attributes with local attributes.
if (attributes != null) { if (attributes != null) {

View File

@@ -32,7 +32,6 @@ import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.io.support.SpringFactoriesLoader;
@@ -115,7 +114,6 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
@SuppressWarnings("unchecked")
@Override @Override
public final List<TestExecutionListener> getTestExecutionListeners() { public final List<TestExecutionListener> getTestExecutionListeners() {
Class<?> clazz = getBootstrapContext().getTestClass(); Class<?> clazz = getBootstrapContext().getTestClass();
@@ -139,23 +137,20 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
// Traverse the class hierarchy... // Traverse the class hierarchy...
while (descriptor != null) { while (descriptor != null) {
Class<?> declaringClass = descriptor.getDeclaringClass(); Class<?> declaringClass = descriptor.getDeclaringClass();
AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes(); TestExecutionListeners testExecutionListeners = descriptor.getMergedAnnotation();
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace(String.format( logger.trace(String.format("Retrieved @TestExecutionListeners [%s] for declaring class [%s].",
"Retrieved @TestExecutionListeners attributes [%s] for declaring class [%s].", annAttrs, testExecutionListeners, declaringClass.getName()));
declaringClass.getName()));
} }
Class<? extends TestExecutionListener>[] listenerClasses = (Class<? extends TestExecutionListener>[]) annAttrs.getClassArray("listeners"); boolean inheritListeners = testExecutionListeners.inheritListeners();
boolean inheritListeners = annAttrs.getBoolean("inheritListeners");
AnnotationDescriptor<TestExecutionListeners> superDescriptor = MetaAnnotationUtils.findAnnotationDescriptor( AnnotationDescriptor<TestExecutionListeners> superDescriptor = MetaAnnotationUtils.findAnnotationDescriptor(
descriptor.getRootDeclaringClass().getSuperclass(), annotationType); descriptor.getRootDeclaringClass().getSuperclass(), annotationType);
// If there are no listeners to inherit, we might need to merge the // If there are no listeners to inherit, we might need to merge the
// locally declared listeners with the defaults. // locally declared listeners with the defaults.
if ((!inheritListeners || superDescriptor == null) if ((!inheritListeners || superDescriptor == null)
&& (annAttrs.getEnum("mergeMode") == MergeMode.MERGE_WITH_DEFAULTS)) { && (testExecutionListeners.mergeMode() == MergeMode.MERGE_WITH_DEFAULTS)) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format( logger.debug(String.format(
"Merging default listeners with listeners configured via @TestExecutionListeners for class [%s].", "Merging default listeners with listeners configured via @TestExecutionListeners for class [%s].",
@@ -165,7 +160,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
classesList.addAll(getDefaultTestExecutionListenerClasses()); classesList.addAll(getDefaultTestExecutionListenerClasses());
} }
classesList.addAll(0, Arrays.<Class<? extends TestExecutionListener>> asList(listenerClasses)); classesList.addAll(0, Arrays.asList(testExecutionListeners.listeners()));
descriptor = (inheritListeners ? superDescriptor : null); descriptor = (inheritListeners ? superDescriptor : null);
} }

View File

@@ -23,7 +23,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ActiveProfilesResolver; import org.springframework.test.context.ActiveProfilesResolver;
import org.springframework.test.util.MetaAnnotationUtils; import org.springframework.test.util.MetaAnnotationUtils;
@@ -87,14 +86,14 @@ abstract class ActiveProfilesUtils {
while (descriptor != null) { while (descriptor != null) {
Class<?> rootDeclaringClass = descriptor.getRootDeclaringClass(); Class<?> rootDeclaringClass = descriptor.getRootDeclaringClass();
Class<?> declaringClass = descriptor.getDeclaringClass(); Class<?> declaringClass = descriptor.getDeclaringClass();
ActiveProfiles annotation = descriptor.getMergedAnnotation();
AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes();
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace(String.format("Retrieved @ActiveProfiles attributes [%s] for declaring class [%s].", logger.trace(String.format("Retrieved @ActiveProfiles [%s] for declaring class [%s].", annotation,
annAttrs, declaringClass.getName())); declaringClass.getName()));
} }
Class<? extends ActiveProfilesResolver> resolverClass = annAttrs.getClass("resolver"); Class<? extends ActiveProfilesResolver> resolverClass = annotation.resolver();
if (ActiveProfilesResolver.class == resolverClass) { if (ActiveProfilesResolver.class == resolverClass) {
resolverClass = DefaultActiveProfilesResolver.class; resolverClass = DefaultActiveProfilesResolver.class;
} }
@@ -125,8 +124,8 @@ abstract class ActiveProfilesUtils {
} }
} }
descriptor = annAttrs.getBoolean("inheritProfiles") ? MetaAnnotationUtils.findAnnotationDescriptor( descriptor = (annotation.inheritProfiles() ? MetaAnnotationUtils.findAnnotationDescriptor(
rootDeclaringClass.getSuperclass(), annotationType) : null; rootDeclaringClass.getSuperclass(), annotationType) : null);
} }
return StringUtils.toStringArray(activeProfiles); return StringUtils.toStringArray(activeProfiles);

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 the original author or authors. * Copyright 2002-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -26,7 +26,6 @@ import java.util.Set;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextConfigurationAttributes; import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.ContextHierarchy;
@@ -133,8 +132,9 @@ abstract class ContextLoaderUtils {
final List<ContextConfigurationAttributes> configAttributesList = new ArrayList<ContextConfigurationAttributes>(); final List<ContextConfigurationAttributes> configAttributesList = new ArrayList<ContextConfigurationAttributes>();
if (contextConfigDeclaredLocally) { if (contextConfigDeclaredLocally) {
convertAnnotationAttributesToConfigAttributesAndAddToList(descriptor.getAnnotationAttributes(), convertContextConfigToConfigAttributesAndAddToList(
rootDeclaringClass, configAttributesList); (ContextConfiguration) descriptor.getMergedAnnotation(), rootDeclaringClass,
configAttributesList);
} }
else if (contextHierarchyDeclaredLocally) { else if (contextHierarchyDeclaredLocally) {
ContextHierarchy contextHierarchy = getAnnotation(declaringClass, contextHierarchyType); ContextHierarchy contextHierarchy = getAnnotation(declaringClass, contextHierarchyType);
@@ -256,7 +256,7 @@ abstract class ContextLoaderUtils {
annotationType.getName(), testClass.getName())); annotationType.getName(), testClass.getName()));
while (descriptor != null) { while (descriptor != null) {
convertAnnotationAttributesToConfigAttributesAndAddToList(descriptor.getAnnotationAttributes(), convertContextConfigToConfigAttributesAndAddToList(descriptor.getMergedAnnotation(),
descriptor.getRootDeclaringClass(), attributesList); descriptor.getRootDeclaringClass(), attributesList);
descriptor = findAnnotationDescriptor(descriptor.getRootDeclaringClass().getSuperclass(), annotationType); descriptor = findAnnotationDescriptor(descriptor.getRootDeclaringClass().getSuperclass(), annotationType);
} }
@@ -284,24 +284,4 @@ abstract class ContextLoaderUtils {
attributesList.add(attributes); attributesList.add(attributes);
} }
/**
* Convenience method for creating a {@link ContextConfigurationAttributes}
* instance from the supplied {@link AnnotationAttributes} and declaring
* class and then adding the attributes to the supplied list.
* @since 4.0
*/
private static void convertAnnotationAttributesToConfigAttributesAndAddToList(AnnotationAttributes annAttrs,
Class<?> declaringClass, final List<ContextConfigurationAttributes> attributesList) {
if (logger.isTraceEnabled()) {
logger.trace(String.format("Retrieved @ContextConfiguration attributes [%s] for declaring class [%s].",
annAttrs, declaringClass.getName()));
}
ContextConfigurationAttributes attributes = new ContextConfigurationAttributes(declaringClass, annAttrs);
if (logger.isTraceEnabled()) {
logger.trace("Resolved context configuration attributes: " + attributes);
}
attributesList.add(attributes);
}
} }

View File

@@ -22,9 +22,9 @@ import java.util.Set;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ActiveProfilesResolver; import org.springframework.test.context.ActiveProfilesResolver;
import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@@ -73,14 +73,14 @@ public class DefaultActiveProfilesResolver implements ActiveProfilesResolver {
} }
else { else {
Class<?> declaringClass = descriptor.getDeclaringClass(); Class<?> declaringClass = descriptor.getDeclaringClass();
ActiveProfiles annotation = descriptor.getMergedAnnotation();
AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes();
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace(String.format("Retrieved @ActiveProfiles attributes [%s] for declaring class [%s].", logger.trace(String.format("Retrieved @ActiveProfiles [%s] for declaring class [%s].", annotation,
annAttrs, declaringClass.getName())); declaringClass.getName()));
} }
for (String profile : annAttrs.getStringArray("profiles")) { for (String profile : annotation.profiles()) {
if (StringUtils.hasText(profile)) { if (StringUtils.hasText(profile)) {
activeProfiles.add(profile.trim()); activeProfiles.add(profile.trim());
} }

View File

@@ -23,7 +23,6 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.annotation.DirtiesContext.HierarchyMode; import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
@@ -150,28 +149,27 @@ public class DirtiesContextTestExecutionListener extends AbstractTestExecutionLi
*/ */
private void beforeOrAfterTestMethod(TestContext testContext, String phase, MethodMode requiredMethodMode, private void beforeOrAfterTestMethod(TestContext testContext, String phase, MethodMode requiredMethodMode,
ClassMode requiredClassMode) throws Exception { ClassMode requiredClassMode) throws Exception {
Class<?> testClass = testContext.getTestClass(); Class<?> testClass = testContext.getTestClass();
Assert.notNull(testClass, "The test class of the supplied TestContext must not be null");
Method testMethod = testContext.getTestMethod(); Method testMethod = testContext.getTestMethod();
Assert.notNull(testClass, "The test class of the supplied TestContext must not be null");
Assert.notNull(testMethod, "The test method of the supplied TestContext must not be null"); Assert.notNull(testMethod, "The test method of the supplied TestContext must not be null");
final String annotationType = DirtiesContext.class.getName(); DirtiesContext methodAnn = AnnotatedElementUtils.findAnnotation(testMethod, DirtiesContext.class);
AnnotationAttributes methodAnnAttrs = AnnotatedElementUtils.findAnnotationAttributes(testMethod, annotationType); DirtiesContext classAnn = AnnotatedElementUtils.findAnnotation(testClass, DirtiesContext.class);
AnnotationAttributes classAnnAttrs = AnnotatedElementUtils.findAnnotationAttributes(testClass, annotationType); boolean methodAnnotated = (methodAnn != null);
boolean methodAnnotated = methodAnnAttrs != null; boolean classAnnotated = (classAnn != null);
boolean classAnnotated = classAnnAttrs != null; MethodMode methodMode = (methodAnnotated ? methodAnn.methodMode() : null);
MethodMode methodMode = methodAnnotated ? methodAnnAttrs.<MethodMode> getEnum("methodMode") : null; ClassMode classMode = (classAnnotated ? classAnn.classMode() : null);
ClassMode classMode = classAnnotated ? classAnnAttrs.<ClassMode> getEnum("classMode") : null;
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format( logger.debug(String.format("%s test method: context %s, class annotated with @DirtiesContext [%s] "
"%s test method: context %s, class annotated with @DirtiesContext [%s] with mode [%s], method annotated with @DirtiesContext [%s] with mode [%s].", + "with mode [%s], method annotated with @DirtiesContext [%s] with mode [%s].", phase, testContext,
phase, testContext, classAnnotated, classMode, methodAnnotated, methodMode)); classAnnotated, classMode, methodAnnotated, methodMode));
} }
if ((methodMode == requiredMethodMode) || (classMode == requiredClassMode)) { if ((methodMode == requiredMethodMode) || (classMode == requiredClassMode)) {
HierarchyMode hierarchyMode = methodAnnotated ? methodAnnAttrs.<HierarchyMode> getEnum("hierarchyMode") HierarchyMode hierarchyMode = (methodAnnotated ? methodAnn.hierarchyMode() : classAnn.hierarchyMode());
: classAnnAttrs.<HierarchyMode> getEnum("hierarchyMode");
dirtyContext(testContext, hierarchyMode); dirtyContext(testContext, hierarchyMode);
} }
} }
@@ -185,10 +183,9 @@ public class DirtiesContextTestExecutionListener extends AbstractTestExecutionLi
Class<?> testClass = testContext.getTestClass(); Class<?> testClass = testContext.getTestClass();
Assert.notNull(testClass, "The test class of the supplied TestContext must not be null"); Assert.notNull(testClass, "The test class of the supplied TestContext must not be null");
final String annotationType = DirtiesContext.class.getName(); DirtiesContext dirtiesContext = AnnotatedElementUtils.findAnnotation(testClass, DirtiesContext.class);
AnnotationAttributes classAnnAttrs = AnnotatedElementUtils.findAnnotationAttributes(testClass, annotationType); boolean classAnnotated = (dirtiesContext != null);
boolean classAnnotated = classAnnAttrs != null; ClassMode classMode = (classAnnotated ? dirtiesContext.classMode() : null);
ClassMode classMode = classAnnotated ? classAnnAttrs.<ClassMode> getEnum("classMode") : null;
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format( logger.debug(String.format(
@@ -197,8 +194,7 @@ public class DirtiesContextTestExecutionListener extends AbstractTestExecutionLi
} }
if (classMode == requiredClassMode) { if (classMode == requiredClassMode) {
HierarchyMode hierarchyMode = classAnnAttrs.<HierarchyMode> getEnum("hierarchyMode"); dirtyContext(testContext, dirtiesContext.hierarchyMode());
dirtyContext(testContext, hierarchyMode);
} }
} }

View File

@@ -19,7 +19,6 @@ package org.springframework.test.context.support;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.style.ToStringCreator; import org.springframework.core.style.ToStringCreator;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
@@ -58,17 +57,17 @@ class TestPropertySourceAttributes {
/** /**
* Create a new {@code TestPropertySourceAttributes} instance for the * Create a new {@code TestPropertySourceAttributes} instance for the
* supplied {@link AnnotationAttributes} (parsed from a * supplied {@link TestPropertySource @TestPropertySource} annotation and
* {@link TestPropertySource @TestPropertySource} annotation) and * the {@linkplain Class test class} that declared it, enforcing
* the {@linkplain Class test class} that declared them, enforcing
* configuration rules and detecting a default properties file if * configuration rules and detecting a default properties file if
* necessary. * necessary.
* @param declaringClass the class that declared {@code @TestPropertySource} * @param declaringClass the class that declared {@code @TestPropertySource}
* @param annAttrs the annotation attributes from which to retrieve the attributes * @param testPropertySource the annotation from which to retrieve the attributes
* @since 4.2
*/ */
TestPropertySourceAttributes(Class<?> declaringClass, AnnotationAttributes annAttrs) { TestPropertySourceAttributes(Class<?> declaringClass, TestPropertySource testPropertySource) {
this(declaringClass, annAttrs.getStringArray("locations"), annAttrs.getBoolean("inheritLocations"), this(declaringClass, testPropertySource.locations(), testPropertySource.inheritLocations(),
annAttrs.getStringArray("properties"), annAttrs.getBoolean("inheritProperties")); testPropertySource.properties(), testPropertySource.inheritProperties());
} }
private TestPropertySourceAttributes(Class<?> declaringClass, String[] locations, boolean inheritLocations, private TestPropertySourceAttributes(Class<?> declaringClass, String[] locations, boolean inheritLocations,

View File

@@ -29,7 +29,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MapPropertySource;
@@ -39,6 +38,7 @@ import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePropertySource; import org.springframework.core.io.support.ResourcePropertySource;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.util.TestContextResourceUtils; import org.springframework.test.context.util.TestContextResourceUtils;
import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@@ -96,15 +96,16 @@ public abstract class TestPropertySourceUtils {
annotationType.getName(), testClass.getName())); annotationType.getName(), testClass.getName()));
while (descriptor != null) { while (descriptor != null) {
AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes(); TestPropertySource testPropertySource = descriptor.getMergedAnnotation();
Class<?> rootDeclaringClass = descriptor.getRootDeclaringClass(); Class<?> rootDeclaringClass = descriptor.getRootDeclaringClass();
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace(String.format("Retrieved @TestPropertySource attributes [%s] for declaring class [%s].", logger.trace(String.format("Retrieved @TestPropertySource [%s] for declaring class [%s].",
annAttrs, rootDeclaringClass.getName())); testPropertySource, rootDeclaringClass.getName()));
} }
TestPropertySourceAttributes attributes = new TestPropertySourceAttributes(rootDeclaringClass, annAttrs); TestPropertySourceAttributes attributes = new TestPropertySourceAttributes(rootDeclaringClass,
testPropertySource);
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Resolved TestPropertySource attributes: " + attributes); logger.trace("Resolved TestPropertySource attributes: " + attributes);
} }

View File

@@ -30,7 +30,6 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils; import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.test.annotation.Rollback; import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.TestContext; import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener; import org.springframework.test.context.support.AbstractTestExecutionListener;
@@ -500,18 +499,18 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
if (this.configurationAttributes == null) { if (this.configurationAttributes == null) {
Class<?> clazz = testContext.getTestClass(); Class<?> clazz = testContext.getTestClass();
AnnotationAttributes annAttrs = AnnotatedElementUtils.findAnnotationAttributes(clazz, TransactionConfiguration txConfig = AnnotatedElementUtils.findAnnotation(clazz,
TransactionConfiguration.class.getName()); TransactionConfiguration.class);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("Retrieved @TransactionConfiguration attributes [%s] for test class [%s].", logger.debug(String.format("Retrieved @TransactionConfiguration [%s] for test class [%s].",
annAttrs, clazz)); txConfig, clazz));
} }
String transactionManagerName; String transactionManagerName;
boolean defaultRollback; boolean defaultRollback;
if (annAttrs != null) { if (txConfig != null) {
transactionManagerName = annAttrs.getString("transactionManager"); transactionManagerName = txConfig.transactionManager();
defaultRollback = annAttrs.getBoolean("defaultRollback"); defaultRollback = txConfig.defaultRollback();
} }
else { else {
transactionManagerName = DEFAULT_TRANSACTION_MANAGER_NAME; transactionManagerName = DEFAULT_TRANSACTION_MANAGER_NAME;

View File

@@ -272,12 +272,16 @@ public abstract class MetaAnnotationUtils {
private final T annotation; private final T annotation;
private final T mergedAnnotation;
private final AnnotationAttributes annotationAttributes; private final AnnotationAttributes annotationAttributes;
public AnnotationDescriptor(Class<?> rootDeclaringClass, T annotation) { public AnnotationDescriptor(Class<?> rootDeclaringClass, T annotation) {
this(rootDeclaringClass, rootDeclaringClass, null, annotation); this(rootDeclaringClass, rootDeclaringClass, null, annotation);
} }
@SuppressWarnings("unchecked")
public AnnotationDescriptor(Class<?> rootDeclaringClass, Class<?> declaringClass, public AnnotationDescriptor(Class<?> rootDeclaringClass, Class<?> declaringClass,
Annotation composedAnnotation, T annotation) { Annotation composedAnnotation, T annotation) {
Assert.notNull(rootDeclaringClass, "rootDeclaringClass must not be null"); Assert.notNull(rootDeclaringClass, "rootDeclaringClass must not be null");
@@ -286,8 +290,10 @@ public abstract class MetaAnnotationUtils {
this.declaringClass = declaringClass; this.declaringClass = declaringClass;
this.composedAnnotation = composedAnnotation; this.composedAnnotation = composedAnnotation;
this.annotation = annotation; this.annotation = annotation;
this.annotationAttributes = AnnotatedElementUtils.findAnnotationAttributes( this.annotationAttributes = AnnotatedElementUtils.findAnnotationAttributes(rootDeclaringClass,
rootDeclaringClass, annotation.annotationType()); annotation.annotationType().getName(), false, false);
this.mergedAnnotation = AnnotationUtils.synthesizeAnnotation(annotationAttributes,
(Class<T>) annotation.annotationType(), rootDeclaringClass);
} }
public Class<?> getRootDeclaringClass() { public Class<?> getRootDeclaringClass() {
@@ -302,6 +308,16 @@ public abstract class MetaAnnotationUtils {
return this.annotation; return this.annotation;
} }
/**
* Get the annotation that was synthesized from the merged
* {@link #getAnnotationAttributes AnnotationAttributes}.
* @see #getAnnotationAttributes()
* @see AnnotationUtils#synthesizeAnnotation(java.util.Map, Class, java.lang.reflect.AnnotatedElement)
*/
public T getMergedAnnotation() {
return this.mergedAnnotation;
}
public Class<? extends Annotation> getAnnotationType() { public Class<? extends Annotation> getAnnotationType() {
return this.annotation.annotationType(); return this.annotation.annotationType();
} }

View File

@@ -79,7 +79,7 @@ public class SqlScriptsTestExecutionListenerTests {
containsString("attribute [scripts] and its alias [value]"))); containsString("attribute [scripts] and its alias [value]")));
exception.expectMessage(either(containsString("values of [{foo}] and [{bar}]")).or( exception.expectMessage(either(containsString("values of [{foo}] and [{bar}]")).or(
containsString("values of [{bar}] and [{foo}]"))); containsString("values of [{bar}] and [{foo}]")));
exception.expectMessage(containsString("but only one declaration is permitted")); exception.expectMessage(containsString("but only one is permitted"));
listener.beforeTestMethod(testContext); listener.beforeTestMethod(testContext);
} }

View File

@@ -401,7 +401,7 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
Builder mappingName(String name); Builder mappingName(String name);
/** /**
* Set a custom conditions to use. * Set a custom condition to use.
*/ */
Builder customCondition(RequestCondition<?> condition); Builder customCondition(RequestCondition<?> condition);

View File

@@ -26,6 +26,7 @@ import java.util.Set;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInterceptor;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
@@ -42,7 +43,6 @@ import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.objenesis.Objenesis; import org.springframework.objenesis.Objenesis;
import org.springframework.objenesis.SpringObjenesis; import org.springframework.objenesis.SpringObjenesis;
import org.springframework.util.AntPathMatcher; import org.springframework.util.AntPathMatcher;
@@ -418,13 +418,11 @@ public class MvcUriComponentsBuilder {
private static String getTypeRequestMapping(Class<?> controllerType) { private static String getTypeRequestMapping(Class<?> controllerType) {
Assert.notNull(controllerType, "'controllerType' must not be null"); Assert.notNull(controllerType, "'controllerType' must not be null");
String annotType = RequestMapping.class.getName(); RequestMapping requestMapping = AnnotatedElementUtils.findAnnotation(controllerType, RequestMapping.class);
AnnotationAttributes attrs = AnnotatedElementUtils.findAnnotationAttributes(controllerType, annotType); if (requestMapping == null) {
if (attrs == null) {
return "/"; return "/";
} }
String[] paths = attrs.getStringArray("path"); String[] paths = requestMapping.path();
paths = ObjectUtils.isEmpty(paths) ? attrs.getStringArray("value") : paths;
if (ObjectUtils.isEmpty(paths) || StringUtils.isEmpty(paths[0])) { if (ObjectUtils.isEmpty(paths) || StringUtils.isEmpty(paths[0])) {
return "/"; return "/";
} }
@@ -435,13 +433,11 @@ public class MvcUriComponentsBuilder {
} }
private static String getMethodRequestMapping(Method method) { private static String getMethodRequestMapping(Method method) {
String annotType = RequestMapping.class.getName(); RequestMapping requestMapping = AnnotatedElementUtils.findAnnotation(method, RequestMapping.class);
AnnotationAttributes attrs = AnnotatedElementUtils.findAnnotationAttributes(method, annotType); if (requestMapping == null) {
if (attrs == null) {
throw new IllegalArgumentException("No @RequestMapping on: " + method.toGenericString()); throw new IllegalArgumentException("No @RequestMapping on: " + method.toGenericString());
} }
String[] paths = attrs.getStringArray("path"); String[] paths = requestMapping.path();
paths = ObjectUtils.isEmpty(paths) ? attrs.getStringArray("value") : paths;
if (ObjectUtils.isEmpty(paths) || StringUtils.isEmpty(paths[0])) { if (ObjectUtils.isEmpty(paths) || StringUtils.isEmpty(paths[0])) {
return "/"; return "/";
} }
@@ -759,7 +755,6 @@ public class MvcUriComponentsBuilder {
* that accept the controllerType. * that accept the controllerType.
*/ */
@Deprecated @Deprecated
@SuppressWarnings("unused")
public MethodArgumentBuilder(Method method) { public MethodArgumentBuilder(Method method) {
this(method.getDeclaringClass(), method); this(method.getDeclaringClass(), method);
} }

View File

@@ -22,12 +22,10 @@ import java.util.List;
import org.springframework.context.EmbeddedValueResolverAware; import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringValueResolver; import org.springframework.util.StringValueResolver;
import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.CrossOrigin;
@@ -49,6 +47,7 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMappi
* *
* @author Arjen Poutsma * @author Arjen Poutsma
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @author Sam Brannen
* @since 3.1 * @since 3.1
*/ */
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
@@ -212,7 +211,6 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
* @param handlerType the handler type for which to create the condition * @param handlerType the handler type for which to create the condition
* @return the condition, or {@code null} * @return the condition, or {@code null}
*/ */
@SuppressWarnings("unused")
protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) { protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
return null; return null;
} }
@@ -228,77 +226,41 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
* @param method the handler method for which to create the condition * @param method the handler method for which to create the condition
* @return the condition, or {@code null} * @return the condition, or {@code null}
*/ */
@SuppressWarnings("unused")
protected RequestCondition<?> getCustomMethodCondition(Method method) { protected RequestCondition<?> getCustomMethodCondition(Method method) {
return null; return null;
} }
/** /**
* Transitional method used to invoke one of two createRequestMappingInfo * Delegates to {@link #createRequestMappingInfo(RequestMapping, RequestCondition)},
* variants one of which is deprecated. * supplying the appropriate custom {@link RequestCondition} depending on whether
* the supplied {@code annotatedElement} is a class or method.
*
* @see #getCustomTypeCondition(Class)
* @see #getCustomMethodCondition(Method)
*/ */
@SuppressWarnings("deprecation")
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement annotatedElement) { private RequestMappingInfo createRequestMappingInfo(AnnotatedElement annotatedElement) {
RequestMapping annotation; RequestMapping requestMapping = AnnotatedElementUtils.findAnnotation(annotatedElement, RequestMapping.class);
AnnotationAttributes attributes; RequestCondition<?> customCondition = ((annotatedElement instanceof Class<?>) ? getCustomTypeCondition((Class<?>) annotatedElement)
RequestCondition<?> customCondition; : getCustomMethodCondition((Method) annotatedElement));
String annotationType = RequestMapping.class.getName(); return ((requestMapping != null) ? createRequestMappingInfo(requestMapping, customCondition) : null);
if (annotatedElement instanceof Class<?>) {
Class<?> type = (Class<?>) annotatedElement;
annotation = AnnotationUtils.findAnnotation(type, RequestMapping.class);
attributes = AnnotatedElementUtils.findAnnotationAttributes(type, annotationType);
customCondition = getCustomTypeCondition(type);
}
else {
Method method = (Method) annotatedElement;
annotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
attributes = AnnotatedElementUtils.findAnnotationAttributes(method, annotationType);
customCondition = getCustomMethodCondition(method);
}
RequestMappingInfo info = null;
if (annotation != null) {
info = createRequestMappingInfo(annotation, customCondition);
if (info == null) {
info = createRequestMappingInfo(attributes, customCondition);
}
}
return info;
} }
/** /**
* Create a RequestMappingInfo from a RequestMapping annotation. * Create a {@link RequestMappingInfo} from the supplied
* @deprecated as of 4.2 after the introduction of support for * {@link RequestMapping @RequestMapping} annotation, which is either
* {@code @RequestMapping} as meta-annotation. Please use * a directly declared annotation, a meta-annotation, or the synthesized
* {@link #createRequestMappingInfo(AnnotationAttributes, RequestCondition)}. * result of merging annotation attributes within an annotation hierarchy.
*/ */
@Deprecated protected RequestMappingInfo createRequestMappingInfo(RequestMapping requestMapping,
@SuppressWarnings("unused")
protected RequestMappingInfo createRequestMappingInfo(RequestMapping annotation,
RequestCondition<?> customCondition) { RequestCondition<?> customCondition) {
return null; return RequestMappingInfo.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
} .methods(requestMapping.method())
.params(requestMapping.params())
/** .headers(requestMapping.headers())
* Create a RequestMappingInfo from the attributes of an .consumes(requestMapping.consumes())
* {@code @RequestMapping} annotation or a meta-annotation, i.e. a custom .produces(requestMapping.produces())
* annotation annotated with {@code @RequestMapping}. .mappingName(requestMapping.name())
* @since 4.2
*/
protected RequestMappingInfo createRequestMappingInfo(AnnotationAttributes attributes,
RequestCondition<?> customCondition) {
String[] paths = attributes.getStringArray("path");
paths = ObjectUtils.isEmpty(paths) ? attributes.getStringArray("value") : paths;
paths = resolveEmbeddedValuesInPatterns(paths);
return RequestMappingInfo.paths(paths)
.methods((RequestMethod[]) attributes.get("method"))
.params(attributes.getStringArray("params"))
.headers(attributes.getStringArray("headers"))
.consumes(attributes.getStringArray("consumes"))
.produces(attributes.getStringArray("produces"))
.mappingName(attributes.getString("name"))
.customCondition(customCondition) .customCondition(customCondition)
.options(this.config) .options(this.config)
.build(); .build();