diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java index 0d80658fc8f..1506ec18f9f 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java @@ -45,14 +45,16 @@ public class AnnotatedElementUtils { * present on the annotation (of the specified * {@code annotationType}) on the supplied {@link AnnotatedElement}. * - *

This method also finds all meta-annotations in the annotation - * hierarchy above the specified annotation. + *

This method finds all meta-annotations in the annotation hierarchy + * above the specified annotation. * * @param element the annotated element; never {@code null} * @param annotationType the annotation type on which to find * meta-annotations; never {@code null} * @return the names of all meta-annotations present on the annotation, * or {@code null} if not found + * @see #getMetaAnnotationTypes(AnnotatedElement, String) + * @see #hasMetaAnnotationTypes */ public static Set getMetaAnnotationTypes(AnnotatedElement element, Class annotationType) { @@ -65,14 +67,16 @@ public class AnnotatedElementUtils { * present on the annotation (of the specified * {@code annotationType}) on the supplied {@link AnnotatedElement}. * - *

This method also finds all meta-annotations in the annotation - * hierarchy above the specified annotation. + *

This method finds all meta-annotations in the annotation hierarchy + * above the specified annotation. * * @param element the annotated element; never {@code null} * @param annotationType the fully qualified class name of the annotation * type on which to find meta-annotations; never {@code null} or empty * @return the names of all meta-annotations present on the annotation, * or {@code null} if not found + * @see #getMetaAnnotationTypes(AnnotatedElement, Class) + * @see #hasMetaAnnotationTypes */ public static Set getMetaAnnotationTypes(AnnotatedElement element, String annotationType) { Assert.notNull(element, "AnnotatedElement must not be null"); @@ -80,32 +84,49 @@ public class AnnotatedElementUtils { final Set types = new LinkedHashSet(); - processWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor() { - @Override - public Object process(Annotation annotation, int metaDepth) { - if (metaDepth > 0) { - types.add(annotation.annotationType().getName()); - } - return null; + try { + Annotation annotation = getAnnotation(element, annotationType); + if (annotation != null) { + processWithGetSemantics(annotation.annotationType(), annotationType, new SimpleAnnotationProcessor() { + + @Override + public Object process(Annotation annotation, int metaDepth) { + types.add(annotation.annotationType().getName()); + return null; + } + }, new HashSet(), 1); } - }); + } + catch (Throwable ex) { + throw new IllegalStateException("Failed to introspect annotations on " + element, ex); + } return (types.isEmpty() ? null : types); } /** + * Determine if the supplied {@link AnnotatedElement} is annotated with + * a composed annotation that is meta-annotated with an + * annotation of the specified {@code annotationType}. + * + *

This method finds all meta-annotations in the annotation hierarchy + * above the specified element. + * * @param element the annotated element; never {@code null} - * @param annotationType the fully qualified class name of the annotation + * @param annotationType the fully qualified class name of the meta-annotation * type to find; never {@code null} or empty + * @return {@code true} if a matching meta-annotation is present + * @see #getMetaAnnotationTypes */ - public static boolean hasMetaAnnotationTypes(AnnotatedElement element, String annotationType) { + public static boolean hasMetaAnnotationTypes(AnnotatedElement element, final String annotationType) { Assert.notNull(element, "AnnotatedElement must not be null"); Assert.hasText(annotationType, "annotationType must not be null or empty"); return Boolean.TRUE.equals(processWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor() { @Override public Boolean process(Annotation annotation, int metaDepth) { - if (metaDepth > 0) { + boolean found = annotation.annotationType().getName().equals(annotationType); + if (found && (metaDepth > 0)) { return Boolean.TRUE; } return null; @@ -114,18 +135,27 @@ public class AnnotatedElementUtils { } /** + * Determine if an annotation of the specified {@code annotationType} + * is present on the supplied {@link AnnotatedElement} or + * within the annotation hierarchy above the specified element. + * + *

If this method returns {@code true}, then {@link #getAnnotationAttributes} + * will return a non-null value. + * * @param element the annotated element; never {@code null} * @param annotationType the fully qualified class name of the annotation * type to find; never {@code null} or empty + * @return {@code true} if a matching annotation is present */ - public static boolean isAnnotated(AnnotatedElement element, String annotationType) { + public static boolean isAnnotated(AnnotatedElement element, final String annotationType) { Assert.notNull(element, "AnnotatedElement must not be null"); Assert.hasText(annotationType, "annotationType must not be null or empty"); return Boolean.TRUE.equals(processWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor() { @Override public Boolean process(Annotation annotation, int metaDepth) { - return Boolean.TRUE; + boolean found = annotation.annotationType().getName().equals(annotationType); + return (found ? Boolean.TRUE : null); } })); } @@ -167,7 +197,7 @@ public class AnnotatedElementUtils { */ public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { - return processWithGetSemantics(element, annotationType, new MergeAnnotationAttributesProcessor( + return processWithGetSemantics(element, annotationType, new MergeAnnotationAttributesProcessor(annotationType, classValuesAsString, nestedAnnotationsAsMap)); } @@ -269,7 +299,7 @@ public class AnnotatedElementUtils { return processWithFindSemantics(element, annotationType, searchOnInterfaces, searchOnSuperclasses, searchOnMethodsInInterfaces, searchOnMethodsInSuperclasses, new MergeAnnotationAttributesProcessor( - classValuesAsString, nestedAnnotationsAsMap)); + annotationType, classValuesAsString, nestedAnnotationsAsMap)); } /** @@ -376,21 +406,12 @@ public class AnnotatedElementUtils { // Search in local annotations for (Annotation annotation : annotations) { - // TODO Add check for !isInJavaLangAnnotationPackage() - // if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation) - // && (annotation.annotationType().getName().equals(annotationType) || - // metaDepth > 0)) { + // TODO Test for !AnnotationUtils.isInJavaLangAnnotationPackage(annotation) if (annotation.annotationType().getName().equals(annotationType) || metaDepth > 0) { T result = processor.process(annotation, metaDepth); if (result != null) { return result; } - result = processWithGetSemantics(annotation.annotationType(), annotationType, processor, - visited, metaDepth + 1); - if (result != null) { - processor.postProcess(annotation, result); - return result; - } } } @@ -398,7 +419,7 @@ public class AnnotatedElementUtils { for (Annotation annotation : annotations) { if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) { T result = processWithGetSemantics(annotation.annotationType(), annotationType, processor, - visited, metaDepth); + visited, metaDepth + 1); if (result != null) { processor.postProcess(annotation, result); return result; @@ -492,13 +513,6 @@ public class AnnotatedElementUtils { if (result != null) { return result; } - result = processWithFindSemantics(annotation.annotationType(), annotationType, - searchOnInterfaces, searchOnSuperclasses, searchOnMethodsInInterfaces, - searchOnMethodsInSuperclasses, processor, visited, metaDepth + 1); - if (result != null) { - processor.postProcess(annotation, result); - return result; - } } } @@ -507,7 +521,7 @@ public class AnnotatedElementUtils { if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) { T result = processWithFindSemantics(annotation.annotationType(), annotationType, searchOnInterfaces, searchOnSuperclasses, searchOnMethodsInInterfaces, - searchOnMethodsInSuperclasses, processor, visited, metaDepth); + searchOnMethodsInSuperclasses, processor, visited, metaDepth + 1); if (result != null) { processor.postProcess(annotation, result); return result; @@ -636,6 +650,14 @@ public class AnnotatedElementUtils { return null; } + private static Annotation getAnnotation(AnnotatedElement element, String annotationType) { + for (Annotation annotation : element.getAnnotations()) { + if (annotation.annotationType().getName().equals(annotationType)) { + return annotation; + } + } + return null; + } /** * Callback interface that is used to process a target annotation (or @@ -707,18 +729,21 @@ public class AnnotatedElementUtils { */ private static class MergeAnnotationAttributesProcessor implements Processor { + private final String annotationType; private final boolean classValuesAsString; private final boolean nestedAnnotationsAsMap; - MergeAnnotationAttributesProcessor(boolean classValuesAsString, boolean nestedAnnotationsAsMap) { + MergeAnnotationAttributesProcessor(String annotationType, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { + this.annotationType = annotationType; this.classValuesAsString = classValuesAsString; this.nestedAnnotationsAsMap = nestedAnnotationsAsMap; } @Override public AnnotationAttributes process(Annotation annotation, int metaDepth) { - return AnnotationUtils.getAnnotationAttributes(annotation, classValuesAsString, nestedAnnotationsAsMap); + boolean found = annotation.annotationType().getName().equals(annotationType); + return (!found ? null : AnnotationUtils.getAnnotationAttributes(annotation, classValuesAsString, nestedAnnotationsAsMap)); } @Override diff --git a/spring-core/src/main/java/org/springframework/core/type/filter/AnnotationTypeFilter.java b/spring-core/src/main/java/org/springframework/core/type/filter/AnnotationTypeFilter.java index 7d2d674c7c3..2b2faa4d05f 100644 --- a/spring-core/src/main/java/org/springframework/core/type/filter/AnnotationTypeFilter.java +++ b/spring-core/src/main/java/org/springframework/core/type/filter/AnnotationTypeFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -28,7 +28,7 @@ import org.springframework.util.ClassUtils; * A simple filter which matches classes with a given annotation, * checking inherited annotations as well. * - *

The matching logic mirrors that of {@code Class.isAnnotationPresent()}. + *

The matching logic mirrors that of {@link java.lang.Class#isAnnotationPresent(Class)}. * * @author Mark Fisher * @author Ramnivas Laddad diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java index 11fa7e68d51..d2195af6a8a 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java @@ -62,11 +62,33 @@ public class AnnotatedElementUtilsTests { @Test public void getMetaAnnotationTypesOnClassWithMetaDepth2() { - Set names = getMetaAnnotationTypes(ComposedTransactionalComponentClass.class, - ComposedTransactionalComponent.class); + Set names = getMetaAnnotationTypes(ComposedTransactionalComponentClass.class, ComposedTransactionalComponent.class); assertEquals(names(TransactionalComponent.class, Transactional.class, Component.class, Retention.class, Documented.class, Target.class, Inherited.class), names); } + @Test + public void hasMetaAnnotationTypesOnNonAnnotatedClass() { + assertFalse(hasMetaAnnotationTypes(NonAnnotatedClass.class, Transactional.class.getName())); + } + + @Test + public void hasMetaAnnotationTypesOnClassWithMetaDepth0() { + assertFalse(hasMetaAnnotationTypes(TransactionalComponentClass.class, TransactionalComponent.class.getName())); + } + + @Test + public void hasMetaAnnotationTypesOnClassWithMetaDepth1() { + assertTrue(hasMetaAnnotationTypes(TransactionalComponentClass.class, Transactional.class.getName())); + assertTrue(hasMetaAnnotationTypes(TransactionalComponentClass.class, Component.class.getName())); + } + + @Test + public void hasMetaAnnotationTypesOnClassWithMetaDepth2() { + assertTrue(hasMetaAnnotationTypes(ComposedTransactionalComponentClass.class, Transactional.class.getName())); + assertTrue(hasMetaAnnotationTypes(ComposedTransactionalComponentClass.class, Component.class.getName())); + assertFalse(hasMetaAnnotationTypes(ComposedTransactionalComponentClass.class, ComposedTransactionalComponent.class.getName())); + } + @Test public void getAllAnnotationAttributesOnClassWithLocalAnnotation() { MultiValueMap attributes = getAllAnnotationAttributes(TxConfig.class, @@ -277,6 +299,14 @@ public class AnnotatedElementUtilsTests { assertNotNull("Should find @Order on StringGenericParameter.getFor() bridge method", attributes); } + /** @since 4.2 */ + @Test + public void findAnnotationAttributesOnClassWithMetaAndLocalTxConfig() { + AnnotationAttributes attributes = findAnnotationAttributes(MetaAndLocalTxConfigClass.class, Transactional.class); + assertNotNull("Should find @Transactional on MetaAndLocalTxConfigClass", attributes); + assertEquals("TX qualifier for MetaAndLocalTxConfigClass.", "localTxMgr", attributes.getString("qualifier")); + } + // ------------------------------------------------------------------------- @@ -315,6 +345,8 @@ public class AnnotatedElementUtilsTests { String value() default ""; + String qualifier() default "transactionManager"; + boolean readOnly() default false; } @@ -333,6 +365,13 @@ public class AnnotatedElementUtilsTests { @interface Composed2 { } + @Transactional + @Retention(RetentionPolicy.RUNTIME) + @interface TxComposedWithOverride { + + String qualifier() default "txMgr"; + } + @Transactional("TxComposed1") @Retention(RetentionPolicy.RUNTIME) @interface TxComposed1 { @@ -354,6 +393,14 @@ public class AnnotatedElementUtilsTests { @interface ComposedTransactionalComponent { } + @TxComposedWithOverride + // Override default "txMgr" from @TxComposedWithOverride with "localTxMgr" + @Transactional(qualifier = "localTxMgr") + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + @interface MetaAndLocalTxConfig { + } + // ------------------------------------------------------------------------- static class NonAnnotatedClass { @@ -389,6 +436,10 @@ public class AnnotatedElementUtilsTests { static class SubSubClassWithInheritedComposedAnnotation extends SubClassWithInheritedComposedAnnotation { } + @MetaAndLocalTxConfig + static class MetaAndLocalTxConfigClass { + } + @Transactional("TxConfig") static class TxConfig { } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java index 277897c307f..e48d055b3bc 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -18,18 +18,14 @@ package org.springframework.web.servlet.config.annotation; import java.util.List; import java.util.Locale; -import java.util.Map; + import javax.servlet.http.HttpServletRequest; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.xml.XmlMapper; import org.joda.time.DateTime; + import org.junit.Test; import org.springframework.beans.DirectFieldAccessor; -import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.context.ApplicationContext; import org.springframework.context.MessageSource; import org.springframework.context.annotation.Bean; @@ -83,14 +79,21 @@ import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.servlet.view.ViewResolverComposite; import org.springframework.web.util.UrlPathHelper; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; + import static org.junit.Assert.*; /** - * A test fixture with an {@link WebMvcConfigurationSupport} instance. + * Integration tests for {@link WebMvcConfigurationSupport} (imported via + * {@link EnableWebMvc @EnableWebMvc}). * * @author Rossen Stoyanchev * @author Juergen Hoeller * @author Sebastien Deleuze + * @author Sam Brannen */ public class WebMvcConfigurationSupportTests { @@ -106,10 +109,10 @@ public class WebMvcConfigurationSupportTests { assertEquals(ConversionServiceExposingInterceptor.class, chain.getInterceptors()[0].getClass()); chain = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/scoped")); - assertNotNull(chain); + assertNotNull("HandlerExecutionChain for '/scoped' mapping should not be null.", chain); chain = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/scopedProxy")); - assertNotNull(chain); + assertNotNull("HandlerExecutionChain for '/scopedProxy' mapping should not be null.", chain); } @Test @@ -207,6 +210,7 @@ public class WebMvcConfigurationSupportTests { } @Test + @SuppressWarnings("unchecked") public void handlerExceptionResolver() throws Exception { ApplicationContext context = initContext(WebConfig.class); HandlerExceptionResolverComposite compositeResolver = @@ -267,9 +271,6 @@ public class WebMvcConfigurationSupportTests { ApplicationContext context = initContext(CustomViewResolverOrderConfig.class); ViewResolverComposite resolver = context.getBean("mvcViewResolver", ViewResolverComposite.class); - Map map = BeanFactoryUtils.beansOfTypeIncludingAncestors( - context, ViewResolver.class, true, false); - assertNotNull(resolver); assertEquals(1, resolver.getViewResolvers().size()); assertEquals(InternalResourceViewResolver.class, resolver.getViewResolvers().get(0).getClass()); @@ -287,8 +288,7 @@ public class WebMvcConfigurationSupportTests { assertEquals(AntPathMatcher.class, pathMatcher.getClass()); } - - private ApplicationContext initContext(Class... configClasses) { + private ApplicationContext initContext(Class... configClasses) { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); context.setServletContext(new MockServletContext()); context.register(configClasses);