Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #393 from sbrannen/SPR-7827
* SPR-7827:
  Provide meta-annotation support in the TCF
  • Loading branch information
sbrannen committed Oct 28, 2013
2 parents 56dfcd1 + 5e7021f commit 2bd5a53
Show file tree
Hide file tree
Showing 28 changed files with 1,852 additions and 205 deletions.
Expand Up @@ -285,9 +285,8 @@ public static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> a
* <p>The standard {@link Class} API does not provide a mechanism for determining which class
* in an inheritance hierarchy actually declares an {@link Annotation}, so we need to handle
* this explicitly.
* @param annotationType the Class object corresponding to the annotation type
* @param clazz the Class object corresponding to the class on which to check for the annotation,
* or {@code null}
* @param annotationType the annotation class to look for, both locally and as a meta-annotation
* @param clazz the class on which to check for the annotation, or {@code null}
* @return the first {@link Class} in the inheritance hierarchy of the specified {@code clazz}
* which declares an annotation for the specified {@code annotationType}, or {@code null}
* if not found
Expand All @@ -301,8 +300,24 @@ public static Class<?> findAnnotationDeclaringClass(Class<? extends Annotation>
if (clazz == null || clazz.equals(Object.class)) {
return null;
}
return (isAnnotationDeclaredLocally(annotationType, clazz)) ? clazz : findAnnotationDeclaringClass(
annotationType, clazz.getSuperclass());

// Declared locally?
if (isAnnotationDeclaredLocally(annotationType, clazz)) {
return clazz;
}

// Declared on a stereotype annotation (i.e., as a meta-annotation)?
if (!Annotation.class.isAssignableFrom(clazz)) {
for (Annotation stereotype : clazz.getAnnotations()) {
Class<?> declaringClass = findAnnotationDeclaringClass(annotationType, stereotype.annotationType());
if (declaringClass != null) {
return declaringClass;
}
}
}

// Declared on a superclass?
return findAnnotationDeclaringClass(annotationType, clazz.getSuperclass());
}

/**
Expand Down
Expand Up @@ -96,6 +96,18 @@ public void testFindMethodAnnotationOnBridgeMethod() throws Exception {
// assertNotNull(o);
// }

@Test
public void findAnnotationPrefersInteracesOverLocalMetaAnnotations() {
Component component = AnnotationUtils.findAnnotation(
ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, Component.class);

// By inspecting ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface, one
// might expect that "meta2" should be found; however, with the current
// implementation "meta1" will be found.
assertNotNull(component);
assertEquals("meta1", component.value());
}

@Test
public void testFindAnnotationDeclaringClass() throws Exception {
// no class-level annotation
Expand Down Expand Up @@ -133,7 +145,7 @@ public void findAnnotationDeclaringClassForTypesWithSingleCandidateType() {
assertEquals(InheritedAnnotationInterface.class,
findAnnotationDeclaringClassForTypes(transactionalCandidateList, InheritedAnnotationInterface.class));
assertNull(findAnnotationDeclaringClassForTypes(transactionalCandidateList,
SubInheritedAnnotationInterface.class));
SubInheritedAnnotationInterface.class));
assertEquals(InheritedAnnotationClass.class,
findAnnotationDeclaringClassForTypes(transactionalCandidateList, InheritedAnnotationClass.class));
assertEquals(InheritedAnnotationClass.class,
Expand Down Expand Up @@ -288,19 +300,23 @@ public void testGetRepeatableFromMethod() throws Exception {


@Component(value = "meta1")
@Order
@Retention(RetentionPolicy.RUNTIME)
@interface Meta1 {
}

@Component(value = "meta2")
@Transactional
@Retention(RetentionPolicy.RUNTIME)
@interface Meta2 {
}

@Meta1
@Component(value = "local")
static interface InterfaceWithMetaAnnotation {
}

@Meta2
static class HasLocalAndMetaComponentAnnotation {
static class ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface implements InterfaceWithMetaAnnotation {
}

public static interface AnnotatedInterface {
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
Expand All @@ -25,6 +25,8 @@
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import static org.springframework.core.annotation.AnnotationUtils.*;

/**
* General utility methods for working with <em>profile values</em>.
*
Expand Down Expand Up @@ -63,7 +65,7 @@ public static ProfileValueSource retrieveProfileValueSource(Class<?> testClass)
Assert.notNull(testClass, "testClass must not be null");

Class<ProfileValueSourceConfiguration> annotationType = ProfileValueSourceConfiguration.class;
ProfileValueSourceConfiguration config = testClass.getAnnotation(annotationType);
ProfileValueSourceConfiguration config = findAnnotation(testClass, annotationType);
if (logger.isDebugEnabled()) {
logger.debug("Retrieved @ProfileValueSourceConfiguration [" + config + "] for test class ["
+ testClass.getName() + "]");
Expand Down Expand Up @@ -114,7 +116,7 @@ public static ProfileValueSource retrieveProfileValueSource(Class<?> testClass)
* environment
*/
public static boolean isTestEnabledInThisEnvironment(Class<?> testClass) {
IfProfileValue ifProfileValue = testClass.getAnnotation(IfProfileValue.class);
IfProfileValue ifProfileValue = findAnnotation(testClass, IfProfileValue.class);
return isTestEnabledInThisEnvironment(retrieveProfileValueSource(testClass), ifProfileValue);
}

Expand Down Expand Up @@ -157,11 +159,11 @@ public static boolean isTestEnabledInThisEnvironment(Method testMethod, Class<?>
public static boolean isTestEnabledInThisEnvironment(ProfileValueSource profileValueSource, Method testMethod,
Class<?> testClass) {

IfProfileValue ifProfileValue = testClass.getAnnotation(IfProfileValue.class);
IfProfileValue ifProfileValue = findAnnotation(testClass, IfProfileValue.class);
boolean classLevelEnabled = isTestEnabledInThisEnvironment(profileValueSource, ifProfileValue);

if (classLevelEnabled) {
ifProfileValue = testMethod.getAnnotation(IfProfileValue.class);
ifProfileValue = findAnnotation(testMethod, IfProfileValue.class);
return isTestEnabledInThisEnvironment(profileValueSource, ifProfileValue);
}

Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
Expand All @@ -17,25 +17,27 @@
package org.springframework.test.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;

/**
* Test annotation to indicate that a test method should be invoked repeatedly.
* <p />
* Note that the scope of execution to be repeated includes execution of the
*
* <p>Note that the scope of execution to be repeated includes execution of the
* test method itself as well as any <em>set up</em> or <em>tear down</em> of
* the test fixture.
*
* @author Rod Johnson
* @author Sam Brannen
* @since 2.0
* @see Timed
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Retention(RUNTIME)
@Target({ METHOD, ANNOTATION_TYPE })
public @interface Repeat {

int value() default 1;
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
Expand All @@ -17,34 +17,31 @@
package org.springframework.test.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;

/**
* <p>
* Test-specific annotation to indicate that a test method has to finish
* execution in a {@link #millis() specified time period}.
* </p>
* <p>
* If the text execution takes longer than the specified time period, then the
* test is to be considered failed.
* </p>
* <p>
* Note that the time period includes execution of the test method itself, any
* {@link Repeat repetitions} of the test, and any <em>set up</em> or
*
* <p>If the text execution takes longer than the specified time period, then
* the test is to be considered failed.
*
* <p>Note that the time period includes execution of the test method itself,
* any {@link Repeat repetitions} of the test, and any <em>set up</em> or
* <em>tear down</em> of the test fixture.
* </p>
*
* @author Rod Johnson
* @author Sam Brannen
* @since 2.0
* @see Repeat
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Retention(RUNTIME)
@Target({ METHOD, ANNOTATION_TYPE })
public @interface Timed {

/**
Expand Down

0 comments on commit 2bd5a53

Please sign in to comment.