/*
* Copyright 2002-2016 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.Set;
import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.springframework.core.annotation.AnnotatedElementUtils.*;
/**
* Unit tests that verify support for finding multiple composed annotations on
* a single annotated element.
*
* <p>See <a href="https://jira.spring.io/browse/SPR-13486">SPR-13486</a>.
*
* @author Sam Brannen
* @since 4.3
* @see AnnotatedElementUtils
* @see AnnotatedElementUtilsTests
* @see ComposedRepeatableAnnotationsTests
*/
public class MultipleComposedAnnotationsOnSingleAnnotatedElementTests {
@Test
public void getMultipleComposedAnnotationsOnClass() {
assertGetAllMergedAnnotationsBehavior(MultipleComposedCachesClass.class);
}
@Test
public void getMultipleInheritedComposedAnnotationsOnSuperclass() {
assertGetAllMergedAnnotationsBehavior(SubMultipleComposedCachesClass.class);
}
@Test
public void getMultipleNoninheritedComposedAnnotationsOnClass() {
Class<?> element = MultipleNoninheritedComposedCachesClass.class;
Set<Cacheable> cacheables = getAllMergedAnnotations(element, Cacheable.class);
assertNotNull(cacheables);
assertEquals(2, cacheables.size());
Iterator<Cacheable> iterator = cacheables.iterator();
Cacheable cacheable1 = iterator.next();
Cacheable cacheable2 = iterator.next();
assertEquals("noninheritedCache1", cacheable1.value());
assertEquals("noninheritedCache2", cacheable2.value());
}
@Test
public void getMultipleNoninheritedComposedAnnotationsOnSuperclass() {
Class<?> element = SubMultipleNoninheritedComposedCachesClass.class;
Set<Cacheable> cacheables = getAllMergedAnnotations(element, Cacheable.class);
assertNotNull(cacheables);
assertEquals(0, cacheables.size());
}
@Test
public void getComposedPlusLocalAnnotationsOnClass() {
assertGetAllMergedAnnotationsBehavior(ComposedPlusLocalCachesClass.class);
}
@Test
public void getMultipleComposedAnnotationsOnInterface() {
Class<MultipleComposedCachesOnInterfaceClass> element = MultipleComposedCachesOnInterfaceClass.class;
Set<Cacheable> cacheables = getAllMergedAnnotations(element, Cacheable.class);
assertNotNull(cacheables);
assertEquals(0, cacheables.size());
}
@Test
public void getMultipleComposedAnnotationsOnMethod() throws Exception {
AnnotatedElement element = getClass().getDeclaredMethod("multipleComposedCachesMethod");
assertGetAllMergedAnnotationsBehavior(element);
}
@Test
public void getComposedPlusLocalAnnotationsOnMethod() throws Exception {
AnnotatedElement element = getClass().getDeclaredMethod("composedPlusLocalCachesMethod");
assertGetAllMergedAnnotationsBehavior(element);
}
@Test
@Ignore("Disabled since some Java 8 updates handle the bridge method differently")
public void getMultipleComposedAnnotationsOnBridgeMethod() throws Exception {
Set<Cacheable> cacheables = getAllMergedAnnotations(getBridgeMethod(), Cacheable.class);
assertNotNull(cacheables);
assertEquals(0, cacheables.size());
}
@Test
public void findMultipleComposedAnnotationsOnClass() {
assertFindAllMergedAnnotationsBehavior(MultipleComposedCachesClass.class);
}
@Test
public void findMultipleInheritedComposedAnnotationsOnSuperclass() {
assertFindAllMergedAnnotationsBehavior(SubMultipleComposedCachesClass.class);
}
@Test
public void findMultipleNoninheritedComposedAnnotationsOnClass() {
Class<?> element = MultipleNoninheritedComposedCachesClass.class;
Set<Cacheable> cacheables = findAllMergedAnnotations(element, Cacheable.class);
assertNotNull(cacheables);
assertEquals(2, cacheables.size());
Iterator<Cacheable> iterator = cacheables.iterator();
Cacheable cacheable1 = iterator.next();
Cacheable cacheable2 = iterator.next();
assertEquals("noninheritedCache1", cacheable1.value());
assertEquals("noninheritedCache2", cacheable2.value());
}
@Test
public void findMultipleNoninheritedComposedAnnotationsOnSuperclass() {
Class<?> element = SubMultipleNoninheritedComposedCachesClass.class;
Set<Cacheable> cacheables = findAllMergedAnnotations(element, Cacheable.class);
assertNotNull(cacheables);
assertEquals(2, cacheables.size());
Iterator<Cacheable> iterator = cacheables.iterator();
Cacheable cacheable1 = iterator.next();
Cacheable cacheable2 = iterator.next();
assertEquals("noninheritedCache1", cacheable1.value());
assertEquals("noninheritedCache2", cacheable2.value());
}
@Test
public void findComposedPlusLocalAnnotationsOnClass() {
assertFindAllMergedAnnotationsBehavior(ComposedPlusLocalCachesClass.class);
}
@Test
public void findMultipleComposedAnnotationsOnInterface() {
assertFindAllMergedAnnotationsBehavior(MultipleComposedCachesOnInterfaceClass.class);
}
@Test
public void findComposedCacheOnInterfaceAndLocalCacheOnClass() {
assertFindAllMergedAnnotationsBehavior(ComposedCacheOnInterfaceAndLocalCacheClass.class);
}
@Test
public void findMultipleComposedAnnotationsOnMethod() throws Exception {
AnnotatedElement element = getClass().getDeclaredMethod("multipleComposedCachesMethod");
assertFindAllMergedAnnotationsBehavior(element);
}
@Test
public void findComposedPlusLocalAnnotationsOnMethod() throws Exception {
AnnotatedElement element = getClass().getDeclaredMethod("composedPlusLocalCachesMethod");
assertFindAllMergedAnnotationsBehavior(element);
}
@Test
public void findMultipleComposedAnnotationsOnBridgeMethod() throws Exception {
assertFindAllMergedAnnotationsBehavior(getBridgeMethod());
}
/**
* Bridge/bridged method setup code copied from
* {@link org.springframework.core.BridgeMethodResolverTests#testWithGenericParameter()}.
*/
public Method getBridgeMethod() throws NoSuchMethodException {
Method[] methods = StringGenericParameter.class.getMethods();
Method bridgeMethod = null;
Method bridgedMethod = null;
for (Method method : methods) {
if ("getFor".equals(method.getName()) && !method.getParameterTypes()[0].equals(Integer.class)) {
if (method.getReturnType().equals(Object.class)) {
bridgeMethod = method;
}
else {
bridgedMethod = method;
}
}
}
assertTrue(bridgeMethod != null && bridgeMethod.isBridge());
assertTrue(bridgedMethod != null && !bridgedMethod.isBridge());
return bridgeMethod;
}
private void assertGetAllMergedAnnotationsBehavior(AnnotatedElement element) {
assertNotNull(element);
Set<Cacheable> cacheables = getAllMergedAnnotations(element, Cacheable.class);
assertNotNull(cacheables);
assertEquals(2, cacheables.size());
Iterator<Cacheable> iterator = cacheables.iterator();
Cacheable fooCacheable = iterator.next();
Cacheable barCacheable = iterator.next();
assertEquals("fooKey", fooCacheable.key());
assertEquals("fooCache", fooCacheable.value());
assertEquals("barKey", barCacheable.key());
assertEquals("barCache", barCacheable.value());
}
private void assertFindAllMergedAnnotationsBehavior(AnnotatedElement element) {
assertNotNull(element);
Set<Cacheable> cacheables = findAllMergedAnnotations(element, Cacheable.class);
assertNotNull(cacheables);
assertEquals(2, cacheables.size());
Iterator<Cacheable> iterator = cacheables.iterator();
Cacheable fooCacheable = iterator.next();
Cacheable barCacheable = iterator.next();
assertEquals("fooKey", fooCacheable.key());
assertEquals("fooCache", fooCacheable.value());
assertEquals("barKey", barCacheable.key());
assertEquals("barCache", barCacheable.value());
}
// -------------------------------------------------------------------------
/**
* Mock of {@code org.springframework.cache.annotation.Cacheable}.
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface Cacheable {
@AliasFor("cacheName")
String value() default "";
@AliasFor("value")
String cacheName() default "";
String key() default "";
}
@Cacheable("fooCache")
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface FooCache {
@AliasFor(annotation = Cacheable.class)
String key() default "";
}
@Cacheable("barCache")
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface BarCache {
@AliasFor(annotation = Cacheable.class)
String key();
}
@Cacheable("noninheritedCache1")
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@interface NoninheritedCache1 {
@AliasFor(annotation = Cacheable.class)
String key() default "";
}
@Cacheable("noninheritedCache2")
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@interface NoninheritedCache2 {
@AliasFor(annotation = Cacheable.class)
String key() default "";
}
@FooCache(key = "fooKey")
@BarCache(key = "barKey")
private static class MultipleComposedCachesClass {
}
private static class SubMultipleComposedCachesClass extends MultipleComposedCachesClass {
}
@NoninheritedCache1
@NoninheritedCache2
private static class MultipleNoninheritedComposedCachesClass {
}
private static class SubMultipleNoninheritedComposedCachesClass extends MultipleNoninheritedComposedCachesClass {
}
@Cacheable(cacheName = "fooCache", key = "fooKey")
@BarCache(key = "barKey")
private static class ComposedPlusLocalCachesClass {
}
@FooCache(key = "fooKey")
@BarCache(key = "barKey")
private interface MultipleComposedCachesInterface {
}
private static class MultipleComposedCachesOnInterfaceClass implements MultipleComposedCachesInterface {
}
@Cacheable(cacheName = "fooCache", key = "fooKey")
private interface ComposedCacheInterface {
}
@BarCache(key = "barKey")
private static class ComposedCacheOnInterfaceAndLocalCacheClass implements ComposedCacheInterface {
}
@FooCache(key = "fooKey")
@BarCache(key = "barKey")
private void multipleComposedCachesMethod() {
}
@Cacheable(cacheName = "fooCache", key = "fooKey")
@BarCache(key = "barKey")
private void composedPlusLocalCachesMethod() {
}
public interface GenericParameter<T> {
T getFor(Class<T> cls);
}
@SuppressWarnings("unused")
private static class StringGenericParameter implements GenericParameter<String> {
@FooCache(key = "fooKey")
@BarCache(key = "barKey")
@Override
public String getFor(Class<String> cls) {
return "foo";
}
public String getFor(Integer integer) {
return "foo";
}
}
}