Browse Source
Spring Framework 4.2 introduced support for aliases between annotation attributes that fall into the following two categories. 1) Alias pairs: two attributes in the same annotation that use @AliasFor to declare that they are explicit aliases for each other. 2) Meta-annotation attribute overrides: an attribute in one annotation uses @AliasFor to declare that it is an explicit override of an attribute in a meta-annotation. However, the existing functionality fails to support the case where two attributes in the same annotation both use @AliasFor to declare that they are both explicit overrides of the same attribute in the same meta-annotation. In such scenarios, one would intuitively assume that two such attributes would be treated as "implicit" aliases for each other, analogous to the existing support for explicit alias pairs. Furthermore, an annotation may potentially declare multiple aliases that are effectively a set of implicit aliases for each other. This commit introduces support for implicit aliases configured via @AliasFor through an extensive overhaul of the support for alias lookups, validation, etc. Specifically, this commit includes the following. - Introduced isAnnotationMetaPresent() in AnnotationUtils. - Introduced private AliasDescriptor class in AnnotationUtils in order to encapsulate the parsing, validation, and comparison of both explicit and implicit aliases configured via @AliasFor. - Switched from single values for alias names to lists of alias names. - Renamed getAliasedAttributeName() to getAliasedAttributeNames() in AnnotationUtils. - Converted alias map to contain lists of aliases in AnnotationUtils. - Refactored the following to support multiple implicit aliases: getRequiredAttributeWithAlias() in AnnotationAttributes, AbstractAliasAwareAnnotationAttributeExtractor, MapAnnotationAttributeExtractor, MergedAnnotationAttributesProcessor in AnnotatedElementUtils, and postProcessAnnotationAttributes() in AnnotationUtils. - Introduced numerous tests for implicit alias support, including AbstractAliasAwareAnnotationAttributeExtractorTestCase, DefaultAnnotationAttributeExtractorTests, and MapAnnotationAttributeExtractorTests. - Updated Javadoc in @AliasFor regarding implicit aliases and in AnnotationUtils regarding "meta-present". Issue: SPR-13345pull/867/head
12 changed files with 1369 additions and 379 deletions
@ -0,0 +1,68 @@
@@ -0,0 +1,68 @@
|
||||
/* |
||||
* 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. |
||||
* 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.Annotation; |
||||
import java.lang.reflect.Method; |
||||
|
||||
import org.junit.Test; |
||||
|
||||
import org.springframework.core.annotation.AnnotationUtilsTests.GroovyImplicitAliasesContextConfigClass; |
||||
import org.springframework.core.annotation.AnnotationUtilsTests.ImplicitAliasesContextConfig; |
||||
import org.springframework.core.annotation.AnnotationUtilsTests.Location1ImplicitAliasesContextConfigClass; |
||||
import org.springframework.core.annotation.AnnotationUtilsTests.Location2ImplicitAliasesContextConfigClass; |
||||
import org.springframework.core.annotation.AnnotationUtilsTests.Location3ImplicitAliasesContextConfigClass; |
||||
import org.springframework.core.annotation.AnnotationUtilsTests.ValueImplicitAliasesContextConfigClass; |
||||
import org.springframework.core.annotation.AnnotationUtilsTests.XmlImplicitAliasesContextConfigClass; |
||||
|
||||
import static org.hamcrest.CoreMatchers.*; |
||||
import static org.junit.Assert.*; |
||||
|
||||
/** |
||||
* Abstract base class for tests involving concrete implementations of |
||||
* {@link AbstractAliasAwareAnnotationAttributeExtractor}. |
||||
* |
||||
* @author Sam Brannen |
||||
* @since 4.2.1 |
||||
*/ |
||||
public abstract class AbstractAliasAwareAnnotationAttributeExtractorTestCase { |
||||
|
||||
@Test |
||||
public void getAttributeValueForImplicitAliases() throws Exception { |
||||
assertGetAttributeValueForImplicitAliases(GroovyImplicitAliasesContextConfigClass.class, "groovyScript"); |
||||
assertGetAttributeValueForImplicitAliases(XmlImplicitAliasesContextConfigClass.class, "xmlFile"); |
||||
assertGetAttributeValueForImplicitAliases(ValueImplicitAliasesContextConfigClass.class, "value"); |
||||
assertGetAttributeValueForImplicitAliases(Location1ImplicitAliasesContextConfigClass.class, "location1"); |
||||
assertGetAttributeValueForImplicitAliases(Location2ImplicitAliasesContextConfigClass.class, "location2"); |
||||
assertGetAttributeValueForImplicitAliases(Location3ImplicitAliasesContextConfigClass.class, "location3"); |
||||
} |
||||
|
||||
private void assertGetAttributeValueForImplicitAliases(Class<?> clazz, String expected) throws Exception { |
||||
Method xmlFile = ImplicitAliasesContextConfig.class.getDeclaredMethod("xmlFile"); |
||||
Method groovyScript = ImplicitAliasesContextConfig.class.getDeclaredMethod("groovyScript"); |
||||
Method value = ImplicitAliasesContextConfig.class.getDeclaredMethod("value"); |
||||
|
||||
AnnotationAttributeExtractor<?> extractor = createExtractorFor(clazz, expected, ImplicitAliasesContextConfig.class); |
||||
|
||||
assertThat(extractor.getAttributeValue(value), is(expected)); |
||||
assertThat(extractor.getAttributeValue(groovyScript), is(expected)); |
||||
assertThat(extractor.getAttributeValue(xmlFile), is(expected)); |
||||
} |
||||
|
||||
protected abstract AnnotationAttributeExtractor<?> createExtractorFor(Class<?> clazz, String expected, Class<? extends Annotation> annotationType); |
||||
|
||||
} |
||||
@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
|
||||
/* |
||||
* 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. |
||||
* 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.Annotation; |
||||
|
||||
/** |
||||
* Unit tests for {@link DefaultAnnotationAttributeExtractor}. |
||||
* |
||||
* @author Sam Brannen |
||||
* @since 4.2.1 |
||||
*/ |
||||
public class DefaultAnnotationAttributeExtractorTests extends AbstractAliasAwareAnnotationAttributeExtractorTestCase { |
||||
|
||||
@Override |
||||
protected AnnotationAttributeExtractor<?> createExtractorFor(Class<?> clazz, String expected, Class<? extends Annotation> annotationType) { |
||||
return new DefaultAnnotationAttributeExtractor(clazz.getAnnotation(annotationType), clazz); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,127 @@
@@ -0,0 +1,127 @@
|
||||
/* |
||||
* 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. |
||||
* 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.Annotation; |
||||
import java.util.Arrays; |
||||
import java.util.Collections; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
|
||||
import org.springframework.core.annotation.AnnotationUtilsTests.ImplicitAliasesContextConfig; |
||||
import org.springframework.util.LinkedMultiValueMap; |
||||
import org.springframework.util.MultiValueMap; |
||||
|
||||
import static org.hamcrest.Matchers.*; |
||||
import static org.junit.Assert.*; |
||||
|
||||
/** |
||||
* Unit tests for {@link MapAnnotationAttributeExtractor}. |
||||
* |
||||
* @author Sam Brannen |
||||
* @since 4.2.1 |
||||
*/ |
||||
public class MapAnnotationAttributeExtractorTests extends AbstractAliasAwareAnnotationAttributeExtractorTestCase { |
||||
|
||||
@Before |
||||
public void clearCachesBeforeTests() { |
||||
AnnotationUtilsTests.clearCaches(); |
||||
} |
||||
|
||||
@Test |
||||
@SuppressWarnings("serial") |
||||
public void enrichAndValidateAttributesWithImplicitAliasesAndMinimalAttributes() { |
||||
Map<String, Object> attributes = new HashMap<String, Object>(); |
||||
Map<String, Object> expectedAttributes = new HashMap<String, Object>() {{ |
||||
put("groovyScript", ""); |
||||
put("xmlFile", ""); |
||||
put("value", ""); |
||||
put("location1", ""); |
||||
put("location2", ""); |
||||
put("location3", ""); |
||||
put("nonAliasedAttribute", ""); |
||||
put("configClass", Object.class); |
||||
}}; |
||||
|
||||
assertEnrichAndValidateAttributes(attributes, expectedAttributes); |
||||
} |
||||
|
||||
@Test |
||||
@SuppressWarnings("serial") |
||||
public void enrichAndValidateAttributesWithImplicitAliases() { |
||||
Map<String, Object> attributes = new HashMap<String, Object>() {{ |
||||
put("groovyScript", "groovy!"); |
||||
}}; |
||||
|
||||
Map<String, Object> expectedAttributes = new HashMap<String, Object>() {{ |
||||
put("groovyScript", "groovy!"); |
||||
put("xmlFile", "groovy!"); |
||||
put("value", "groovy!"); |
||||
put("location1", "groovy!"); |
||||
put("location2", "groovy!"); |
||||
put("location3", "groovy!"); |
||||
put("nonAliasedAttribute", ""); |
||||
put("configClass", Object.class); |
||||
}}; |
||||
|
||||
assertEnrichAndValidateAttributes(attributes, expectedAttributes); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
private void assertEnrichAndValidateAttributes(Map<String, Object> sourceAttributes, Map<String, Object> expected) { |
||||
Class<? extends Annotation> annotationType = ImplicitAliasesContextConfig.class; |
||||
|
||||
// Since the ordering of attribute methods returned by the JVM is
|
||||
// non-deterministic, we have to rig the attributeAliasesCache in AnnotationUtils
|
||||
// so that the tests consistently fail in case enrichAndValidateAttributes() is
|
||||
// buggy.
|
||||
//
|
||||
// Otherwise, these tests would intermittently pass even for an invalid
|
||||
// implementation.
|
||||
Map<Class<? extends Annotation>, MultiValueMap<String, String>> attributeAliasesCache = |
||||
(Map<Class<? extends Annotation>, MultiValueMap<String, String>>) AnnotationUtilsTests.getCache("attributeAliasesCache"); |
||||
|
||||
// Declare aliases in an order that will cause enrichAndValidateAttributes() to
|
||||
// fail unless it considers all aliases in the set of implicit aliases.
|
||||
MultiValueMap<String, String> aliases = new LinkedMultiValueMap<String, String>(); |
||||
aliases.put("xmlFile", Arrays.asList("value", "groovyScript", "location1", "location2", "location3")); |
||||
aliases.put("groovyScript", Arrays.asList("value", "xmlFile", "location1", "location2", "location3")); |
||||
aliases.put("value", Arrays.asList("xmlFile", "groovyScript", "location1", "location2", "location3")); |
||||
aliases.put("location1", Arrays.asList("xmlFile", "groovyScript", "value", "location2", "location3")); |
||||
aliases.put("location2", Arrays.asList("xmlFile", "groovyScript", "value", "location1", "location3")); |
||||
aliases.put("location3", Arrays.asList("xmlFile", "groovyScript", "value", "location1", "location2")); |
||||
|
||||
attributeAliasesCache.put(annotationType, aliases); |
||||
|
||||
MapAnnotationAttributeExtractor extractor = new MapAnnotationAttributeExtractor(sourceAttributes, annotationType, null); |
||||
Map<String, Object> enriched = extractor.getSource(); |
||||
|
||||
assertEquals("attribute map size", expected.size(), enriched.size()); |
||||
expected.keySet().stream().forEach( attr -> |
||||
assertThat("for attribute '" + attr + "'", enriched.get(attr), is(expected.get(attr)))); |
||||
} |
||||
|
||||
@Override |
||||
protected AnnotationAttributeExtractor<?> createExtractorFor(Class<?> clazz, String expected, Class<? extends Annotation> annotationType) { |
||||
Map<String, Object> attributes = Collections.singletonMap(expected, expected); |
||||
return new MapAnnotationAttributeExtractor(attributes, annotationType, clazz); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue