Skip to content

Commit 2bd5a53

Browse files
committed
Merge pull request #393 from sbrannen/SPR-7827
* SPR-7827: Provide meta-annotation support in the TCF
2 parents 56dfcd1 + 5e7021f commit 2bd5a53

28 files changed

+1852
-205
lines changed

spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -285,9 +285,8 @@ public static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> a
285285
* <p>The standard {@link Class} API does not provide a mechanism for determining which class
286286
* in an inheritance hierarchy actually declares an {@link Annotation}, so we need to handle
287287
* this explicitly.
288-
* @param annotationType the Class object corresponding to the annotation type
289-
* @param clazz the Class object corresponding to the class on which to check for the annotation,
290-
* or {@code null}
288+
* @param annotationType the annotation class to look for, both locally and as a meta-annotation
289+
* @param clazz the class on which to check for the annotation, or {@code null}
291290
* @return the first {@link Class} in the inheritance hierarchy of the specified {@code clazz}
292291
* which declares an annotation for the specified {@code annotationType}, or {@code null}
293292
* if not found
@@ -301,8 +300,24 @@ public static Class<?> findAnnotationDeclaringClass(Class<? extends Annotation>
301300
if (clazz == null || clazz.equals(Object.class)) {
302301
return null;
303302
}
304-
return (isAnnotationDeclaredLocally(annotationType, clazz)) ? clazz : findAnnotationDeclaringClass(
305-
annotationType, clazz.getSuperclass());
303+
304+
// Declared locally?
305+
if (isAnnotationDeclaredLocally(annotationType, clazz)) {
306+
return clazz;
307+
}
308+
309+
// Declared on a stereotype annotation (i.e., as a meta-annotation)?
310+
if (!Annotation.class.isAssignableFrom(clazz)) {
311+
for (Annotation stereotype : clazz.getAnnotations()) {
312+
Class<?> declaringClass = findAnnotationDeclaringClass(annotationType, stereotype.annotationType());
313+
if (declaringClass != null) {
314+
return declaringClass;
315+
}
316+
}
317+
}
318+
319+
// Declared on a superclass?
320+
return findAnnotationDeclaringClass(annotationType, clazz.getSuperclass());
306321
}
307322

308323
/**

spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,18 @@ public void testFindMethodAnnotationOnBridgeMethod() throws Exception {
9696
// assertNotNull(o);
9797
// }
9898

99+
@Test
100+
public void findAnnotationPrefersInteracesOverLocalMetaAnnotations() {
101+
Component component = AnnotationUtils.findAnnotation(
102+
ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, Component.class);
103+
104+
// By inspecting ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface, one
105+
// might expect that "meta2" should be found; however, with the current
106+
// implementation "meta1" will be found.
107+
assertNotNull(component);
108+
assertEquals("meta1", component.value());
109+
}
110+
99111
@Test
100112
public void testFindAnnotationDeclaringClass() throws Exception {
101113
// no class-level annotation
@@ -133,7 +145,7 @@ public void findAnnotationDeclaringClassForTypesWithSingleCandidateType() {
133145
assertEquals(InheritedAnnotationInterface.class,
134146
findAnnotationDeclaringClassForTypes(transactionalCandidateList, InheritedAnnotationInterface.class));
135147
assertNull(findAnnotationDeclaringClassForTypes(transactionalCandidateList,
136-
SubInheritedAnnotationInterface.class));
148+
SubInheritedAnnotationInterface.class));
137149
assertEquals(InheritedAnnotationClass.class,
138150
findAnnotationDeclaringClassForTypes(transactionalCandidateList, InheritedAnnotationClass.class));
139151
assertEquals(InheritedAnnotationClass.class,
@@ -288,19 +300,23 @@ public void testGetRepeatableFromMethod() throws Exception {
288300

289301

290302
@Component(value = "meta1")
303+
@Order
291304
@Retention(RetentionPolicy.RUNTIME)
292305
@interface Meta1 {
293306
}
294307

295308
@Component(value = "meta2")
309+
@Transactional
296310
@Retention(RetentionPolicy.RUNTIME)
297311
@interface Meta2 {
298312
}
299313

300314
@Meta1
301-
@Component(value = "local")
315+
static interface InterfaceWithMetaAnnotation {
316+
}
317+
302318
@Meta2
303-
static class HasLocalAndMetaComponentAnnotation {
319+
static class ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface implements InterfaceWithMetaAnnotation {
304320
}
305321

306322
public static interface AnnotatedInterface {

spring-test/src/main/java/org/springframework/test/annotation/ProfileValueUtils.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2013 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,6 +25,8 @@
2525
import org.springframework.util.ObjectUtils;
2626
import org.springframework.util.StringUtils;
2727

28+
import static org.springframework.core.annotation.AnnotationUtils.*;
29+
2830
/**
2931
* General utility methods for working with <em>profile values</em>.
3032
*
@@ -63,7 +65,7 @@ public static ProfileValueSource retrieveProfileValueSource(Class<?> testClass)
6365
Assert.notNull(testClass, "testClass must not be null");
6466

6567
Class<ProfileValueSourceConfiguration> annotationType = ProfileValueSourceConfiguration.class;
66-
ProfileValueSourceConfiguration config = testClass.getAnnotation(annotationType);
68+
ProfileValueSourceConfiguration config = findAnnotation(testClass, annotationType);
6769
if (logger.isDebugEnabled()) {
6870
logger.debug("Retrieved @ProfileValueSourceConfiguration [" + config + "] for test class ["
6971
+ testClass.getName() + "]");
@@ -114,7 +116,7 @@ public static ProfileValueSource retrieveProfileValueSource(Class<?> testClass)
114116
* environment
115117
*/
116118
public static boolean isTestEnabledInThisEnvironment(Class<?> testClass) {
117-
IfProfileValue ifProfileValue = testClass.getAnnotation(IfProfileValue.class);
119+
IfProfileValue ifProfileValue = findAnnotation(testClass, IfProfileValue.class);
118120
return isTestEnabledInThisEnvironment(retrieveProfileValueSource(testClass), ifProfileValue);
119121
}
120122

@@ -157,11 +159,11 @@ public static boolean isTestEnabledInThisEnvironment(Method testMethod, Class<?>
157159
public static boolean isTestEnabledInThisEnvironment(ProfileValueSource profileValueSource, Method testMethod,
158160
Class<?> testClass) {
159161

160-
IfProfileValue ifProfileValue = testClass.getAnnotation(IfProfileValue.class);
162+
IfProfileValue ifProfileValue = findAnnotation(testClass, IfProfileValue.class);
161163
boolean classLevelEnabled = isTestEnabledInThisEnvironment(profileValueSource, ifProfileValue);
162164

163165
if (classLevelEnabled) {
164-
ifProfileValue = testMethod.getAnnotation(IfProfileValue.class);
166+
ifProfileValue = findAnnotation(testMethod, IfProfileValue.class);
165167
return isTestEnabledInThisEnvironment(profileValueSource, ifProfileValue);
166168
}
167169

spring-test/src/main/java/org/springframework/test/annotation/Repeat.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2013 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,25 +17,27 @@
1717
package org.springframework.test.annotation;
1818

1919
import java.lang.annotation.Documented;
20-
import java.lang.annotation.ElementType;
2120
import java.lang.annotation.Retention;
22-
import java.lang.annotation.RetentionPolicy;
2321
import java.lang.annotation.Target;
2422

23+
import static java.lang.annotation.ElementType.*;
24+
import static java.lang.annotation.RetentionPolicy.*;
25+
2526
/**
2627
* Test annotation to indicate that a test method should be invoked repeatedly.
27-
* <p />
28-
* Note that the scope of execution to be repeated includes execution of the
28+
*
29+
* <p>Note that the scope of execution to be repeated includes execution of the
2930
* test method itself as well as any <em>set up</em> or <em>tear down</em> of
3031
* the test fixture.
3132
*
3233
* @author Rod Johnson
3334
* @author Sam Brannen
3435
* @since 2.0
36+
* @see Timed
3537
*/
3638
@Documented
37-
@Retention(RetentionPolicy.RUNTIME)
38-
@Target(ElementType.METHOD)
39+
@Retention(RUNTIME)
40+
@Target({ METHOD, ANNOTATION_TYPE })
3941
public @interface Repeat {
4042

4143
int value() default 1;

spring-test/src/main/java/org/springframework/test/annotation/Timed.java

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2013 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,34 +17,31 @@
1717
package org.springframework.test.annotation;
1818

1919
import java.lang.annotation.Documented;
20-
import java.lang.annotation.ElementType;
2120
import java.lang.annotation.Retention;
22-
import java.lang.annotation.RetentionPolicy;
2321
import java.lang.annotation.Target;
2422

23+
import static java.lang.annotation.ElementType.*;
24+
import static java.lang.annotation.RetentionPolicy.*;
25+
2526
/**
26-
* <p>
2727
* Test-specific annotation to indicate that a test method has to finish
2828
* execution in a {@link #millis() specified time period}.
29-
* </p>
30-
* <p>
31-
* If the text execution takes longer than the specified time period, then the
32-
* test is to be considered failed.
33-
* </p>
34-
* <p>
35-
* Note that the time period includes execution of the test method itself, any
36-
* {@link Repeat repetitions} of the test, and any <em>set up</em> or
29+
*
30+
* <p>If the text execution takes longer than the specified time period, then
31+
* the test is to be considered failed.
32+
*
33+
* <p>Note that the time period includes execution of the test method itself,
34+
* any {@link Repeat repetitions} of the test, and any <em>set up</em> or
3735
* <em>tear down</em> of the test fixture.
38-
* </p>
3936
*
4037
* @author Rod Johnson
4138
* @author Sam Brannen
4239
* @since 2.0
4340
* @see Repeat
4441
*/
4542
@Documented
46-
@Retention(RetentionPolicy.RUNTIME)
47-
@Target(ElementType.METHOD)
43+
@Retention(RUNTIME)
44+
@Target({ METHOD, ANNOTATION_TYPE })
4845
public @interface Timed {
4946

5047
/**

0 commit comments

Comments
 (0)