Skip to content

Support meta-annotation attr overrides in the TCF #421

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 12 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
Expand Down Expand Up @@ -68,21 +68,29 @@ public class ContextConfigurationAttributes {
* @throws IllegalStateException if both the locations and value attributes have been declared
*/
private static String[] resolveLocations(Class<?> declaringClass, ContextConfiguration contextConfiguration) {
Assert.notNull(declaringClass, "declaringClass must not be null");
return resolveLocations(declaringClass, contextConfiguration.locations(), contextConfiguration.value());
}

String[] locations = contextConfiguration.locations();
String[] valueLocations = contextConfiguration.value();
/**
* Resolve resource locations from the supplied {@code locations} and
* {@code value} arrays, which correspond to attributes of the same names in
* the {@link ContextConfiguration} annotation.
*
* @throws IllegalStateException if both the locations and value attributes have been declared
*/
private static String[] resolveLocations(Class<?> declaringClass, String[] locations, String[] value) {
Assert.notNull(declaringClass, "declaringClass must not be null");

if (!ObjectUtils.isEmpty(valueLocations) && !ObjectUtils.isEmpty(locations)) {
if (!ObjectUtils.isEmpty(value) && !ObjectUtils.isEmpty(locations)) {
String msg = String.format("Test class [%s] has been configured with @ContextConfiguration's 'value' %s "
+ "and 'locations' %s attributes. Only one declaration of resource "
+ "locations is permitted per @ContextConfiguration annotation.", declaringClass.getName(),
ObjectUtils.nullSafeToString(valueLocations), ObjectUtils.nullSafeToString(locations));
ObjectUtils.nullSafeToString(value), ObjectUtils.nullSafeToString(locations));
logger.error(msg);
throw new IllegalStateException(msg);
}
else if (!ObjectUtils.isEmpty(valueLocations)) {
locations = valueLocations;
else if (!ObjectUtils.isEmpty(value)) {
locations = value;
}

return locations;
Expand All @@ -101,6 +109,25 @@ public ContextConfigurationAttributes(Class<?> declaringClass, ContextConfigurat
contextConfiguration.inheritInitializers(), contextConfiguration.name(), contextConfiguration.loader());
}

/**
* Construct a new {@link ContextConfigurationAttributes} instance for the
* supplied {@link ContextConfiguration @ContextConfiguration} annotation and
* the {@linkplain Class test class} that declared it.
* @param declaringClass the test class that declared {@code @ContextConfiguration}
* @param annAttrs the annotation attributes from which to retrieve the attributes
*/
@SuppressWarnings("unchecked")
public ContextConfigurationAttributes(Class<?> declaringClass, AnnotationAttributes annAttrs) {
this(
declaringClass,
resolveLocations(declaringClass, annAttrs.getStringArray("locations"), annAttrs.getStringArray("value")),
annAttrs.getClassArray("classes"),
annAttrs.getBoolean("inheritLocations"),
(Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>[]) annAttrs.getClassArray("initializers"),
annAttrs.getBoolean("inheritInitializers"), annAttrs.getString("name"),
(Class<? extends ContextLoader>) annAttrs.getClass("loader"));
}

/**
* Construct a new {@link ContextConfigurationAttributes} instance for the
* {@linkplain Class test class} that declared the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.test.context.MetaAnnotationUtils.AnnotationDescriptor;
import org.springframework.test.context.MetaAnnotationUtils.UntypedAnnotationDescriptor;
Expand Down Expand Up @@ -192,9 +193,9 @@ static Class<? extends ContextLoader> resolveContextLoaderClass(Class<?> testCla
}

/**
* Convenience method for creating a {@link ContextConfigurationAttributes} instance
* from the supplied {@link ContextConfiguration} and declaring class and then adding
* the attributes to the supplied list.
* Convenience method for creating a {@link ContextConfigurationAttributes}
* instance from the supplied {@link ContextConfiguration} annotation and
* declaring class and then adding the attributes to the supplied list.
*/
private static void convertContextConfigToConfigAttributesAndAddToList(ContextConfiguration contextConfiguration,
Class<?> declaringClass, final List<ContextConfigurationAttributes> attributesList) {
Expand All @@ -211,6 +212,27 @@ private static void convertContextConfigToConfigAttributesAndAddToList(ContextCo
attributesList.add(attributes);
}

/**
* Convenience method for creating a {@link ContextConfigurationAttributes}
* instance from the supplied {@link AnnotationAttributes} and declaring
* class and then adding the attributes to the supplied list.
*
* @since 4.0
*/
private static void convertAnnotationAttributesToConfigAttributesAndAddToList(AnnotationAttributes annAttrs,
Class<?> declaringClass, final List<ContextConfigurationAttributes> attributesList) {
if (logger.isTraceEnabled()) {
logger.trace(String.format("Retrieved @ContextConfiguration attributes [%s] for declaring class [%s].",
annAttrs, declaringClass.getName()));
}

ContextConfigurationAttributes attributes = new ContextConfigurationAttributes(declaringClass, annAttrs);
if (logger.isTraceEnabled()) {
logger.trace("Resolved context configuration attributes: " + attributes);
}
attributesList.add(attributes);
}

/**
* Resolve the list of lists of {@linkplain ContextConfigurationAttributes context
* configuration attributes} for the supplied {@linkplain Class test class} and its
Expand Down Expand Up @@ -243,6 +265,8 @@ private static void convertContextConfigToConfigAttributesAndAddToList(ContextCo
* <em>present</em> on the supplied class; or if a given class in the class hierarchy
* declares both {@code @ContextConfiguration} and {@code @ContextHierarchy} as
* top-level annotations.
* @throws IllegalStateException if no class in the class hierarchy declares
* {@code @ContextHierarchy}.
*
* @since 3.2.2
* @see #buildContextHierarchyMap(Class)
Expand All @@ -251,6 +275,7 @@ private static void convertContextConfigToConfigAttributesAndAddToList(ContextCo
@SuppressWarnings("unchecked")
static List<List<ContextConfigurationAttributes>> resolveContextHierarchyAttributes(Class<?> testClass) {
Assert.notNull(testClass, "Class must not be null");
Assert.state(findAnnotation(testClass, ContextHierarchy.class) != null, "@ContextHierarchy must be present");

final Class<ContextConfiguration> contextConfigType = ContextConfiguration.class;
final Class<ContextHierarchy> contextHierarchyType = ContextHierarchy.class;
Expand All @@ -263,27 +288,25 @@ static List<List<ContextConfigurationAttributes>> resolveContextHierarchyAttribu
contextConfigType.getName(), contextHierarchyType.getName(), testClass.getName()));

while (descriptor != null) {
Class<?> rootDeclaringClass = descriptor.getDeclaringClass();
Class<?> declaringClass = (descriptor.getStereotype() != null) ? descriptor.getStereotypeType()
: rootDeclaringClass;
Class<?> rootDeclaringClass = descriptor.getRootDeclaringClass();
Class<?> declaringClass = descriptor.getDeclaringClass();

boolean contextConfigDeclaredLocally = isAnnotationDeclaredLocally(contextConfigType, declaringClass);
boolean contextHierarchyDeclaredLocally = isAnnotationDeclaredLocally(contextHierarchyType, declaringClass);

if (contextConfigDeclaredLocally && contextHierarchyDeclaredLocally) {
String msg = String.format("Test class [%s] has been configured with both @ContextConfiguration "
String msg = String.format("Class [%s] has been configured with both @ContextConfiguration "
+ "and @ContextHierarchy. Only one of these annotations may be declared on a test class "
+ "or custom stereotype annotation.", rootDeclaringClass.getName());
+ "or custom stereotype annotation.", declaringClass.getName());
logger.error(msg);
throw new IllegalStateException(msg);
}

final List<ContextConfigurationAttributes> configAttributesList = new ArrayList<ContextConfigurationAttributes>();

if (contextConfigDeclaredLocally) {
ContextConfiguration contextConfiguration = getAnnotation(declaringClass, contextConfigType);
convertContextConfigToConfigAttributesAndAddToList(contextConfiguration, declaringClass,
configAttributesList);
convertAnnotationAttributesToConfigAttributesAndAddToList(descriptor.getAnnotationAttributes(),
declaringClass, configAttributesList);
}
else if (contextHierarchyDeclaredLocally) {
ContextHierarchy contextHierarchy = getAnnotation(declaringClass, contextHierarchyType);
Expand All @@ -293,7 +316,7 @@ else if (contextHierarchyDeclaredLocally) {
}
}
else {
// This should theoretically actually never happen...
// This should theoretically never happen...
String msg = String.format("Test class [%s] has been configured with neither @ContextConfiguration "
+ "nor @ContextHierarchy as a class-level annotation.", rootDeclaringClass.getName());
logger.error(msg);
Expand Down Expand Up @@ -405,13 +428,9 @@ static List<ContextConfigurationAttributes> resolveContextConfigurationAttribute
annotationType.getName(), testClass.getName()));

while (descriptor != null) {
Class<?> rootDeclaringClass = descriptor.getDeclaringClass();
Class<?> declaringClass = (descriptor.getStereotype() != null) ? descriptor.getStereotypeType()
: rootDeclaringClass;

convertContextConfigToConfigAttributesAndAddToList(descriptor.getAnnotation(), declaringClass,
attributesList);
descriptor = findAnnotationDescriptor(rootDeclaringClass.getSuperclass(), annotationType);
convertAnnotationAttributesToConfigAttributesAndAddToList(descriptor.getAnnotationAttributes(),
descriptor.getDeclaringClass(), attributesList);
descriptor = findAnnotationDescriptor(descriptor.getRootDeclaringClass().getSuperclass(), annotationType);
}

return attributesList;
Expand Down Expand Up @@ -489,20 +508,18 @@ static String[] resolveActiveProfiles(Class<?> testClass) {
final Set<String> activeProfiles = new HashSet<String>();

while (descriptor != null) {
Class<?> rootDeclaringClass = descriptor.getDeclaringClass();
Class<?> declaringClass = (descriptor.getStereotype() != null) ? descriptor.getStereotypeType()
: rootDeclaringClass;
Class<?> declaringClass = descriptor.getDeclaringClass();

ActiveProfiles annotation = descriptor.getAnnotation();
AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes();
if (logger.isTraceEnabled()) {
logger.trace(String.format("Retrieved @ActiveProfiles [%s] for declaring class [%s].", annotation,
declaringClass.getName()));
logger.trace(String.format("Retrieved @ActiveProfiles attributes [%s] for declaring class [%s].",
annAttrs, declaringClass.getName()));
}
validateActiveProfilesConfiguration(declaringClass, annotation);
validateActiveProfilesConfiguration(declaringClass, annAttrs);

String[] profiles = annotation.profiles();
String[] valueProfiles = annotation.value();
Class<? extends ActiveProfilesResolver> resolverClass = annotation.resolver();
String[] profiles = annAttrs.getStringArray("profiles");
String[] valueProfiles = annAttrs.getStringArray("value");
Class<? extends ActiveProfilesResolver> resolverClass = annAttrs.getClass("resolver");

boolean resolverDeclared = !ActiveProfilesResolver.class.equals(resolverClass);
boolean valueDeclared = !ObjectUtils.isEmpty(valueProfiles);
Expand Down Expand Up @@ -538,17 +555,17 @@ else if (valueDeclared) {
}
}

descriptor = annotation.inheritProfiles() ? findAnnotationDescriptor(rootDeclaringClass.getSuperclass(),
annotationType) : null;
descriptor = annAttrs.getBoolean("inheritProfiles") ? findAnnotationDescriptor(
descriptor.getRootDeclaringClass().getSuperclass(), annotationType) : null;
}

return StringUtils.toStringArray(activeProfiles);
}

private static void validateActiveProfilesConfiguration(Class<?> declaringClass, ActiveProfiles annotation) {
String[] valueProfiles = annotation.value();
String[] profiles = annotation.profiles();
Class<? extends ActiveProfilesResolver> resolverClass = annotation.resolver();
private static void validateActiveProfilesConfiguration(Class<?> declaringClass, AnnotationAttributes annAttrs) {
String[] valueProfiles = annAttrs.getStringArray("value");
String[] profiles = annAttrs.getStringArray("profiles");
Class<? extends ActiveProfilesResolver> resolverClass = annAttrs.getClass("resolver");
boolean valueDeclared = !ObjectUtils.isEmpty(valueProfiles);
boolean profilesDeclared = !ObjectUtils.isEmpty(profiles);
boolean resolverDeclared = !ActiveProfilesResolver.class.equals(resolverClass);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

import java.lang.annotation.Annotation;

import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
Expand Down Expand Up @@ -124,7 +126,7 @@ public static UntypedAnnotationDescriptor findAnnotationDescriptorForTypes(Class
* <p>
* If the annotation is used as a meta-annotation, the descriptor also includes
* the {@linkplain #getStereotype() stereotype} on which the annotation is
* present. In such cases, the <em>declaring class</em> is not directly
* present. In such cases, the <em>root declaring class</em> is not directly
* annotated with the annotation but rather indirectly via the stereotype.
*
* <p>
Expand All @@ -133,6 +135,7 @@ public static UntypedAnnotationDescriptor findAnnotationDescriptorForTypes(Class
* properties of the {@code AnnotationDescriptor} would be as follows.
*
* <ul>
* <li>rootDeclaringClass: {@code TransactionalTests} class object</li>
* <li>declaringClass: {@code TransactionalTests} class object</li>
* <li>stereotype: {@code null}</li>
* <li>annotation: instance of the {@code Transactional} annotation</li>
Expand All @@ -150,7 +153,8 @@ public static UntypedAnnotationDescriptor findAnnotationDescriptorForTypes(Class
* properties of the {@code AnnotationDescriptor} would be as follows.
*
* <ul>
* <li>declaringClass: {@code UserRepositoryTests} class object</li>
* <li>rootDeclaringClass: {@code UserRepositoryTests} class object</li>
* <li>declaringClass: {@code RepositoryTests} class object</li>
* <li>stereotype: instance of the {@code RepositoryTests} annotation</li>
* <li>annotation: instance of the {@code Transactional} annotation</li>
* </ul>
Expand All @@ -170,22 +174,31 @@ public static UntypedAnnotationDescriptor findAnnotationDescriptorForTypes(Class
*/
public static class AnnotationDescriptor<T extends Annotation> {

private final Class<?> rootDeclaringClass;
private final Class<?> declaringClass;
private final Annotation stereotype;
private final T annotation;
private final AnnotationAttributes annotationAttributes;


public AnnotationDescriptor(Class<?> declaringClass, T annotation) {
this(declaringClass, null, annotation);
public AnnotationDescriptor(Class<?> rootDeclaringClass, T annotation) {
this(rootDeclaringClass, null, annotation);
}

public AnnotationDescriptor(Class<?> declaringClass, Annotation stereotype, T annotation) {
Assert.notNull(declaringClass, "declaringClass must not be null");
public AnnotationDescriptor(Class<?> rootDeclaringClass, Annotation stereotype, T annotation) {
Assert.notNull(rootDeclaringClass, "rootDeclaringClass must not be null");
Assert.notNull(annotation, "annotation must not be null");

this.declaringClass = declaringClass;
this.rootDeclaringClass = rootDeclaringClass;
this.declaringClass = (stereotype != null) ? stereotype.annotationType() : rootDeclaringClass;
this.stereotype = stereotype;
this.annotation = annotation;
this.annotationAttributes = AnnotatedElementUtils.getAnnotationAttributes(rootDeclaringClass,
annotation.annotationType().getName());
}

public Class<?> getRootDeclaringClass() {
return this.rootDeclaringClass;
}

public Class<?> getDeclaringClass() {
Expand All @@ -200,6 +213,10 @@ public Class<? extends Annotation> getAnnotationType() {
return this.annotation.annotationType();
}

public AnnotationAttributes getAnnotationAttributes() {
return this.annotationAttributes;
}

public Annotation getStereotype() {
return this.stereotype;
}
Expand All @@ -214,6 +231,7 @@ public Class<? extends Annotation> getStereotypeType() {
@Override
public String toString() {
return new ToStringCreator(this)//
.append("rootDeclaringClass", rootDeclaringClass)//
.append("declaringClass", declaringClass)//
.append("stereotype", stereotype)//
.append("annotation", annotation)//
Expand Down
Loading