Skip to content

Commit fc9650a

Browse files
committed
Finalize API for TestContextAnnotationUtils
1 parent 82fa3f3 commit fc9650a

File tree

8 files changed

+191
-271
lines changed

8 files changed

+191
-271
lines changed

spring-test/src/main/java/org/springframework/test/context/TestContextAnnotationUtils.java

Lines changed: 28 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424

2525
import org.springframework.core.SpringProperties;
2626
import org.springframework.core.annotation.AnnotatedElementUtils;
27-
import org.springframework.core.annotation.AnnotationAttributes;
2827
import org.springframework.core.annotation.AnnotationUtils;
2928
import org.springframework.core.annotation.MergedAnnotation;
3029
import org.springframework.core.annotation.MergedAnnotationCollectors;
@@ -112,7 +111,7 @@ private static <T extends Annotation> T findMergedAnnotation(Class<?> clazz, Cla
112111

113112
AnnotationDescriptor<T> descriptor =
114113
findAnnotationDescriptor(clazz, annotationType, searchEnclosingClass, new HashSet<>());
115-
return (descriptor != null ? descriptor.synthesizeAnnotation() : null);
114+
return (descriptor != null ? descriptor.getAnnotation() : null);
116115
}
117116

118117
/**
@@ -234,8 +233,7 @@ private static <T extends Annotation> AnnotationDescriptor<T> findAnnotationDesc
234233
if (!AnnotationUtils.isInJavaLangAnnotationPackage(composedType.getName()) && visited.add(composedAnn)) {
235234
descriptor = findAnnotationDescriptor(composedType, annotationType, searchEnclosingClass, visited);
236235
if (descriptor != null) {
237-
return new AnnotationDescriptor<>(
238-
clazz, descriptor.getDeclaringClass(), composedAnn, descriptor.getAnnotation());
236+
return new AnnotationDescriptor<>(clazz, descriptor.getDeclaringClass(), descriptor.getAnnotation());
239237
}
240238
}
241239
}
@@ -244,8 +242,7 @@ private static <T extends Annotation> AnnotationDescriptor<T> findAnnotationDesc
244242
for (Class<?> ifc : clazz.getInterfaces()) {
245243
descriptor = findAnnotationDescriptor(ifc, annotationType, searchEnclosingClass, visited);
246244
if (descriptor != null) {
247-
return new AnnotationDescriptor<>(clazz, descriptor.getDeclaringClass(),
248-
descriptor.getComposedAnnotation(), descriptor.getAnnotation());
245+
return new AnnotationDescriptor<>(clazz, descriptor.getDeclaringClass(), descriptor.getAnnotation());
249246
}
250247
}
251248

@@ -340,7 +337,7 @@ private static UntypedAnnotationDescriptor findAnnotationDescriptorForTypes(@Nul
340337
composedAnnotation.annotationType(), annotationTypes, visited);
341338
if (descriptor != null) {
342339
return new UntypedAnnotationDescriptor(clazz, descriptor.getDeclaringClass(),
343-
composedAnnotation, descriptor.getAnnotation(), annotationTypes);
340+
descriptor.getAnnotation(), annotationTypes);
344341
}
345342
}
346343
}
@@ -350,7 +347,7 @@ private static UntypedAnnotationDescriptor findAnnotationDescriptorForTypes(@Nul
350347
UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(ifc, annotationTypes, visited);
351348
if (descriptor != null) {
352349
return new UntypedAnnotationDescriptor(clazz, descriptor.getDeclaringClass(),
353-
descriptor.getComposedAnnotation(), descriptor.getAnnotation(), annotationTypes);
350+
descriptor.getAnnotation(), annotationTypes);
354351
}
355352
}
356353

@@ -439,19 +436,16 @@ private static void assertNonEmptyAnnotationTypeArray(Class<?>[] annotationTypes
439436
/**
440437
* Descriptor for an {@link Annotation}, including the {@linkplain
441438
* #getDeclaringClass() class} on which the annotation is <em>declared</em>
442-
* as well as the actual {@linkplain #getAnnotation() annotation} instance.
443-
* <p>If the annotation is used as a meta-annotation, the descriptor also includes
444-
* the {@linkplain #getComposedAnnotation() composed annotation} on which the
445-
* annotation is present. In such cases, the <em>root declaring class</em> is
446-
* not directly annotated with the annotation but rather indirectly via the
447-
* composed annotation.
439+
* as well as the {@linkplain #getAnnotation() merged annotation} instance.
440+
* <p>If the annotation is used as a meta-annotation, the <em>root declaring
441+
* class</em> is not directly annotated with the annotation but rather
442+
* indirectly via a composed annotation.
448443
* <p>Given the following example, if we are searching for the {@code @Transactional}
449444
* annotation <em>on</em> the {@code TransactionalTests} class, then the
450445
* properties of the {@code AnnotationDescriptor} would be as follows.
451446
* <ul>
452447
* <li>rootDeclaringClass: {@code TransactionalTests} class object</li>
453448
* <li>declaringClass: {@code TransactionalTests} class object</li>
454-
* <li>composedAnnotation: {@code null}</li>
455449
* <li>annotation: instance of the {@code Transactional} annotation</li>
456450
* </ul>
457451
* <p><pre style="code">
@@ -465,7 +459,6 @@ private static void assertNonEmptyAnnotationTypeArray(Class<?>[] annotationTypes
465459
* <ul>
466460
* <li>rootDeclaringClass: {@code UserRepositoryTests} class object</li>
467461
* <li>declaringClass: {@code RepositoryTests} class object</li>
468-
* <li>composedAnnotation: instance of the {@code RepositoryTests} annotation</li>
469462
* <li>annotation: instance of the {@code Transactional} annotation</li>
470463
* </ul>
471464
* <p><pre style="code">
@@ -486,31 +479,23 @@ public static class AnnotationDescriptor<T extends Annotation> {
486479

487480
private final Class<?> declaringClass;
488481

489-
@Nullable
490-
private final Annotation composedAnnotation;
491-
492482
private final T annotation;
493483

494-
private final AnnotationAttributes annotationAttributes;
495-
496484
AnnotationDescriptor(Class<?> rootDeclaringClass, T annotation) {
497-
this(rootDeclaringClass, rootDeclaringClass, null, annotation);
485+
this(rootDeclaringClass, rootDeclaringClass, annotation);
498486
}
499487

500-
AnnotationDescriptor(Class<?> rootDeclaringClass, Class<?> declaringClass,
501-
@Nullable Annotation composedAnnotation, T annotation) {
502-
488+
@SuppressWarnings("unchecked")
489+
AnnotationDescriptor(Class<?> rootDeclaringClass, Class<?> declaringClass, T annotation) {
503490
Assert.notNull(rootDeclaringClass, "'rootDeclaringClass' must not be null");
504491
Assert.notNull(declaringClass, "'declaringClass' must not be null");
505492
Assert.notNull(annotation, "Annotation must not be null");
506493
this.rootDeclaringClass = rootDeclaringClass;
507494
this.declaringClass = declaringClass;
508-
this.composedAnnotation = composedAnnotation;
509-
this.annotation = annotation;
510-
AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
511-
rootDeclaringClass, annotation.annotationType().getName(), false, false);
512-
Assert.state(attributes != null, "No annotation attributes");
513-
this.annotationAttributes = attributes;
495+
this.annotation = (T) AnnotatedElementUtils.findMergedAnnotation(
496+
rootDeclaringClass, annotation.annotationType());
497+
Assert.state(this.annotation != null,
498+
() -> "Failed to find merged annotation for " + annotation);
514499
}
515500

516501
public Class<?> getRootDeclaringClass() {
@@ -521,47 +506,22 @@ public Class<?> getDeclaringClass() {
521506
return this.declaringClass;
522507
}
523508

524-
T getAnnotation() {
525-
return this.annotation;
526-
}
527-
528509
/**
529-
* Synthesize the merged {@link #getAnnotationAttributes AnnotationAttributes}
530-
* in this descriptor back into an annotation of the target
531-
* {@linkplain #getAnnotationType annotation type}.
532-
* @see #getAnnotationAttributes()
533-
* @see #getAnnotationType()
534-
* @see AnnotationUtils#synthesizeAnnotation(java.util.Map, Class, java.lang.reflect.AnnotatedElement)
510+
* Get the merged annotation for this descriptor.
535511
*/
536-
public T synthesizeAnnotation() {
537-
return AnnotationUtils.synthesizeAnnotation(
538-
getAnnotationAttributes(), getAnnotationType(), getRootDeclaringClass());
512+
public T getAnnotation() {
513+
return this.annotation;
539514
}
540515

541516
@SuppressWarnings("unchecked")
542517
Class<T> getAnnotationType() {
543518
return (Class<T>) this.annotation.annotationType();
544519
}
545520

546-
AnnotationAttributes getAnnotationAttributes() {
547-
return this.annotationAttributes;
548-
}
549-
550-
@Nullable
551-
Annotation getComposedAnnotation() {
552-
return this.composedAnnotation;
553-
}
554-
555-
@Nullable
556-
Class<? extends Annotation> getComposedAnnotationType() {
557-
return (this.composedAnnotation != null ? this.composedAnnotation.annotationType() : null);
558-
}
559-
560521
/**
561-
* Find the next {@link AnnotationDescriptor} for the specified
562-
* {@linkplain #getAnnotationType() annotation type} in the hierarchy
563-
* above the {@linkplain #getRootDeclaringClass() root declaring class}
564-
* of this descriptor.
522+
* Find the next {@link AnnotationDescriptor} for the specified annotation
523+
* type in the hierarchy above the {@linkplain #getRootDeclaringClass()
524+
* root declaring class} of this descriptor.
565525
* <p>If a corresponding annotation is found in the superclass hierarchy
566526
* of the root declaring class, that will be returned. Otherwise, an
567527
* attempt will be made to find a corresponding annotation in the
@@ -583,10 +543,9 @@ public AnnotationDescriptor<T> next() {
583543
}
584544

585545
/**
586-
* Find <strong>all</strong> annotations of the specified
587-
* {@linkplain #getAnnotationType() annotation type} that are present or
588-
* meta-present on the {@linkplain #getRootDeclaringClass() root declaring
589-
* class} of this descriptor.
546+
* Find <strong>all</strong> annotations of the specified annotation type
547+
* that are present or meta-present on the {@linkplain #getRootDeclaringClass()
548+
* root declaring class} of this descriptor.
590549
* @return the set of all merged, synthesized {@code Annotations} found,
591550
* or an empty set if none were found
592551
*/
@@ -609,7 +568,6 @@ public String toString() {
609568
return new ToStringCreator(this)
610569
.append("rootDeclaringClass", this.rootDeclaringClass.getName())
611570
.append("declaringClass", this.declaringClass.getName())
612-
.append("composedAnnotation", this.composedAnnotation)
613571
.append("annotation", this.annotation)
614572
.toString();
615573
}
@@ -628,14 +586,13 @@ public static class UntypedAnnotationDescriptor extends AnnotationDescriptor<Ann
628586
UntypedAnnotationDescriptor(Class<?> rootDeclaringClass, Annotation annotation,
629587
Class<? extends Annotation>[] annotationTypes) {
630588

631-
this(rootDeclaringClass, rootDeclaringClass, null, annotation, annotationTypes);
589+
this(rootDeclaringClass, rootDeclaringClass, annotation, annotationTypes);
632590
}
633591

634592
UntypedAnnotationDescriptor(Class<?> rootDeclaringClass, Class<?> declaringClass,
635-
@Nullable Annotation composedAnnotation, Annotation annotation,
636-
Class<? extends Annotation>[] annotationTypes) {
593+
Annotation annotation, Class<? extends Annotation>[] annotationTypes) {
637594

638-
super(rootDeclaringClass, declaringClass, composedAnnotation, annotation);
595+
super(rootDeclaringClass, declaringClass, annotation);
639596
this.annotationTypes = annotationTypes;
640597
}
641598

spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ public final List<TestExecutionListener> getTestExecutionListeners() {
131131
// Traverse the class hierarchy...
132132
while (descriptor != null) {
133133
Class<?> declaringClass = descriptor.getDeclaringClass();
134-
TestExecutionListeners testExecutionListeners = descriptor.synthesizeAnnotation();
134+
TestExecutionListeners testExecutionListeners = descriptor.getAnnotation();
135135
if (logger.isTraceEnabled()) {
136136
logger.trace(String.format("Retrieved @TestExecutionListeners [%s] for declaring class [%s].",
137137
testExecutionListeners, declaringClass.getName()));

spring-test/src/main/java/org/springframework/test/context/support/ActiveProfilesUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ static String[] resolveActiveProfiles(Class<?> testClass) {
8181

8282
while (descriptor != null) {
8383
Class<?> rootDeclaringClass = descriptor.getRootDeclaringClass();
84-
ActiveProfiles annotation = descriptor.synthesizeAnnotation();
84+
ActiveProfiles annotation = descriptor.getAnnotation();
8585

8686
if (logger.isTraceEnabled()) {
8787
logger.trace(String.format("Retrieved @ActiveProfiles [%s] for declaring class [%s]",

spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ static List<List<ContextConfigurationAttributes>> resolveContextHierarchyAttribu
126126
List<ContextConfigurationAttributes> configAttributesList = new ArrayList<>();
127127

128128
if (contextConfigDeclaredLocally) {
129-
ContextConfiguration contextConfiguration = (ContextConfiguration) desc.synthesizeAnnotation();
129+
ContextConfiguration contextConfiguration = (ContextConfiguration) desc.getAnnotation();
130130
convertContextConfigToConfigAttributesAndAddToList(
131131
contextConfiguration, rootDeclaringClass, configAttributesList);
132132
}
@@ -245,7 +245,7 @@ static List<ContextConfigurationAttributes> resolveContextConfigurationAttribute
245245
ContextConfiguration previousAnnotation = null;
246246
Class<?> previousDeclaringClass = null;
247247
while (descriptor != null) {
248-
ContextConfiguration currentAnnotation = descriptor.synthesizeAnnotation();
248+
ContextConfiguration currentAnnotation = descriptor.getAnnotation();
249249
// Don't ignore duplicate @ContextConfiguration declaration without resources,
250250
// because the ContextLoader will likely detect default resources specific to the
251251
// annotated class.

spring-test/src/main/java/org/springframework/test/context/support/DefaultActiveProfilesResolver.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public String[] resolve(Class<?> testClass) {
6767
return EMPTY_STRING_ARRAY;
6868
}
6969
else {
70-
ActiveProfiles annotation = descriptor.synthesizeAnnotation();
70+
ActiveProfiles annotation = descriptor.getAnnotation();
7171
if (logger.isTraceEnabled()) {
7272
logger.trace(String.format("Retrieved @ActiveProfiles [%s] for declaring class [%s].", annotation,
7373
descriptor.getDeclaringClass().getName()));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*
2+
* Copyright 2002-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.context;
18+
19+
import java.lang.annotation.Retention;
20+
import java.lang.annotation.RetentionPolicy;
21+
22+
import org.junit.jupiter.api.Test;
23+
24+
import org.springframework.core.annotation.AliasFor;
25+
import org.springframework.test.context.TestContextAnnotationUtils.AnnotationDescriptor;
26+
27+
import static org.assertj.core.api.Assertions.assertThat;
28+
import static org.springframework.test.context.TestContextAnnotationUtils.findAnnotationDescriptor;
29+
30+
/**
31+
* Unit tests for {@link TestContextAnnotationUtils} that verify support for
32+
* overridden meta-annotation attributes.
33+
*
34+
* <p>See <a href="https://jira.spring.io/browse/SPR-10181">SPR-10181</a>.
35+
*
36+
* @author Sam Brannen
37+
* @since 5.3, though originally since 4.0 for the deprecated
38+
* {@link org.springframework.test.util.MetaAnnotationUtils} support
39+
* @see TestContextAnnotationUtilsTests
40+
*/
41+
class OverriddenMetaAnnotationAttributesTestContextAnnotationUtilsTests {
42+
43+
@Test
44+
void contextConfigurationValue() {
45+
Class<?> rootDeclaringClass = MetaValueConfigTestCase.class;
46+
AnnotationDescriptor<ContextConfiguration> descriptor = findAnnotationDescriptor(rootDeclaringClass,
47+
ContextConfiguration.class);
48+
assertThat(descriptor).isNotNull();
49+
assertThat(descriptor.getRootDeclaringClass()).isEqualTo(rootDeclaringClass);
50+
assertThat(descriptor.getDeclaringClass()).isEqualTo(MetaValueConfig.class);
51+
assertThat(descriptor.getAnnotationType()).isEqualTo(ContextConfiguration.class);
52+
assertThat(descriptor.getAnnotation().value()).containsExactly("foo.xml");
53+
assertThat(descriptor.getAnnotation().locations()).containsExactly("foo.xml");
54+
}
55+
56+
@Test
57+
void overriddenContextConfigurationValue() {
58+
Class<?> rootDeclaringClass = OverriddenMetaValueConfigTestCase.class;
59+
AnnotationDescriptor<ContextConfiguration> descriptor = findAnnotationDescriptor(rootDeclaringClass,
60+
ContextConfiguration.class);
61+
assertThat(descriptor).isNotNull();
62+
assertThat(descriptor.getRootDeclaringClass()).isEqualTo(rootDeclaringClass);
63+
assertThat(descriptor.getDeclaringClass()).isEqualTo(MetaValueConfig.class);
64+
assertThat(descriptor.getAnnotationType()).isEqualTo(ContextConfiguration.class);
65+
assertThat(descriptor.getAnnotation().value()).containsExactly("bar.xml");
66+
assertThat(descriptor.getAnnotation().locations()).containsExactly("bar.xml");
67+
}
68+
69+
@Test
70+
void contextConfigurationLocationsAndInheritLocations() {
71+
Class<?> rootDeclaringClass = MetaLocationsConfigTestCase.class;
72+
AnnotationDescriptor<ContextConfiguration> descriptor = findAnnotationDescriptor(rootDeclaringClass,
73+
ContextConfiguration.class);
74+
assertThat(descriptor).isNotNull();
75+
assertThat(descriptor.getRootDeclaringClass()).isEqualTo(rootDeclaringClass);
76+
assertThat(descriptor.getDeclaringClass()).isEqualTo(MetaLocationsConfig.class);
77+
assertThat(descriptor.getAnnotationType()).isEqualTo(ContextConfiguration.class);
78+
assertThat(descriptor.getAnnotation().value()).isEmpty();
79+
assertThat(descriptor.getAnnotation().locations()).isEmpty();
80+
assertThat(descriptor.getAnnotation().inheritLocations()).isTrue();
81+
}
82+
83+
@Test
84+
void overriddenContextConfigurationLocationsAndInheritLocations() {
85+
Class<?> rootDeclaringClass = OverriddenMetaLocationsConfigTestCase.class;
86+
AnnotationDescriptor<ContextConfiguration> descriptor = findAnnotationDescriptor(rootDeclaringClass,
87+
ContextConfiguration.class);
88+
assertThat(descriptor).isNotNull();
89+
assertThat(descriptor.getRootDeclaringClass()).isEqualTo(rootDeclaringClass);
90+
assertThat(descriptor.getDeclaringClass()).isEqualTo(MetaLocationsConfig.class);
91+
assertThat(descriptor.getAnnotationType()).isEqualTo(ContextConfiguration.class);
92+
assertThat(descriptor.getAnnotation().value()).containsExactly("bar.xml");
93+
assertThat(descriptor.getAnnotation().locations()).containsExactly("bar.xml");
94+
assertThat(descriptor.getAnnotation().inheritLocations()).isTrue();
95+
}
96+
97+
98+
// -------------------------------------------------------------------------
99+
100+
@ContextConfiguration
101+
@Retention(RetentionPolicy.RUNTIME)
102+
@interface MetaValueConfig {
103+
104+
@AliasFor(annotation = ContextConfiguration.class)
105+
String[] value() default "foo.xml";
106+
}
107+
108+
@MetaValueConfig
109+
static class MetaValueConfigTestCase {
110+
}
111+
112+
@MetaValueConfig("bar.xml")
113+
static class OverriddenMetaValueConfigTestCase {
114+
}
115+
116+
@ContextConfiguration(locations = "foo.xml", inheritLocations = false)
117+
@Retention(RetentionPolicy.RUNTIME)
118+
@interface MetaLocationsConfig {
119+
120+
String[] locations() default {};
121+
122+
boolean inheritLocations();
123+
}
124+
125+
@MetaLocationsConfig(inheritLocations = true)
126+
static class MetaLocationsConfigTestCase {
127+
}
128+
129+
@MetaLocationsConfig(locations = "bar.xml", inheritLocations = true)
130+
static class OverriddenMetaLocationsConfigTestCase {
131+
}
132+
133+
}

0 commit comments

Comments
 (0)