From f8950960f2a610f65c485831c437ffa4eaff622d Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sun, 23 Feb 2014 01:05:15 +0100 Subject: [PATCH] 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 --- .../test/context/MetaAnnotationUtils.java | 141 ++++++++------ .../context/MetaAnnotationUtilsTests.java | 174 ++++++++++++++++-- .../meta/MetaContextHierarchyConfig.java | 62 +++++++ .../meta/MetaHierarchyLevelOneTests.java | 43 +++++ .../meta/MetaHierarchyLevelTwoTests.java | 68 +++++++ .../meta/MetaMetaContextHierarchyConfig.java | 36 ++++ .../meta/MetaConfigDefaultsTests.java | 2 +- .../annotation/meta/MetaMetaConfig.java | 40 ++++ .../meta/MetaMetaConfigDefaultsTests.java | 46 +++++ 9 files changed, 542 insertions(+), 70 deletions(-) create mode 100644 spring-test/src/test/java/org/springframework/test/context/hierarchies/meta/MetaContextHierarchyConfig.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/hierarchies/meta/MetaHierarchyLevelOneTests.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/hierarchies/meta/MetaHierarchyLevelTwoTests.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/hierarchies/meta/MetaMetaContextHierarchyConfig.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaMetaConfig.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaMetaConfigDefaultsTests.java diff --git a/spring-test/src/main/java/org/springframework/test/context/MetaAnnotationUtils.java b/spring-test/src/main/java/org/springframework/test/context/MetaAnnotationUtils.java index 0c215dc92a2..24e3c8fbdd5 100644 --- a/spring-test/src/main/java/org/springframework/test/context/MetaAnnotationUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/MetaAnnotationUtils.java @@ -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}. * - *

Whereas {@code AnnotationUtils} only provides utilities for getting - * or finding an annotation, {@code MetaAnnotationUtils} provides - * additional support for determining the root class on which an + *

Whereas {@code AnnotationUtils} provides utilities for getting or + * finding an annotation, {@code MetaAnnotationUtils} goes a step further + * by providing support for determining the root class on which an * annotation is declared, either directly or via a composed annotation. * This additional information is encapsulated in an {@link AnnotationDescriptor}. * *

The additional information provided by an {@code AnnotationDescriptor} is - * required in the Spring TestContext Framework in order to be able to - * support class hierarchy traversals for inherited annotations such as + * required by the Spring TestContext Framework 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 inherited 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. * *

This method explicitly handles class-level annotations which are not @@ -66,23 +70,22 @@ abstract class MetaAnnotationUtils { * *

The algorithm operates as follows: *

    - *
  1. Search for a local declaration of the annotation on the given class - * and return a corresponding {@code AnnotationDescriptor} if found. - *
  2. Search through all annotations that the given class declares, - * returning an {@code AnnotationDescriptor} for the first matching - * candidate, if any. - *
  3. 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. + *
  4. Search for the annotation on the given class and return a corresponding + * {@code AnnotationDescriptor} if found. + *
  5. Recursively search through all annotations that the given class declares. + *
  6. Recursively search through the superclass hierarchy of the given class. *
* + *

In this context, the term recursively 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. + * *

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 AnnotationDescriptor findAnnotationDescriptor(Class clazz, Class annotationType) { + return findAnnotationDescriptor(clazz, new HashSet(), annotationType); + } + + /** + * Perform the search algorithm for {@link #findAnnotationDescriptor(Class, Class)}, + * avoiding endless recursion by tracking which annotations have already been + * visited. + * + * @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 AnnotationDescriptor findAnnotationDescriptor(Class clazz, + Set visited, Class 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(clazz, composedAnnotation, annotation); + for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) { + if (visited.add(composedAnnotation)) { + AnnotationDescriptor descriptor = findAnnotationDescriptor(composedAnnotation.annotationType(), + visited, annotationType); + if (descriptor != null) { + return new AnnotationDescriptor(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}. * *

This method traverses the annotations and superclasses of the specified * {@code clazz} if no annotation can be found on the given class itself. - * + * *

This method explicitly handles class-level annotations which are not * declared as {@linkplain java.lang.annotation.Inherited inherited} as * well as meta-annotations. @@ -135,21 +155,20 @@ abstract class MetaAnnotationUtils { *

  • Search for a local declaration of one of the annotation types on * the given class and return a corresponding {@code UntypedAnnotationDescriptor} * if found. - *
  • Search through all annotations that the given class declares, - * returning an {@code UntypedAnnotationDescriptor} for the first matching - * candidate, if any. - *
  • 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. + *
  • Recursively search through all annotations that the given class declares. + *
  • Recursively search through the superclass hierarchy of the given class. * * + *

    In this context, the term recursively 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. + * *

    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... annotationTypes) { + return findAnnotationDescriptorForTypes(clazz, new HashSet(), annotationTypes); + } + + /** + * Perform the search algorithm for {@link #findAnnotationDescriptorForTypes(Class, Class...)}, + * avoiding endless recursion by tracking which annotations have already been + * visited. + * + * @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 visited, Class... 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 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 { - 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); } } diff --git a/spring-test/src/test/java/org/springframework/test/context/MetaAnnotationUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/MetaAnnotationUtilsTests.java index dbf1825a429..0fb5ad25332 100644 --- a/spring-test/src/test/java/org/springframework/test/context/MetaAnnotationUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/MetaAnnotationUtilsTests.java @@ -47,28 +47,48 @@ public class MetaAnnotationUtilsTests { private void assertAtComponentOnComposedAnnotation(Class startClass, Class rootDeclaringClass, String name, Class composedAnnotationType) { + assertAtComponentOnComposedAnnotation(rootDeclaringClass, rootDeclaringClass, composedAnnotationType, name, + composedAnnotationType); + } + + private void assertAtComponentOnComposedAnnotation(Class startClass, Class rootDeclaringClass, + Class declaringClass, String name, Class composedAnnotationType) { AnnotationDescriptor 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 composedAnnotationType) { + assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(startClass, startClass, name, + composedAnnotationType); + } + + private void assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(Class startClass, + Class rootDeclaringClass, String name, Class composedAnnotationType) { + assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(startClass, rootDeclaringClass, + composedAnnotationType, name, composedAnnotationType); } @SuppressWarnings("unchecked") private void assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(Class startClass, - Class declaringClass, String name, Class composedAnnotationType) { + Class rootDeclaringClass, Class declaringClass, String name, + Class composedAnnotationType) { Class 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 startClass = MetaMetaAnnotatedClass.class; + assertAtComponentOnComposedAnnotation(startClass, startClass, Meta2.class, "meta2", MetaMeta.class); + } + + /** + * @since 4.0.3 + */ + @Test + public void findAnnotationDescriptorOnMetaMetaMetaAnnotatedClass() { + Class 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 descriptor = findAnnotationDescriptor(InheritedAnnotationClass.class, + Component.class); + assertNull("Should not find @Component on InheritedAnnotationClass", descriptor); + } + + /** + * @since 4.0.3 + */ + @Test + public void findAnnotationDescriptorOnMetaCycleAnnotatedClassWithMissingTargetMetaAnnotation() { + AnnotationDescriptor 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 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 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 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 startClass = MetaMetaAnnotatedClass.class; + assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(startClass, startClass, Meta2.class, "meta2", + MetaMeta.class); + } + + /** + * @since 4.0.3 + */ + @Test + public void findAnnotationDescriptorForTypesOnMetaMetaMetaAnnotatedClass() { + Class 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 { } diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/meta/MetaContextHierarchyConfig.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/meta/MetaContextHierarchyConfig.java new file mode 100644 index 00000000000..70482902b55 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/meta/MetaContextHierarchyConfig.java @@ -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"; + } +} \ No newline at end of file diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/meta/MetaHierarchyLevelOneTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/meta/MetaHierarchyLevelOneTests.java new file mode 100644 index 00000000000..b212823da3f --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/meta/MetaHierarchyLevelOneTests.java @@ -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); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/meta/MetaHierarchyLevelTwoTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/meta/MetaHierarchyLevelTwoTests.java new file mode 100644 index 00000000000..38214d46d3c --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/meta/MetaHierarchyLevelTwoTests.java @@ -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()); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/meta/MetaMetaContextHierarchyConfig.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/meta/MetaMetaContextHierarchyConfig.java new file mode 100644 index 00000000000..4e7b012af37 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/meta/MetaMetaContextHierarchyConfig.java @@ -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 { + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaConfigDefaultsTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaConfigDefaultsTests.java index 8349aa846a7..0e42192f6e4 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaConfigDefaultsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaConfigDefaultsTests.java @@ -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 diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaMetaConfig.java b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaMetaConfig.java new file mode 100644 index 00000000000..d468addee0c --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaMetaConfig.java @@ -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 { + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaMetaConfigDefaultsTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaMetaConfigDefaultsTests.java new file mode 100644 index 00000000000..87277690e0e --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaMetaConfigDefaultsTests.java @@ -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); + } +}