Support arbitrary meta-annotation levels in the TCF
Prior to this commit, the findAnnotationDescriptor() and findAnnotationDescriptorForTypes() methods in MetaAnnotationUtils only supported a single level of meta-annotations. In particular, this kept the following annotations from being used as meta-annotations on meta-annotations: - @ContextConfiguration - @ContextHierarchy - @ActiveProfiles - @TestExecutionListeners This commit alters the search algorithms used in MetaAnnotationUtils so that arbitrary levels of meta-annotations are now supported for the aforementioned test-related annotations. Issue: SPR-11470
This commit is contained in:
parent
2c8f25a14c
commit
f8950960f2
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
package org.springframework.test.context;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
|
|
@ -29,20 +31,22 @@ import static org.springframework.core.annotation.AnnotationUtils.*;
|
|||
|
||||
/**
|
||||
* {@code MetaAnnotationUtils} is a collection of utility methods that complements
|
||||
* support already available in {@link AnnotationUtils}.
|
||||
* the standard support already available in {@link AnnotationUtils}.
|
||||
*
|
||||
* <p>Whereas {@code AnnotationUtils} only provides utilities for <em>getting</em>
|
||||
* or <em>finding</em> an annotation, {@code MetaAnnotationUtils} provides
|
||||
* additional support for determining the <em>root class</em> on which an
|
||||
* <p>Whereas {@code AnnotationUtils} provides utilities for <em>getting</em> or
|
||||
* <em>finding</em> an annotation, {@code MetaAnnotationUtils} goes a step further
|
||||
* by providing support for determining the <em>root class</em> on which an
|
||||
* annotation is declared, either directly or via a <em>composed annotation</em>.
|
||||
* This additional information is encapsulated in an {@link AnnotationDescriptor}.
|
||||
*
|
||||
* <p>The additional information provided by an {@code AnnotationDescriptor} is
|
||||
* required in the <em>Spring TestContext Framework</em> in order to be able to
|
||||
* support class hierarchy traversals for <em>inherited</em> annotations such as
|
||||
* required by the <em>Spring TestContext Framework</em> in order to be able to
|
||||
* support class hierarchy traversals for annotations such as
|
||||
* {@link ContextConfiguration @ContextConfiguration},
|
||||
* {@link TestExecutionListeners @TestExecutionListeners}, and
|
||||
* {@link ActiveProfiles @ActiveProfiles}.
|
||||
* {@link ActiveProfiles @ActiveProfiles} which offer support for merging and
|
||||
* overriding various <em>inherited</em> annotation attributes (e.g., {@link
|
||||
* ContextConfiguration#inheritLocations}).
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 4.0
|
||||
|
|
@ -57,7 +61,7 @@ abstract class MetaAnnotationUtils {
|
|||
|
||||
/**
|
||||
* Find the {@link AnnotationDescriptor} for the supplied {@code annotationType}
|
||||
* from the supplied {@link Class}, traversing its annotations and superclasses
|
||||
* on the supplied {@link Class}, traversing its annotations and superclasses
|
||||
* if no annotation can be found on the given class itself.
|
||||
*
|
||||
* <p>This method explicitly handles class-level annotations which are not
|
||||
|
|
@ -66,23 +70,22 @@ abstract class MetaAnnotationUtils {
|
|||
*
|
||||
* <p>The algorithm operates as follows:
|
||||
* <ol>
|
||||
* <li>Search for a local declaration of the annotation on the given class
|
||||
* and return a corresponding {@code AnnotationDescriptor} if found.
|
||||
* <li>Search through all annotations that the given class declares,
|
||||
* returning an {@code AnnotationDescriptor} for the first matching
|
||||
* candidate, if any.
|
||||
* <li>Proceed with introspection of the superclass hierarchy of the given
|
||||
* class by returning to step #1 with the superclass as the class to look
|
||||
* for annotations on.
|
||||
* <li>Search for the annotation on the given class and return a corresponding
|
||||
* {@code AnnotationDescriptor} if found.
|
||||
* <li>Recursively search through all annotations that the given class declares.
|
||||
* <li>Recursively search through the superclass hierarchy of the given class.
|
||||
* </ol>
|
||||
*
|
||||
* <p>In this context, the term <em>recursively</em> means that the search
|
||||
* process continues by returning to step #1 with the current annotation or
|
||||
* superclass as the class to look for annotations on.
|
||||
*
|
||||
* <p>If the supplied {@code clazz} is an interface, only the interface
|
||||
* itself will be checked; the inheritance hierarchy for interfaces will not
|
||||
* be traversed.
|
||||
*
|
||||
* @param clazz the class to look for annotations on
|
||||
* @param annotationType the annotation class to look for, both locally and
|
||||
* as a meta-annotation
|
||||
* @param annotationType the type of annotation to look for
|
||||
* @return the corresponding annotation descriptor if the annotation was found;
|
||||
* otherwise {@code null}
|
||||
* @see AnnotationUtils#findAnnotationDeclaringClass(Class, Class)
|
||||
|
|
@ -90,6 +93,22 @@ abstract class MetaAnnotationUtils {
|
|||
*/
|
||||
public static <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescriptor(Class<?> clazz,
|
||||
Class<T> annotationType) {
|
||||
return findAnnotationDescriptor(clazz, new HashSet<Annotation>(), annotationType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the search algorithm for {@link #findAnnotationDescriptor(Class, Class)},
|
||||
* avoiding endless recursion by tracking which annotations have already been
|
||||
* <em>visited</em>.
|
||||
*
|
||||
* @param clazz the class to look for annotations on
|
||||
* @param visited the set of annotations that have already been visited
|
||||
* @param annotationType the type of annotation to look for
|
||||
* @return the corresponding annotation descriptor if the annotation was found;
|
||||
* otherwise {@code null}
|
||||
*/
|
||||
private static <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescriptor(Class<?> clazz,
|
||||
Set<Annotation> visited, Class<T> annotationType) {
|
||||
|
||||
Assert.notNull(annotationType, "Annotation type must not be null");
|
||||
|
||||
|
|
@ -103,29 +122,30 @@ abstract class MetaAnnotationUtils {
|
|||
}
|
||||
|
||||
// Declared on a composed annotation (i.e., as a meta-annotation)?
|
||||
if (!Annotation.class.isAssignableFrom(clazz)) {
|
||||
for (Annotation composedAnnotation : clazz.getAnnotations()) {
|
||||
T annotation = composedAnnotation.annotationType().getAnnotation(annotationType);
|
||||
if (annotation != null) {
|
||||
return new AnnotationDescriptor<T>(clazz, composedAnnotation, annotation);
|
||||
for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) {
|
||||
if (visited.add(composedAnnotation)) {
|
||||
AnnotationDescriptor<T> descriptor = findAnnotationDescriptor(composedAnnotation.annotationType(),
|
||||
visited, annotationType);
|
||||
if (descriptor != null) {
|
||||
return new AnnotationDescriptor<T>(clazz, descriptor.getDeclaringClass(), composedAnnotation,
|
||||
descriptor.getAnnotation());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Declared on a superclass?
|
||||
return findAnnotationDescriptor(clazz.getSuperclass(), annotationType);
|
||||
return findAnnotationDescriptor(clazz.getSuperclass(), visited, annotationType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the {@link UntypedAnnotationDescriptor} for the first {@link Class}
|
||||
* in the inheritance hierarchy of the specified {@code clazz} (including
|
||||
* the specified {@code clazz} itself) which declares at least one of the
|
||||
* specified {@code annotationTypes}, or {@code null} if none of the
|
||||
* specified annotation types could be found.
|
||||
* specified {@code annotationTypes}.
|
||||
*
|
||||
* <p>This method traverses the annotations and superclasses of the specified
|
||||
* {@code clazz} if no annotation can be found on the given class itself.
|
||||
*
|
||||
*
|
||||
* <p>This method explicitly handles class-level annotations which are not
|
||||
* declared as {@linkplain java.lang.annotation.Inherited inherited} <em>as
|
||||
* well as meta-annotations</em>.
|
||||
|
|
@ -135,21 +155,20 @@ abstract class MetaAnnotationUtils {
|
|||
* <li>Search for a local declaration of one of the annotation types on
|
||||
* the given class and return a corresponding {@code UntypedAnnotationDescriptor}
|
||||
* if found.
|
||||
* <li>Search through all annotations that the given class declares,
|
||||
* returning an {@code UntypedAnnotationDescriptor} for the first matching
|
||||
* candidate, if any.
|
||||
* <li>Proceed with introspection of the superclass hierarchy of the given
|
||||
* class by returning to step #1 with the superclass as the class to look
|
||||
* for annotations on.
|
||||
* <li>Recursively search through all annotations that the given class declares.
|
||||
* <li>Recursively search through the superclass hierarchy of the given class.
|
||||
* </ol>
|
||||
*
|
||||
* <p>In this context, the term <em>recursively</em> means that the search
|
||||
* process continues by returning to step #1 with the current annotation or
|
||||
* superclass as the class to look for annotations on.
|
||||
*
|
||||
* <p>If the supplied {@code clazz} is an interface, only the interface
|
||||
* itself will be checked; the inheritance hierarchy for interfaces will not
|
||||
* be traversed.
|
||||
*
|
||||
* @param clazz the class to look for annotations on
|
||||
* @param annotationTypes the types of annotations to look for, both locally
|
||||
* and as meta-annotations
|
||||
* @param annotationTypes the types of annotations to look for
|
||||
* @return the corresponding annotation descriptor if one of the annotations
|
||||
* was found; otherwise {@code null}
|
||||
* @see AnnotationUtils#findAnnotationDeclaringClassForTypes(java.util.List, Class)
|
||||
|
|
@ -158,6 +177,23 @@ abstract class MetaAnnotationUtils {
|
|||
@SuppressWarnings("unchecked")
|
||||
public static UntypedAnnotationDescriptor findAnnotationDescriptorForTypes(Class<?> clazz,
|
||||
Class<? extends Annotation>... annotationTypes) {
|
||||
return findAnnotationDescriptorForTypes(clazz, new HashSet<Annotation>(), annotationTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the search algorithm for {@link #findAnnotationDescriptorForTypes(Class, Class...)},
|
||||
* avoiding endless recursion by tracking which annotations have already been
|
||||
* <em>visited</em>.
|
||||
*
|
||||
* @param clazz the class to look for annotations on
|
||||
* @param visited the set of annotations that have already been visited
|
||||
* @param annotationTypes the types of annotations to look for
|
||||
* @return the corresponding annotation descriptor if one of the annotations
|
||||
* was found; otherwise {@code null}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static UntypedAnnotationDescriptor findAnnotationDescriptorForTypes(Class<?> clazz,
|
||||
Set<Annotation> visited, Class<? extends Annotation>... annotationTypes) {
|
||||
|
||||
assertNonEmptyAnnotationTypeArray(annotationTypes, "The list of annotation types must not be empty");
|
||||
|
||||
|
|
@ -173,19 +209,19 @@ abstract class MetaAnnotationUtils {
|
|||
}
|
||||
|
||||
// Declared on a composed annotation (i.e., as a meta-annotation)?
|
||||
if (!Annotation.class.isAssignableFrom(clazz)) {
|
||||
for (Annotation composedAnnotation : clazz.getAnnotations()) {
|
||||
for (Class<? extends Annotation> annotationType : annotationTypes) {
|
||||
Annotation annotation = composedAnnotation.annotationType().getAnnotation(annotationType);
|
||||
if (annotation != null) {
|
||||
return new UntypedAnnotationDescriptor(clazz, composedAnnotation, annotation);
|
||||
}
|
||||
for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) {
|
||||
if (visited.add(composedAnnotation)) {
|
||||
UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(
|
||||
composedAnnotation.annotationType(), visited, annotationTypes);
|
||||
if (descriptor != null) {
|
||||
return new UntypedAnnotationDescriptor(clazz, descriptor.getDeclaringClass(), composedAnnotation,
|
||||
descriptor.getAnnotation());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Declared on a superclass?
|
||||
return findAnnotationDescriptorForTypes(clazz.getSuperclass(), annotationTypes);
|
||||
return findAnnotationDescriptorForTypes(clazz.getSuperclass(), visited, annotationTypes);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -254,16 +290,16 @@ abstract class MetaAnnotationUtils {
|
|||
|
||||
|
||||
public AnnotationDescriptor(Class<?> rootDeclaringClass, T annotation) {
|
||||
this(rootDeclaringClass, null, annotation);
|
||||
this(rootDeclaringClass, rootDeclaringClass, null, annotation);
|
||||
}
|
||||
|
||||
public AnnotationDescriptor(Class<?> rootDeclaringClass, Annotation composedAnnotation, T annotation) {
|
||||
public AnnotationDescriptor(Class<?> rootDeclaringClass, Class<?> declaringClass,
|
||||
Annotation composedAnnotation, T annotation) {
|
||||
Assert.notNull(rootDeclaringClass, "rootDeclaringClass must not be null");
|
||||
Assert.notNull(annotation, "annotation must not be null");
|
||||
|
||||
this.rootDeclaringClass = rootDeclaringClass;
|
||||
this.declaringClass = (composedAnnotation != null) ? composedAnnotation.annotationType()
|
||||
: rootDeclaringClass;
|
||||
this.declaringClass = declaringClass;
|
||||
this.composedAnnotation = composedAnnotation;
|
||||
this.annotation = annotation;
|
||||
this.annotationAttributes = AnnotatedElementUtils.getAnnotationAttributes(rootDeclaringClass,
|
||||
|
|
@ -322,12 +358,13 @@ abstract class MetaAnnotationUtils {
|
|||
*/
|
||||
public static class UntypedAnnotationDescriptor extends AnnotationDescriptor<Annotation> {
|
||||
|
||||
public UntypedAnnotationDescriptor(Class<?> declaringClass, Annotation annotation) {
|
||||
super(declaringClass, annotation);
|
||||
public UntypedAnnotationDescriptor(Class<?> rootDeclaringClass, Annotation annotation) {
|
||||
this(rootDeclaringClass, rootDeclaringClass, null, annotation);
|
||||
}
|
||||
|
||||
public UntypedAnnotationDescriptor(Class<?> declaringClass, Annotation composedAnnotation, Annotation annotation) {
|
||||
super(declaringClass, composedAnnotation, annotation);
|
||||
public UntypedAnnotationDescriptor(Class<?> rootDeclaringClass, Class<?> declaringClass,
|
||||
Annotation composedAnnotation, Annotation annotation) {
|
||||
super(rootDeclaringClass, declaringClass, composedAnnotation, annotation);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -47,28 +47,48 @@ public class MetaAnnotationUtilsTests {
|
|||
|
||||
private void assertAtComponentOnComposedAnnotation(Class<?> startClass, Class<?> rootDeclaringClass, String name,
|
||||
Class<? extends Annotation> composedAnnotationType) {
|
||||
assertAtComponentOnComposedAnnotation(rootDeclaringClass, rootDeclaringClass, composedAnnotationType, name,
|
||||
composedAnnotationType);
|
||||
}
|
||||
|
||||
private void assertAtComponentOnComposedAnnotation(Class<?> startClass, Class<?> rootDeclaringClass,
|
||||
Class<?> declaringClass, String name, Class<? extends Annotation> composedAnnotationType) {
|
||||
AnnotationDescriptor<Component> descriptor = findAnnotationDescriptor(startClass, Component.class);
|
||||
assertNotNull(descriptor);
|
||||
assertEquals(rootDeclaringClass, descriptor.getRootDeclaringClass());
|
||||
assertEquals(composedAnnotationType, descriptor.getDeclaringClass());
|
||||
assertEquals(Component.class, descriptor.getAnnotationType());
|
||||
assertEquals(name, descriptor.getAnnotation().value());
|
||||
assertNotNull(descriptor.getComposedAnnotation());
|
||||
assertEquals(composedAnnotationType, descriptor.getComposedAnnotationType());
|
||||
assertNotNull("AnnotationDescriptor should not be null", descriptor);
|
||||
assertEquals("rootDeclaringClass", rootDeclaringClass, descriptor.getRootDeclaringClass());
|
||||
assertEquals("declaringClass", declaringClass, descriptor.getDeclaringClass());
|
||||
assertEquals("annotationType", Component.class, descriptor.getAnnotationType());
|
||||
assertEquals("component name", name, descriptor.getAnnotation().value());
|
||||
assertNotNull("composedAnnotation should not be null", descriptor.getComposedAnnotation());
|
||||
assertEquals("composedAnnotationType", composedAnnotationType, descriptor.getComposedAnnotationType());
|
||||
}
|
||||
|
||||
private void assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(Class<?> startClass, String name,
|
||||
Class<? extends Annotation> composedAnnotationType) {
|
||||
assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(startClass, startClass, name,
|
||||
composedAnnotationType);
|
||||
}
|
||||
|
||||
private void assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(Class<?> startClass,
|
||||
Class<?> rootDeclaringClass, String name, Class<? extends Annotation> composedAnnotationType) {
|
||||
assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(startClass, rootDeclaringClass,
|
||||
composedAnnotationType, name, composedAnnotationType);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(Class<?> startClass,
|
||||
Class<?> declaringClass, String name, Class<? extends Annotation> composedAnnotationType) {
|
||||
Class<?> rootDeclaringClass, Class<?> declaringClass, String name,
|
||||
Class<? extends Annotation> composedAnnotationType) {
|
||||
Class<Component> annotationType = Component.class;
|
||||
UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(startClass, Service.class,
|
||||
annotationType, Order.class, Transactional.class);
|
||||
assertNotNull(descriptor);
|
||||
assertEquals(declaringClass, descriptor.getRootDeclaringClass());
|
||||
assertEquals(annotationType, descriptor.getAnnotationType());
|
||||
assertEquals(name, ((Component) descriptor.getAnnotation()).value());
|
||||
assertNotNull(descriptor.getComposedAnnotation());
|
||||
assertEquals(composedAnnotationType, descriptor.getComposedAnnotationType());
|
||||
assertNotNull("UntypedAnnotationDescriptor should not be null", descriptor);
|
||||
assertEquals("rootDeclaringClass", rootDeclaringClass, descriptor.getRootDeclaringClass());
|
||||
assertEquals("declaringClass", declaringClass, descriptor.getDeclaringClass());
|
||||
assertEquals("annotationType", annotationType, descriptor.getAnnotationType());
|
||||
assertEquals("component name", name, ((Component) descriptor.getAnnotation()).value());
|
||||
assertNotNull("composedAnnotation should not be null", descriptor.getComposedAnnotation());
|
||||
assertEquals("composedAnnotationType", composedAnnotationType, descriptor.getComposedAnnotationType());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -150,6 +170,45 @@ public class MetaAnnotationUtilsTests {
|
|||
ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, "meta2", Meta2.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.0.3
|
||||
*/
|
||||
@Test
|
||||
public void findAnnotationDescriptorOnMetaMetaAnnotatedClass() {
|
||||
Class<MetaMetaAnnotatedClass> startClass = MetaMetaAnnotatedClass.class;
|
||||
assertAtComponentOnComposedAnnotation(startClass, startClass, Meta2.class, "meta2", MetaMeta.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.0.3
|
||||
*/
|
||||
@Test
|
||||
public void findAnnotationDescriptorOnMetaMetaMetaAnnotatedClass() {
|
||||
Class<MetaMetaMetaAnnotatedClass> startClass = MetaMetaMetaAnnotatedClass.class;
|
||||
assertAtComponentOnComposedAnnotation(startClass, startClass, Meta2.class, "meta2", MetaMetaMeta.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.0.3
|
||||
*/
|
||||
@Test
|
||||
public void findAnnotationDescriptorOnAnnotatedClassWithMissingTargetMetaAnnotation() {
|
||||
// InheritedAnnotationClass is NOT annotated or meta-annotated with @Component
|
||||
AnnotationDescriptor<Component> descriptor = findAnnotationDescriptor(InheritedAnnotationClass.class,
|
||||
Component.class);
|
||||
assertNull("Should not find @Component on InheritedAnnotationClass", descriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.0.3
|
||||
*/
|
||||
@Test
|
||||
public void findAnnotationDescriptorOnMetaCycleAnnotatedClassWithMissingTargetMetaAnnotation() {
|
||||
AnnotationDescriptor<Component> descriptor = findAnnotationDescriptor(MetaCycleAnnotatedClass.class,
|
||||
Component.class);
|
||||
assertNull("Should not find @Component on MetaCycleAnnotatedClass", descriptor);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
|
|
@ -217,7 +276,7 @@ public class MetaAnnotationUtilsTests {
|
|||
@Test
|
||||
public void findAnnotationDescriptorForTypesWithMetaComponentAnnotation() throws Exception {
|
||||
Class<HasMetaComponentAnnotation> startClass = HasMetaComponentAnnotation.class;
|
||||
assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(startClass, startClass, "meta1", Meta1.class);
|
||||
assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(startClass, "meta1", Meta1.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -261,7 +320,7 @@ public class MetaAnnotationUtilsTests {
|
|||
@Test
|
||||
public void findAnnotationDescriptorForTypesForInterfaceWithMetaAnnotation() {
|
||||
Class<InterfaceWithMetaAnnotation> startClass = InterfaceWithMetaAnnotation.class;
|
||||
assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(startClass, startClass, "meta1", Meta1.class);
|
||||
assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(startClass, "meta1", Meta1.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -274,7 +333,7 @@ public class MetaAnnotationUtilsTests {
|
|||
@Test
|
||||
public void findAnnotationDescriptorForTypesForClassWithLocalMetaAnnotationAndMetaAnnotatedInterface() {
|
||||
Class<ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface> startClass = ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class;
|
||||
assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(startClass, startClass, "meta2", Meta2.class);
|
||||
assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(startClass, "meta2", Meta2.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -284,6 +343,50 @@ public class MetaAnnotationUtilsTests {
|
|||
ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, "meta2", Meta2.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.0.3
|
||||
*/
|
||||
@Test
|
||||
public void findAnnotationDescriptorForTypesOnMetaMetaAnnotatedClass() {
|
||||
Class<MetaMetaAnnotatedClass> startClass = MetaMetaAnnotatedClass.class;
|
||||
assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(startClass, startClass, Meta2.class, "meta2",
|
||||
MetaMeta.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.0.3
|
||||
*/
|
||||
@Test
|
||||
public void findAnnotationDescriptorForTypesOnMetaMetaMetaAnnotatedClass() {
|
||||
Class<MetaMetaMetaAnnotatedClass> startClass = MetaMetaMetaAnnotatedClass.class;
|
||||
assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(startClass, startClass, Meta2.class, "meta2",
|
||||
MetaMetaMeta.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.0.3
|
||||
*/
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void findAnnotationDescriptorForTypesOnAnnotatedClassWithMissingTargetMetaAnnotation() {
|
||||
// InheritedAnnotationClass is NOT annotated or meta-annotated with @Component,
|
||||
// @Service, or @Order, but it is annotated with @Transactional.
|
||||
UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(InheritedAnnotationClass.class,
|
||||
Service.class, Component.class, Order.class);
|
||||
assertNull("Should not find @Component on InheritedAnnotationClass", descriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.0.3
|
||||
*/
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void findAnnotationDescriptorForTypesOnMetaCycleAnnotatedClassWithMissingTargetMetaAnnotation() {
|
||||
UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(MetaCycleAnnotatedClass.class,
|
||||
Service.class, Component.class, Order.class);
|
||||
assertNull("Should not find @Component on MetaCycleAnnotatedClass", descriptor);
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
|
|
@ -299,6 +402,31 @@ public class MetaAnnotationUtilsTests {
|
|||
static @interface Meta2 {
|
||||
}
|
||||
|
||||
@Meta2
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface MetaMeta {
|
||||
}
|
||||
|
||||
@MetaMeta
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface MetaMetaMeta {
|
||||
}
|
||||
|
||||
@MetaCycle3
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface MetaCycle1 {
|
||||
}
|
||||
|
||||
@MetaCycle1
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface MetaCycle2 {
|
||||
}
|
||||
|
||||
@MetaCycle2
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface MetaCycle3 {
|
||||
}
|
||||
|
||||
@ContextConfiguration
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
static @interface MetaConfig {
|
||||
|
|
@ -340,6 +468,18 @@ public class MetaAnnotationUtilsTests {
|
|||
ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface {
|
||||
}
|
||||
|
||||
@MetaMeta
|
||||
static class MetaMetaAnnotatedClass {
|
||||
}
|
||||
|
||||
@MetaMetaMeta
|
||||
static class MetaMetaMetaAnnotatedClass {
|
||||
}
|
||||
|
||||
@MetaCycle3
|
||||
static class MetaCycleAnnotatedClass {
|
||||
}
|
||||
|
||||
@MetaConfig
|
||||
public class MetaConfigWithDefaultAttributesTestCase {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright 2002-2014 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.test.context.hierarchies.meta;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.ContextHierarchy;
|
||||
|
||||
/**
|
||||
* Custom context hierarchy configuration annotation.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 4.0.3
|
||||
*/
|
||||
@ContextHierarchy(@ContextConfiguration(classes = { DevConfig.class, ProductionConfig.class }))
|
||||
@ActiveProfiles("dev")
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface MetaContextHierarchyConfig {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Profile("dev")
|
||||
class DevConfig {
|
||||
|
||||
@Bean
|
||||
public String foo() {
|
||||
return "Dev Foo";
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Profile("prod")
|
||||
class ProductionConfig {
|
||||
|
||||
@Bean
|
||||
public String foo() {
|
||||
return "Production Foo";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright 2002-2014 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.test.context.hierarchies.meta;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Sam Brannen
|
||||
* @since 4.0.3
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@MetaMetaContextHierarchyConfig
|
||||
public class MetaHierarchyLevelOneTests {
|
||||
|
||||
@Autowired
|
||||
private String foo;
|
||||
|
||||
|
||||
@Test
|
||||
public void foo() {
|
||||
assertEquals("Dev Foo", foo);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright 2002-2014 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.test.context.hierarchies.meta;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Sam Brannen
|
||||
* @since 4.0.3
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@ActiveProfiles("prod")
|
||||
public class MetaHierarchyLevelTwoTests extends MetaHierarchyLevelOneTests {
|
||||
|
||||
@Configuration
|
||||
@Profile("prod")
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public String bar() {
|
||||
return "Prod Bar";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Autowired
|
||||
protected ApplicationContext context;
|
||||
|
||||
@Autowired
|
||||
private String bar;
|
||||
|
||||
|
||||
@Test
|
||||
public void bar() {
|
||||
assertEquals("Prod Bar", bar);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contextHierarchy() {
|
||||
assertNotNull("child ApplicationContext", context);
|
||||
assertNotNull("parent ApplicationContext", context.getParent());
|
||||
assertNull("grandparent ApplicationContext", context.getParent().getParent());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright 2002-2014 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.test.context.hierarchies.meta;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Custom context hierarchy configuration annotation that is itself meta-annotated
|
||||
* with {@link MetaContextHierarchyConfig @MetaContextHierarchyConfig}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 4.0.3
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@MetaContextHierarchyConfig
|
||||
public @interface MetaMetaContextHierarchyConfig {
|
||||
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ import static org.junit.Assert.*;
|
|||
|
||||
/**
|
||||
* Integration tests for meta-annotation attribute override support, relying on
|
||||
* default attribute values defined in {@link MetaConfig}.
|
||||
* default attribute values defined in {@link MetaConfig @MetaConfig}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 4.0
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2002-2014 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.test.context.junit4.annotation.meta;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
/**
|
||||
* Custom configuration annotation that is itself meta-annotated with
|
||||
* {@link MetaConfig @MetaConfig} and {@link ActiveProfiles @ActiveProfiles}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 4.0.3
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@MetaConfig
|
||||
// Override default "dev" profile from @MetaConfig:
|
||||
@ActiveProfiles("prod")
|
||||
public @interface MetaMetaConfig {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2002-2014 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.test.context.junit4.annotation.meta;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Integration tests for meta-meta-annotation support, relying on
|
||||
* default attribute values defined in {@link MetaConfig @MetaConfig} and
|
||||
* overrides in {@link MetaMetaConfig @MetaMetaConfig}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 4.0.3
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@MetaMetaConfig
|
||||
public class MetaMetaConfigDefaultsTests {
|
||||
|
||||
@Autowired
|
||||
private String foo;
|
||||
|
||||
|
||||
@Test
|
||||
public void foo() {
|
||||
assertEquals("Production Foo", foo);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue