Merge from sbrannen/SPR-11512
* SPR-11512: Support annotation attribute aliases and overrides via @AliasFor
This commit is contained in:
commit
4549d76fbe
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -32,7 +32,6 @@ import org.springframework.jmx.export.metadata.ManagedNotification;
|
||||||
import org.springframework.jmx.export.metadata.ManagedOperation;
|
import org.springframework.jmx.export.metadata.ManagedOperation;
|
||||||
import org.springframework.jmx.export.metadata.ManagedOperationParameter;
|
import org.springframework.jmx.export.metadata.ManagedOperationParameter;
|
||||||
import org.springframework.jmx.export.metadata.ManagedResource;
|
import org.springframework.jmx.export.metadata.ManagedResource;
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
import org.springframework.util.StringValueResolver;
|
import org.springframework.util.StringValueResolver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -66,7 +65,6 @@ public class AnnotationJmxAttributeSource implements JmxAttributeSource, BeanFac
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ManagedResource getManagedResource(Class<?> beanClass) throws InvalidMetadataException {
|
public ManagedResource getManagedResource(Class<?> beanClass) throws InvalidMetadataException {
|
||||||
org.springframework.jmx.export.annotation.ManagedResource ann =
|
org.springframework.jmx.export.annotation.ManagedResource ann =
|
||||||
|
@ -76,13 +74,6 @@ public class AnnotationJmxAttributeSource implements JmxAttributeSource, BeanFac
|
||||||
}
|
}
|
||||||
ManagedResource managedResource = new ManagedResource();
|
ManagedResource managedResource = new ManagedResource();
|
||||||
AnnotationBeanUtils.copyPropertiesToBean(ann, managedResource, this.embeddedValueResolver);
|
AnnotationBeanUtils.copyPropertiesToBean(ann, managedResource, this.embeddedValueResolver);
|
||||||
if (!"".equals(ann.value()) && !StringUtils.hasLength(managedResource.getObjectName())) {
|
|
||||||
String value = ann.value();
|
|
||||||
if (this.embeddedValueResolver != null) {
|
|
||||||
value = this.embeddedValueResolver.resolveStringValue(value);
|
|
||||||
}
|
|
||||||
managedResource.setObjectName(value);
|
|
||||||
}
|
|
||||||
return managedResource;
|
return managedResource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2012 the original author or authors.
|
* Copyright 2002-2015 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -23,6 +23,8 @@ import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AliasFor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JDK 1.5+ class-level annotation that indicates to register instances of a
|
* JDK 1.5+ class-level annotation that indicates to register instances of a
|
||||||
* class with a JMX server, corresponding to the ManagedResource attribute.
|
* class with a JMX server, corresponding to the ManagedResource attribute.
|
||||||
|
@ -34,6 +36,7 @@ import java.lang.annotation.Target;
|
||||||
*
|
*
|
||||||
* @author Rob Harrop
|
* @author Rob Harrop
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
|
* @author Sam Brannen
|
||||||
* @since 1.2
|
* @since 1.2
|
||||||
* @see org.springframework.jmx.export.metadata.ManagedResource
|
* @see org.springframework.jmx.export.metadata.ManagedResource
|
||||||
*/
|
*/
|
||||||
|
@ -44,11 +47,12 @@ import java.lang.annotation.Target;
|
||||||
public @interface ManagedResource {
|
public @interface ManagedResource {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The annotation value is equivalent to the {@code objectName}
|
* Alias for the {@link #objectName} attribute, for simple default usage.
|
||||||
* attribute, for simple default usage.
|
|
||||||
*/
|
*/
|
||||||
|
@AliasFor(attribute = "objectName")
|
||||||
String value() default "";
|
String value() default "";
|
||||||
|
|
||||||
|
@AliasFor(attribute = "value")
|
||||||
String objectName() default "";
|
String objectName() default "";
|
||||||
|
|
||||||
String description() default "";
|
String description() default "";
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* 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.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO Document @AliasFor.
|
||||||
|
*
|
||||||
|
* @author Sam Brannen
|
||||||
|
* @since 4.2
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
@Documented
|
||||||
|
public @interface AliasFor {
|
||||||
|
|
||||||
|
String attribute();
|
||||||
|
|
||||||
|
Class<? extends Annotation> annotation() default Annotation.class;
|
||||||
|
|
||||||
|
}
|
|
@ -31,6 +31,8 @@ import org.springframework.core.BridgeMethodResolver;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.util.ReflectionUtils;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* General utility methods for finding annotations and meta-annotations on
|
* General utility methods for finding annotations and meta-annotations on
|
||||||
|
@ -145,7 +147,7 @@ public class AnnotatedElementUtils {
|
||||||
searchWithGetSemantics(annotation.annotationType(), annotationType, new SimpleAnnotationProcessor<Object>() {
|
searchWithGetSemantics(annotation.annotationType(), annotationType, new SimpleAnnotationProcessor<Object>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object process(Annotation annotation, int metaDepth) {
|
public Object process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
|
||||||
types.add(annotation.annotationType().getName());
|
types.add(annotation.annotationType().getName());
|
||||||
return CONTINUE;
|
return CONTINUE;
|
||||||
}
|
}
|
||||||
|
@ -153,6 +155,7 @@ public class AnnotatedElementUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Throwable ex) {
|
catch (Throwable ex) {
|
||||||
|
AnnotationUtils.rethrowAnnotationConfigurationException(ex);
|
||||||
throw new IllegalStateException("Failed to introspect annotations on " + element, ex);
|
throw new IllegalStateException("Failed to introspect annotations on " + element, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,7 +182,7 @@ public class AnnotatedElementUtils {
|
||||||
|
|
||||||
return Boolean.TRUE.equals(searchWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor<Boolean>() {
|
return Boolean.TRUE.equals(searchWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor<Boolean>() {
|
||||||
@Override
|
@Override
|
||||||
public Boolean process(Annotation annotation, int metaDepth) {
|
public Boolean process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
|
||||||
boolean found = annotation.annotationType().getName().equals(annotationType);
|
boolean found = annotation.annotationType().getName().equals(annotationType);
|
||||||
return ((found && (metaDepth > 0)) ? Boolean.TRUE : CONTINUE);
|
return ((found && (metaDepth > 0)) ? Boolean.TRUE : CONTINUE);
|
||||||
}
|
}
|
||||||
|
@ -208,7 +211,7 @@ public class AnnotatedElementUtils {
|
||||||
|
|
||||||
return Boolean.TRUE.equals(searchWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor<Boolean>() {
|
return Boolean.TRUE.equals(searchWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor<Boolean>() {
|
||||||
@Override
|
@Override
|
||||||
public Boolean process(Annotation annotation, int metaDepth) {
|
public Boolean process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
|
||||||
boolean found = annotation.annotationType().getName().equals(annotationType);
|
boolean found = annotation.annotationType().getName().equals(annotationType);
|
||||||
return (found ? Boolean.TRUE : CONTINUE);
|
return (found ? Boolean.TRUE : CONTINUE);
|
||||||
}
|
}
|
||||||
|
@ -273,8 +276,12 @@ public class AnnotatedElementUtils {
|
||||||
*/
|
*/
|
||||||
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType,
|
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType,
|
||||||
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
||||||
return searchWithGetSemantics(element, annotationType, new MergedAnnotationAttributesProcessor(annotationType,
|
|
||||||
classValuesAsString, nestedAnnotationsAsMap));
|
AnnotationAttributes attributes = searchWithGetSemantics(element, annotationType,
|
||||||
|
new MergedAnnotationAttributesProcessor(annotationType, classValuesAsString, nestedAnnotationsAsMap));
|
||||||
|
AnnotationUtils.postProcessAnnotationAttributes(element, attributes, classValuesAsString,
|
||||||
|
nestedAnnotationsAsMap);
|
||||||
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -297,7 +304,7 @@ public class AnnotatedElementUtils {
|
||||||
public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element,
|
public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element,
|
||||||
Class<? extends Annotation> annotationType) {
|
Class<? extends Annotation> annotationType) {
|
||||||
Assert.notNull(annotationType, "annotationType must not be null");
|
Assert.notNull(annotationType, "annotationType must not be null");
|
||||||
return findAnnotationAttributes(element, annotationType.getName(), false, false);
|
return findAnnotationAttributes(element, annotationType.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -357,8 +364,12 @@ public class AnnotatedElementUtils {
|
||||||
*/
|
*/
|
||||||
public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, String annotationType,
|
public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, String annotationType,
|
||||||
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
||||||
return searchWithFindSemantics(element, annotationType, new MergedAnnotationAttributesProcessor(annotationType,
|
|
||||||
classValuesAsString, nestedAnnotationsAsMap));
|
AnnotationAttributes attributes = searchWithFindSemantics(element, annotationType,
|
||||||
|
new MergedAnnotationAttributesProcessor(annotationType, classValuesAsString, nestedAnnotationsAsMap));
|
||||||
|
AnnotationUtils.postProcessAnnotationAttributes(element, attributes, classValuesAsString,
|
||||||
|
nestedAnnotationsAsMap);
|
||||||
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -417,7 +428,7 @@ public class AnnotatedElementUtils {
|
||||||
searchWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor<Void>() {
|
searchWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor<Void>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Void process(Annotation annotation, int metaDepth) {
|
public Void process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
|
||||||
boolean found = annotation.annotationType().getName().equals(annotationType);
|
boolean found = annotation.annotationType().getName().equals(annotationType);
|
||||||
if (found) {
|
if (found) {
|
||||||
AnnotationAttributes annotationAttributes = AnnotationUtils.getAnnotationAttributes(annotation,
|
AnnotationAttributes annotationAttributes = AnnotationUtils.getAnnotationAttributes(annotation,
|
||||||
|
@ -450,6 +461,7 @@ public class AnnotatedElementUtils {
|
||||||
return searchWithGetSemantics(element, annotationType, processor, new HashSet<AnnotatedElement>(), 0);
|
return searchWithGetSemantics(element, annotationType, processor, new HashSet<AnnotatedElement>(), 0);
|
||||||
}
|
}
|
||||||
catch (Throwable ex) {
|
catch (Throwable ex) {
|
||||||
|
AnnotationUtils.rethrowAnnotationConfigurationException(ex);
|
||||||
throw new IllegalStateException("Failed to introspect annotations on " + element, ex);
|
throw new IllegalStateException("Failed to introspect annotations on " + element, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -482,8 +494,8 @@ public class AnnotatedElementUtils {
|
||||||
|
|
||||||
// Start searching within locally declared annotations
|
// Start searching within locally declared annotations
|
||||||
List<Annotation> declaredAnnotations = Arrays.asList(element.getDeclaredAnnotations());
|
List<Annotation> declaredAnnotations = Arrays.asList(element.getDeclaredAnnotations());
|
||||||
T result = searchWithGetSemanticsInAnnotations(declaredAnnotations, annotationType, processor, visited,
|
T result = searchWithGetSemanticsInAnnotations(element, declaredAnnotations, annotationType, processor,
|
||||||
metaDepth);
|
visited, metaDepth);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -496,16 +508,17 @@ public class AnnotatedElementUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Continue searching within inherited annotations
|
// Continue searching within inherited annotations
|
||||||
result = searchWithGetSemanticsInAnnotations(inheritedAnnotations, annotationType, processor, visited,
|
result = searchWithGetSemanticsInAnnotations(element, inheritedAnnotations, annotationType, processor,
|
||||||
metaDepth);
|
visited, metaDepth);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
AnnotationUtils.logIntrospectionFailure(element, ex);
|
AnnotationUtils.handleIntrospectionFailure(element, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -521,6 +534,8 @@ public class AnnotatedElementUtils {
|
||||||
* {@link Processor#process process()} method of the {@link Processor}
|
* {@link Processor#process process()} method of the {@link Processor}
|
||||||
* API.
|
* API.
|
||||||
*
|
*
|
||||||
|
* @param annotatedElement the element that is annotated with the supplied
|
||||||
|
* annotations, used for contextual logging; may be {@code null} if unknown
|
||||||
* @param annotations the annotations to search in; never {@code null}
|
* @param annotations the annotations to search in; never {@code null}
|
||||||
* @param annotationType the fully qualified class name of the annotation
|
* @param annotationType the fully qualified class name of the annotation
|
||||||
* type to find; never {@code null} or empty
|
* type to find; never {@code null} or empty
|
||||||
|
@ -529,14 +544,15 @@ public class AnnotatedElementUtils {
|
||||||
* @param metaDepth the meta-depth of the annotation
|
* @param metaDepth the meta-depth of the annotation
|
||||||
* @return the result of the processor, potentially {@code null}
|
* @return the result of the processor, potentially {@code null}
|
||||||
*/
|
*/
|
||||||
private static <T> T searchWithGetSemanticsInAnnotations(List<Annotation> annotations, String annotationType,
|
private static <T> T searchWithGetSemanticsInAnnotations(AnnotatedElement annotatedElement,
|
||||||
Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth) {
|
List<Annotation> annotations, String annotationType, Processor<T> processor, Set<AnnotatedElement> visited,
|
||||||
|
int metaDepth) {
|
||||||
|
|
||||||
// Search in annotations
|
// Search in annotations
|
||||||
for (Annotation annotation : annotations) {
|
for (Annotation annotation : annotations) {
|
||||||
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)
|
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)
|
||||||
&& (annotation.annotationType().getName().equals(annotationType) || metaDepth > 0)) {
|
&& (annotation.annotationType().getName().equals(annotationType) || metaDepth > 0)) {
|
||||||
T result = processor.process(annotation, metaDepth);
|
T result = processor.process(annotatedElement, annotation, metaDepth);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -549,7 +565,7 @@ public class AnnotatedElementUtils {
|
||||||
T result = searchWithGetSemantics(annotation.annotationType(), annotationType, processor, visited,
|
T result = searchWithGetSemantics(annotation.annotationType(), annotationType, processor, visited,
|
||||||
metaDepth + 1);
|
metaDepth + 1);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
processor.postProcess(annotation, result);
|
processor.postProcess(annotatedElement, annotation, result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -599,6 +615,7 @@ public class AnnotatedElementUtils {
|
||||||
searchOnMethodsInInterfaces, searchOnMethodsInSuperclasses, processor, new HashSet<AnnotatedElement>(), 0);
|
searchOnMethodsInInterfaces, searchOnMethodsInSuperclasses, processor, new HashSet<AnnotatedElement>(), 0);
|
||||||
}
|
}
|
||||||
catch (Throwable ex) {
|
catch (Throwable ex) {
|
||||||
|
AnnotationUtils.rethrowAnnotationConfigurationException(ex);
|
||||||
throw new IllegalStateException("Failed to introspect annotations on " + element, ex);
|
throw new IllegalStateException("Failed to introspect annotations on " + element, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -646,7 +663,7 @@ public class AnnotatedElementUtils {
|
||||||
for (Annotation annotation : annotations) {
|
for (Annotation annotation : annotations) {
|
||||||
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)
|
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)
|
||||||
&& (annotation.annotationType().getName().equals(annotationType) || metaDepth > 0)) {
|
&& (annotation.annotationType().getName().equals(annotationType) || metaDepth > 0)) {
|
||||||
T result = processor.process(annotation, metaDepth);
|
T result = processor.process(element, annotation, metaDepth);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -660,7 +677,7 @@ public class AnnotatedElementUtils {
|
||||||
searchOnInterfaces, searchOnSuperclasses, searchOnMethodsInInterfaces,
|
searchOnInterfaces, searchOnSuperclasses, searchOnMethodsInInterfaces,
|
||||||
searchOnMethodsInSuperclasses, processor, visited, metaDepth + 1);
|
searchOnMethodsInSuperclasses, processor, visited, metaDepth + 1);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
processor.postProcess(annotation, result);
|
processor.postProcess(annotation.annotationType(), annotation, result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -756,7 +773,7 @@ public class AnnotatedElementUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
AnnotationUtils.logIntrospectionFailure(element, ex);
|
AnnotationUtils.handleIntrospectionFailure(element, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -840,12 +857,15 @@ public class AnnotatedElementUtils {
|
||||||
* of 0; a meta-annotation will have a depth of 1; and a
|
* of 0; a meta-annotation will have a depth of 1; and a
|
||||||
* meta-meta-annotation will have a depth of 2; etc.
|
* meta-meta-annotation will have a depth of 2; etc.
|
||||||
*
|
*
|
||||||
|
* @param annotatedElement the element that is annotated with the
|
||||||
|
* supplied annotation, used for contextual logging; may be
|
||||||
|
* {@code null} if unknown
|
||||||
* @param annotation the annotation to process
|
* @param annotation the annotation to process
|
||||||
* @param metaDepth the meta-depth of the annotation
|
* @param metaDepth the meta-depth of the annotation
|
||||||
* @return the result of the processing, or {@code null} to continue
|
* @return the result of the processing, or {@code null} to continue
|
||||||
* searching for additional annotations
|
* searching for additional annotations
|
||||||
*/
|
*/
|
||||||
T process(Annotation annotation, int metaDepth);
|
T process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Post-process the result returned by the {@link #process} method.
|
* Post-process the result returned by the {@link #process} method.
|
||||||
|
@ -855,10 +875,13 @@ public class AnnotatedElementUtils {
|
||||||
* {@link AnnotatedElement} and an invocation of {@link #process}
|
* {@link AnnotatedElement} and an invocation of {@link #process}
|
||||||
* that returned a non-null value.
|
* that returned a non-null value.
|
||||||
*
|
*
|
||||||
|
* @param annotatedElement the element that is annotated with the
|
||||||
|
* supplied annotation, used for contextual logging; may be
|
||||||
|
* {@code null} if unknown
|
||||||
* @param annotation the annotation to post-process
|
* @param annotation the annotation to post-process
|
||||||
* @param result the result to post-process
|
* @param result the result to post-process
|
||||||
*/
|
*/
|
||||||
void postProcess(Annotation annotation, T result);
|
void postProcess(AnnotatedElement annotatedElement, Annotation annotation, T result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -872,7 +895,7 @@ public class AnnotatedElementUtils {
|
||||||
* <em>No-op</em>.
|
* <em>No-op</em>.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final void postProcess(Annotation annotation, T result) {
|
public final void postProcess(AnnotatedElement annotatedElement, Annotation annotation, T result) {
|
||||||
/* no-op */
|
/* no-op */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -887,30 +910,64 @@ public class AnnotatedElementUtils {
|
||||||
*/
|
*/
|
||||||
private static class MergedAnnotationAttributesProcessor implements Processor<AnnotationAttributes> {
|
private static class MergedAnnotationAttributesProcessor implements Processor<AnnotationAttributes> {
|
||||||
|
|
||||||
private final String annotationType;
|
private final String annotationTypeName;
|
||||||
private final boolean classValuesAsString;
|
private final boolean classValuesAsString;
|
||||||
private final boolean nestedAnnotationsAsMap;
|
private final boolean nestedAnnotationsAsMap;
|
||||||
|
|
||||||
|
|
||||||
MergedAnnotationAttributesProcessor(String annotationType, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
MergedAnnotationAttributesProcessor(String annotationType, boolean classValuesAsString,
|
||||||
this.annotationType = annotationType;
|
boolean nestedAnnotationsAsMap) {
|
||||||
|
this.annotationTypeName = annotationType;
|
||||||
this.classValuesAsString = classValuesAsString;
|
this.classValuesAsString = classValuesAsString;
|
||||||
this.nestedAnnotationsAsMap = nestedAnnotationsAsMap;
|
this.nestedAnnotationsAsMap = nestedAnnotationsAsMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AnnotationAttributes process(Annotation annotation, int metaDepth) {
|
public AnnotationAttributes process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
|
||||||
boolean found = annotation.annotationType().getName().equals(annotationType);
|
boolean found = annotation.annotationType().getName().equals(this.annotationTypeName);
|
||||||
return (found ? AnnotationUtils.getAnnotationAttributes(annotation, classValuesAsString, nestedAnnotationsAsMap) : null);
|
return (found ? AnnotationUtils.getAnnotationAttributes(annotatedElement, annotation,
|
||||||
|
this.classValuesAsString, this.nestedAnnotationsAsMap, true, false) : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postProcess(Annotation annotation, AnnotationAttributes attributes) {
|
public void postProcess(AnnotatedElement element, Annotation annotation, AnnotationAttributes attributes) {
|
||||||
for (String key : attributes.keySet()) {
|
annotation = AnnotationUtils.synthesizeAnnotation(annotation);
|
||||||
if (!AnnotationUtils.VALUE.equals(key)) {
|
Class<? extends Annotation> targetAnnotationType = attributes.annotationType();
|
||||||
Object value = AnnotationUtils.getValue(annotation, key);
|
|
||||||
if (value != null) {
|
for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotation.annotationType())) {
|
||||||
attributes.put(key, AnnotationUtils.adaptValue(value, classValuesAsString, nestedAnnotationsAsMap));
|
String attributeName = attributeMethod.getName();
|
||||||
|
String aliasedAttributeName = AnnotationUtils.getAliasedAttributeName(attributeMethod,
|
||||||
|
targetAnnotationType);
|
||||||
|
|
||||||
|
// Explicit annotation attribute override declared via @AliasFor
|
||||||
|
if (StringUtils.hasText(aliasedAttributeName)) {
|
||||||
|
if (attributes.containsKey(aliasedAttributeName)) {
|
||||||
|
Object value = AnnotationUtils.getValue(annotation, attributeName);
|
||||||
|
attributes.put(aliasedAttributeName, AnnotationUtils.adaptValue(element, value,
|
||||||
|
this.classValuesAsString, this.nestedAnnotationsAsMap));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Implicit annotation attribute override based on convention
|
||||||
|
else if (!AnnotationUtils.VALUE.equals(attributeName) && attributes.containsKey(attributeName)) {
|
||||||
|
Object value = AnnotationUtils.getValue(annotation, attributeName);
|
||||||
|
Object adaptedValue = AnnotationUtils.adaptValue(element, value, this.classValuesAsString,
|
||||||
|
this.nestedAnnotationsAsMap);
|
||||||
|
attributes.put(attributeName, adaptedValue);
|
||||||
|
|
||||||
|
// If an aliased attribute defined by @AliasFor semantics does not
|
||||||
|
// already have an explicit value, ensure that the aliased attribute
|
||||||
|
// is also present in the map with a value identical to its mirror
|
||||||
|
// alias.
|
||||||
|
Method attributeMethodInTarget = ReflectionUtils.findMethod(targetAnnotationType, attributeName);
|
||||||
|
if (attributeMethodInTarget != null) {
|
||||||
|
String aliasedAttributeNameInTarget = AnnotationUtils.getAliasedAttributeName(
|
||||||
|
attributeMethodInTarget, null);
|
||||||
|
if (aliasedAttributeNameInTarget != null) {
|
||||||
|
Object aliasedValueInTarget = attributes.get(aliasedAttributeNameInTarget);
|
||||||
|
if (aliasedValueInTarget == null) {
|
||||||
|
attributes.put(aliasedAttributeNameInTarget, adaptedValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package org.springframework.core.annotation;
|
package org.springframework.core.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.Array;
|
import java.lang.reflect.Array;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
@ -25,21 +26,41 @@ import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link LinkedHashMap} subclass representing annotation attribute key/value pairs
|
* {@link LinkedHashMap} subclass representing annotation attribute key/value
|
||||||
* as read by Spring's reflection- or ASM-based {@link org.springframework.core.type.AnnotationMetadata}
|
* pairs as read by Spring's reflection- or ASM-based
|
||||||
* implementations. Provides 'pseudo-reification' to avoid noisy Map generics in the calling code
|
* {@link org.springframework.core.type.AnnotationMetadata} implementations,
|
||||||
* as well as convenience methods for looking up annotation attributes in a type-safe fashion.
|
* {@link AnnotationUtils}, and {@link AnnotatedElementUtils}.
|
||||||
|
*
|
||||||
|
* <p>Provides 'pseudo-reification' to avoid noisy Map generics in the calling
|
||||||
|
* code as well as convenience methods for looking up annotation attributes
|
||||||
|
* in a type-safe fashion.
|
||||||
*
|
*
|
||||||
* @author Chris Beams
|
* @author Chris Beams
|
||||||
|
* @author Sam Brannen
|
||||||
* @since 3.1.1
|
* @since 3.1.1
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
||||||
|
|
||||||
|
private final Class<? extends Annotation> annotationType;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new, empty {@link AnnotationAttributes} instance.
|
* Create a new, empty {@link AnnotationAttributes} instance.
|
||||||
*/
|
*/
|
||||||
public AnnotationAttributes() {
|
public AnnotationAttributes() {
|
||||||
|
this.annotationType = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new, empty {@link AnnotationAttributes} instance for the
|
||||||
|
* specified {@code annotationType}.
|
||||||
|
* @param annotationType the type of annotation represented by this
|
||||||
|
* {@code AnnotationAttributes} instance
|
||||||
|
* @since 4.2
|
||||||
|
*/
|
||||||
|
public AnnotationAttributes(Class<? extends Annotation> annotationType) {
|
||||||
|
this.annotationType = annotationType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -49,6 +70,7 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
||||||
*/
|
*/
|
||||||
public AnnotationAttributes(int initialCapacity) {
|
public AnnotationAttributes(int initialCapacity) {
|
||||||
super(initialCapacity);
|
super(initialCapacity);
|
||||||
|
this.annotationType = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -59,8 +81,18 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
||||||
*/
|
*/
|
||||||
public AnnotationAttributes(Map<String, Object> map) {
|
public AnnotationAttributes(Map<String, Object> map) {
|
||||||
super(map);
|
super(map);
|
||||||
|
this.annotationType = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the type of annotation represented by this {@code AnnotationAttributes}
|
||||||
|
* instance.
|
||||||
|
* @return the annotation type, or {@code null} if unknown
|
||||||
|
* @since 4.2
|
||||||
|
*/
|
||||||
|
public Class<? extends Annotation> annotationType() {
|
||||||
|
return this.annotationType;
|
||||||
|
}
|
||||||
|
|
||||||
public String getString(String attributeName) {
|
public String getString(String attributeName) {
|
||||||
return doGet(attributeName, String.class);
|
return doGet(attributeName, String.class);
|
||||||
|
@ -106,7 +138,9 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
||||||
Assert.hasText(attributeName, "attributeName must not be null or empty");
|
Assert.hasText(attributeName, "attributeName must not be null or empty");
|
||||||
Object value = get(attributeName);
|
Object value = get(attributeName);
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
throw new IllegalArgumentException(String.format("Attribute '%s' not found", attributeName));
|
throw new IllegalArgumentException(String.format(
|
||||||
|
"Attribute '%s' not found in attributes for annotation [%s]",
|
||||||
|
attributeName, (annotationType() != null ? annotationType.getName() : "unknown")));
|
||||||
}
|
}
|
||||||
if (!expectedType.isInstance(value)) {
|
if (!expectedType.isInstance(value)) {
|
||||||
if (expectedType.isArray() && expectedType.getComponentType().isInstance(value)) {
|
if (expectedType.isArray() && expectedType.getComponentType().isInstance(value)) {
|
||||||
|
@ -115,9 +149,10 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
||||||
value = arrayValue;
|
value = arrayValue;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(String.format(
|
||||||
String.format("Attribute '%s' is of type [%s], but [%s] was expected.",
|
"Attribute '%s' is of type [%s], but [%s] was expected in attributes for annotation [%s]",
|
||||||
attributeName, value.getClass().getSimpleName(), expectedType.getSimpleName()));
|
attributeName, value.getClass().getSimpleName(), expectedType.getSimpleName(),
|
||||||
|
(annotationType() != null ? annotationType.getName() : "unknown")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (T) value;
|
return (T) value;
|
||||||
|
@ -150,10 +185,11 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an {@link AnnotationAttributes} instance based on the given map; if the map
|
* Return an {@link AnnotationAttributes} instance based on the given map.
|
||||||
* is already an {@code AnnotationAttributes} instance, it is casted and returned
|
* <p>If the map is already an {@code AnnotationAttributes} instance, it
|
||||||
* immediately without creating any new instance; otherwise create a new instance by
|
* will be cast and returned immediately without creating a new instance.
|
||||||
* wrapping the map with the {@link #AnnotationAttributes(Map)} constructor.
|
* Otherwise a new instance will be created by passing the supplied map
|
||||||
|
* to the {@link #AnnotationAttributes(Map)} constructor.
|
||||||
* @param map original source of annotation attribute key/value pairs
|
* @param map original source of annotation attribute key/value pairs
|
||||||
*/
|
*/
|
||||||
public static AnnotationAttributes fromMap(Map<String, Object> map) {
|
public static AnnotationAttributes fromMap(Map<String, Object> map) {
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown by {@link AnnotationUtils} if an annotation is improperly configured.
|
||||||
|
*
|
||||||
|
* @author Sam Brannen
|
||||||
|
* @since 4.2
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class AnnotationConfigurationException extends RuntimeException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new {@code AnnotationConfigurationException} with the
|
||||||
|
* supplied message.
|
||||||
|
* @param message the detail message
|
||||||
|
*/
|
||||||
|
public AnnotationConfigurationException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new {@code AnnotationConfigurationException} with the
|
||||||
|
* supplied message and cause.
|
||||||
|
* @param message the detail message
|
||||||
|
* @param cause the root cause
|
||||||
|
*/
|
||||||
|
public AnnotationConfigurationException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -18,9 +18,13 @@ package org.springframework.core.annotation;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.AnnotatedElement;
|
import java.lang.reflect.AnnotatedElement;
|
||||||
|
import java.lang.reflect.InvocationHandler;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Arrays;
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -29,9 +33,9 @@ import java.util.Set;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
import org.springframework.core.BridgeMethodResolver;
|
import org.springframework.core.BridgeMethodResolver;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
import org.springframework.util.ReflectionUtils;
|
import org.springframework.util.ReflectionUtils;
|
||||||
|
@ -88,9 +92,17 @@ import org.springframework.util.StringUtils;
|
||||||
*/
|
*/
|
||||||
public abstract class AnnotationUtils {
|
public abstract class AnnotationUtils {
|
||||||
|
|
||||||
/** The attribute name for annotations with a single element */
|
/**
|
||||||
|
* The attribute name for annotations with a single element.
|
||||||
|
*/
|
||||||
public static final String VALUE = "value";
|
public static final String VALUE = "value";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A object that can be stored in {@link AnnotationAttributes} as a
|
||||||
|
* placeholder for an attribute's declared default value.
|
||||||
|
*/
|
||||||
|
public static final Object DEFAULT_VALUE_PLACEHOLDER = "<SPRING DEFAULT VALUE PLACEHOLDER>";
|
||||||
|
|
||||||
|
|
||||||
private static final Map<AnnotationCacheKey, Annotation> findAnnotationCache =
|
private static final Map<AnnotationCacheKey, Annotation> findAnnotationCache =
|
||||||
new ConcurrentReferenceHashMap<AnnotationCacheKey, Annotation>(256);
|
new ConcurrentReferenceHashMap<AnnotationCacheKey, Annotation>(256);
|
||||||
|
@ -116,14 +128,15 @@ public abstract class AnnotationUtils {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static <A extends Annotation> A getAnnotation(Annotation ann, Class<A> annotationType) {
|
public static <A extends Annotation> A getAnnotation(Annotation ann, Class<A> annotationType) {
|
||||||
if (annotationType.isInstance(ann)) {
|
if (annotationType.isInstance(ann)) {
|
||||||
return (A) ann;
|
return synthesizeAnnotation((A) ann);
|
||||||
}
|
}
|
||||||
|
Class<? extends Annotation> annotatedElement = ann.annotationType();
|
||||||
try {
|
try {
|
||||||
return ann.annotationType().getAnnotation(annotationType);
|
return synthesizeAnnotation(annotatedElement, annotatedElement.getAnnotation(annotationType));
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
// Assuming nested Class values not resolvable within annotation attributes...
|
// Assuming nested Class values not resolvable within annotation attributes...
|
||||||
logIntrospectionFailure(ann.annotationType(), ex);
|
handleIntrospectionFailure(annotatedElement, ex);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,11 +164,11 @@ public abstract class AnnotationUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ann;
|
return synthesizeAnnotation(annotatedElement, ann);
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
// Assuming nested Class values not resolvable within annotation attributes...
|
// Assuming nested Class values not resolvable within annotation attributes...
|
||||||
logIntrospectionFailure(annotatedElement, ex);
|
handleIntrospectionFailure(annotatedElement, ex);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,7 +208,7 @@ public abstract class AnnotationUtils {
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
// Assuming nested Class values not resolvable within annotation attributes...
|
// Assuming nested Class values not resolvable within annotation attributes...
|
||||||
logIntrospectionFailure(annotatedElement, ex);
|
handleIntrospectionFailure(annotatedElement, ex);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -218,7 +231,7 @@ public abstract class AnnotationUtils {
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
// Assuming nested Class values not resolvable within annotation attributes...
|
// Assuming nested Class values not resolvable within annotation attributes...
|
||||||
logIntrospectionFailure(method, ex);
|
handleIntrospectionFailure(method, ex);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -274,7 +287,7 @@ public abstract class AnnotationUtils {
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
// Assuming nested Class values not resolvable within annotation attributes...
|
// Assuming nested Class values not resolvable within annotation attributes...
|
||||||
logIntrospectionFailure(annotatedElement, ex);
|
handleIntrospectionFailure(annotatedElement, ex);
|
||||||
}
|
}
|
||||||
return Collections.emptySet();
|
return Collections.emptySet();
|
||||||
}
|
}
|
||||||
|
@ -298,7 +311,8 @@ public abstract class AnnotationUtils {
|
||||||
public static <A extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) {
|
public static <A extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) {
|
||||||
// Do NOT store result in the findAnnotationCache since doing so could break
|
// Do NOT store result in the findAnnotationCache since doing so could break
|
||||||
// findAnnotation(Class, Class) and findAnnotation(Method, Class).
|
// findAnnotation(Class, Class) and findAnnotation(Method, Class).
|
||||||
return findAnnotation(annotatedElement, annotationType, new HashSet<Annotation>());
|
return synthesizeAnnotation(annotatedElement,
|
||||||
|
findAnnotation(annotatedElement, annotationType, new HashSet<Annotation>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -332,7 +346,7 @@ public abstract class AnnotationUtils {
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
// Assuming nested Class values not resolvable within annotation attributes...
|
// Assuming nested Class values not resolvable within annotation attributes...
|
||||||
logIntrospectionFailure(annotatedElement, ex);
|
handleIntrospectionFailure(annotatedElement, ex);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -389,7 +403,7 @@ public abstract class AnnotationUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return synthesizeAnnotation(method, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <A extends Annotation> A searchOnInterfaces(Method method, Class<A> annotationType, Class<?>... ifcs) {
|
private static <A extends Annotation> A searchOnInterfaces(Method method, Class<A> annotationType, Class<?>... ifcs) {
|
||||||
|
@ -426,7 +440,7 @@ public abstract class AnnotationUtils {
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
// Assuming nested Class values not resolvable within annotation attributes...
|
// Assuming nested Class values not resolvable within annotation attributes...
|
||||||
logIntrospectionFailure(ifcMethod, ex);
|
handleIntrospectionFailure(ifcMethod, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
annotatedInterfaceCache.put(iface, found);
|
annotatedInterfaceCache.put(iface, found);
|
||||||
|
@ -465,7 +479,7 @@ public abstract class AnnotationUtils {
|
||||||
findAnnotationCache.put(cacheKey, result);
|
findAnnotationCache.put(cacheKey, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return synthesizeAnnotation(clazz, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -499,7 +513,7 @@ public abstract class AnnotationUtils {
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
// Assuming nested Class values not resolvable within annotation attributes...
|
// Assuming nested Class values not resolvable within annotation attributes...
|
||||||
logIntrospectionFailure(clazz, ex);
|
handleIntrospectionFailure(clazz, ex);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -617,7 +631,7 @@ public abstract class AnnotationUtils {
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
// Assuming nested Class values not resolvable within annotation attributes...
|
// Assuming nested Class values not resolvable within annotation attributes...
|
||||||
logIntrospectionFailure(clazz, ex);
|
handleIntrospectionFailure(clazz, ex);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -682,9 +696,10 @@ public abstract class AnnotationUtils {
|
||||||
* @return the Map of annotation attributes, with attribute names as keys and
|
* @return the Map of annotation attributes, with attribute names as keys and
|
||||||
* corresponding attribute values as values; never {@code null}
|
* corresponding attribute values as values; never {@code null}
|
||||||
* @see #getAnnotationAttributes(Annotation, boolean, boolean)
|
* @see #getAnnotationAttributes(Annotation, boolean, boolean)
|
||||||
|
* @see #getAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean)
|
||||||
*/
|
*/
|
||||||
public static Map<String, Object> getAnnotationAttributes(Annotation annotation) {
|
public static Map<String, Object> getAnnotationAttributes(Annotation annotation) {
|
||||||
return getAnnotationAttributes(annotation, false, false);
|
return getAnnotationAttributes(null, annotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -706,8 +721,7 @@ public abstract class AnnotationUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the given annotation's attributes as an {@link AnnotationAttributes}
|
* Retrieve the given annotation's attributes as an {@link AnnotationAttributes} map.
|
||||||
* map structure.
|
|
||||||
* <p>This method provides fully recursive annotation reading capabilities on par with
|
* <p>This method provides fully recursive annotation reading capabilities on par with
|
||||||
* the reflection-based {@link org.springframework.core.type.StandardAnnotationMetadata}.
|
* the reflection-based {@link org.springframework.core.type.StandardAnnotationMetadata}.
|
||||||
* @param annotation the annotation to retrieve the attributes for
|
* @param annotation the annotation to retrieve the attributes for
|
||||||
|
@ -724,19 +738,106 @@ public abstract class AnnotationUtils {
|
||||||
*/
|
*/
|
||||||
public static AnnotationAttributes getAnnotationAttributes(Annotation annotation, boolean classValuesAsString,
|
public static AnnotationAttributes getAnnotationAttributes(Annotation annotation, boolean classValuesAsString,
|
||||||
boolean nestedAnnotationsAsMap) {
|
boolean nestedAnnotationsAsMap) {
|
||||||
|
return getAnnotationAttributes(null, annotation, classValuesAsString, nestedAnnotationsAsMap);
|
||||||
|
}
|
||||||
|
|
||||||
AnnotationAttributes attrs = new AnnotationAttributes();
|
/**
|
||||||
Method[] methods = annotation.annotationType().getDeclaredMethods();
|
* Retrieve the given annotation's attributes as an {@link AnnotationAttributes} map.
|
||||||
for (Method method : methods) {
|
* <p>Equivalent to calling {@link #getAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean)}
|
||||||
if (method.getParameterTypes().length == 0 && method.getReturnType() != void.class) {
|
* with the {@code classValuesAsString} and {@code nestedAnnotationsAsMap} parameters
|
||||||
try {
|
* set to {@code false}.
|
||||||
ReflectionUtils.makeAccessible(method);
|
* @param annotation the annotation to retrieve the attributes for
|
||||||
Object value = method.invoke(annotation);
|
* @return the Map of annotation attributes, with attribute names as keys and
|
||||||
attrs.put(method.getName(), adaptValue(value, classValuesAsString, nestedAnnotationsAsMap));
|
* corresponding attribute values as values; never {@code null}
|
||||||
|
* @see #getAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean)
|
||||||
|
* @since 4.2
|
||||||
|
*/
|
||||||
|
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement annotatedElement, Annotation annotation) {
|
||||||
|
return getAnnotationAttributes(annotatedElement, annotation, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the given annotation's attributes as an {@link AnnotationAttributes} map.
|
||||||
|
* <p>This method provides fully recursive annotation reading capabilities on par with
|
||||||
|
* the reflection-based {@link org.springframework.core.type.StandardAnnotationMetadata}.
|
||||||
|
* @param annotatedElement the element that is annotated with the supplied annotation,
|
||||||
|
* used for contextual logging; may be {@code null} if unknown
|
||||||
|
* @param annotation the annotation to retrieve the attributes for
|
||||||
|
* @param classValuesAsString whether to convert Class references into Strings (for
|
||||||
|
* compatibility with {@link org.springframework.core.type.AnnotationMetadata})
|
||||||
|
* or to preserve them as Class references
|
||||||
|
* @param nestedAnnotationsAsMap whether to convert nested Annotation instances into
|
||||||
|
* {@link AnnotationAttributes} maps (for compatibility with
|
||||||
|
* {@link org.springframework.core.type.AnnotationMetadata}) or to preserve them as
|
||||||
|
* Annotation instances
|
||||||
|
* @param defaultValuesAsPlaceholder whether to replace default values with
|
||||||
|
* {@link #DEFAULT_VALUE_PLACEHOLDER} or leave them as is
|
||||||
|
* @return the annotation attributes (a specialized Map) with attribute names as keys
|
||||||
|
* and corresponding attribute values as values; never {@code null}
|
||||||
|
* @since 4.2
|
||||||
|
*/
|
||||||
|
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement annotatedElement,
|
||||||
|
Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
||||||
|
|
||||||
|
return getAnnotationAttributes(annotatedElement, annotation, classValuesAsString, nestedAnnotationsAsMap,
|
||||||
|
false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the given annotation's attributes as an {@link AnnotationAttributes} map.
|
||||||
|
*
|
||||||
|
* <p>This method provides fully recursive annotation reading capabilities on par with
|
||||||
|
* the reflection-based {@link org.springframework.core.type.StandardAnnotationMetadata}.
|
||||||
|
*
|
||||||
|
* @param annotatedElement the element that is annotated with the supplied annotation,
|
||||||
|
* used for contextual logging; may be {@code null} if unknown
|
||||||
|
* @param annotation the annotation to retrieve the attributes for
|
||||||
|
* @param classValuesAsString whether to convert Class references into Strings (for
|
||||||
|
* compatibility with {@link org.springframework.core.type.AnnotationMetadata})
|
||||||
|
* or to preserve them as Class references
|
||||||
|
* @param nestedAnnotationsAsMap whether to convert nested Annotation instances into
|
||||||
|
* {@link AnnotationAttributes} maps (for compatibility with
|
||||||
|
* {@link org.springframework.core.type.AnnotationMetadata}) or to preserve them as
|
||||||
|
* Annotation instances
|
||||||
|
* @param defaultValuesAsPlaceholder whether to replace default values with
|
||||||
|
* {@link #DEFAULT_VALUE_PLACEHOLDER} or leave them as is
|
||||||
|
* @param synthesizeAnnotation whether or not the annotation should be
|
||||||
|
* {@linkplain #synthesizeAnnotation synthesized} before processing
|
||||||
|
* @return the annotation attributes (a specialized Map) with attribute names as keys
|
||||||
|
* and corresponding attribute values as values; never {@code null}
|
||||||
|
* @since 4.2
|
||||||
|
*/
|
||||||
|
static AnnotationAttributes getAnnotationAttributes(AnnotatedElement annotatedElement,
|
||||||
|
Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap,
|
||||||
|
boolean defaultValuesAsPlaceholder, boolean synthesizeAnnotation) {
|
||||||
|
|
||||||
|
if (synthesizeAnnotation) {
|
||||||
|
annotation = synthesizeAnnotation(annotatedElement, annotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<? extends Annotation> annotationType = annotation.annotationType();
|
||||||
|
AnnotationAttributes attrs = new AnnotationAttributes(annotationType);
|
||||||
|
for (Method method : getAttributeMethods(annotationType)) {
|
||||||
|
try {
|
||||||
|
ReflectionUtils.makeAccessible(method);
|
||||||
|
Object value = method.invoke(annotation);
|
||||||
|
|
||||||
|
Object defaultValue = method.getDefaultValue();
|
||||||
|
if (defaultValuesAsPlaceholder && (defaultValue != null)) {
|
||||||
|
if (ObjectUtils.nullSafeEquals(value, defaultValue)) {
|
||||||
|
value = DEFAULT_VALUE_PLACEHOLDER;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
|
||||||
throw new IllegalStateException("Could not obtain annotation attribute values", ex);
|
attrs.put(method.getName(),
|
||||||
|
adaptValue(annotatedElement, value, classValuesAsString, nestedAnnotationsAsMap));
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
if (ex instanceof InvocationTargetException) {
|
||||||
|
Throwable targetException = ((InvocationTargetException) ex).getTargetException();
|
||||||
|
rethrowAnnotationConfigurationException(targetException);
|
||||||
}
|
}
|
||||||
|
throw new IllegalStateException("Could not obtain annotation attribute value for " + method, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return attrs;
|
return attrs;
|
||||||
|
@ -744,6 +845,10 @@ public abstract class AnnotationUtils {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapt the given value according to the given class and nested annotation settings.
|
* Adapt the given value according to the given class and nested annotation settings.
|
||||||
|
* <p>Nested annotations will be
|
||||||
|
* {@linkplain #synthesizeAnnotation(AnnotatedElement, Annotation) synthesized}.
|
||||||
|
* @param annotatedElement the element that is annotated, used for contextual
|
||||||
|
* logging; may be {@code null} if unknown
|
||||||
* @param value the annotation attribute value
|
* @param value the annotation attribute value
|
||||||
* @param classValuesAsString whether to turn Class references into Strings (for
|
* @param classValuesAsString whether to turn Class references into Strings (for
|
||||||
* compatibility with {@link org.springframework.core.type.AnnotationMetadata})
|
* compatibility with {@link org.springframework.core.type.AnnotationMetadata})
|
||||||
|
@ -754,34 +859,57 @@ public abstract class AnnotationUtils {
|
||||||
* Annotation instances
|
* Annotation instances
|
||||||
* @return the adapted value, or the original value if no adaptation is needed
|
* @return the adapted value, or the original value if no adaptation is needed
|
||||||
*/
|
*/
|
||||||
static Object adaptValue(Object value, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
static Object adaptValue(AnnotatedElement annotatedElement, Object value, boolean classValuesAsString,
|
||||||
|
boolean nestedAnnotationsAsMap) {
|
||||||
|
|
||||||
if (classValuesAsString) {
|
if (classValuesAsString) {
|
||||||
if (value instanceof Class) {
|
if (value instanceof Class) {
|
||||||
value = ((Class<?>) value).getName();
|
return ((Class<?>) value).getName();
|
||||||
}
|
}
|
||||||
else if (value instanceof Class[]) {
|
else if (value instanceof Class[]) {
|
||||||
Class<?>[] clazzArray = (Class<?>[]) value;
|
Class<?>[] clazzArray = (Class<?>[]) value;
|
||||||
String[] newValue = new String[clazzArray.length];
|
String[] classNames = new String[clazzArray.length];
|
||||||
for (int i = 0; i < clazzArray.length; i++) {
|
for (int i = 0; i < clazzArray.length; i++) {
|
||||||
newValue[i] = clazzArray[i].getName();
|
classNames[i] = clazzArray[i].getName();
|
||||||
}
|
}
|
||||||
value = newValue;
|
return classNames;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (nestedAnnotationsAsMap && value instanceof Annotation) {
|
|
||||||
return getAnnotationAttributes((Annotation) value, classValuesAsString, true);
|
if (value instanceof Annotation) {
|
||||||
}
|
Annotation annotation = (Annotation) value;
|
||||||
else if (nestedAnnotationsAsMap && value instanceof Annotation[]) {
|
|
||||||
Annotation[] realAnnotations = (Annotation[]) value;
|
if (nestedAnnotationsAsMap) {
|
||||||
AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[realAnnotations.length];
|
return getAnnotationAttributes(annotatedElement, annotation, classValuesAsString,
|
||||||
for (int i = 0; i < realAnnotations.length; i++) {
|
nestedAnnotationsAsMap);
|
||||||
mappedAnnotations[i] = getAnnotationAttributes(realAnnotations[i], classValuesAsString, true);
|
}
|
||||||
|
else {
|
||||||
|
return synthesizeAnnotation(annotatedElement, annotation);
|
||||||
}
|
}
|
||||||
return mappedAnnotations;
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
return value;
|
if (value instanceof Annotation[]) {
|
||||||
|
Annotation[] annotations = (Annotation[]) value;
|
||||||
|
|
||||||
|
if (nestedAnnotationsAsMap) {
|
||||||
|
AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[annotations.length];
|
||||||
|
for (int i = 0; i < annotations.length; i++) {
|
||||||
|
mappedAnnotations[i] = getAnnotationAttributes(annotatedElement, annotations[i],
|
||||||
|
classValuesAsString, nestedAnnotationsAsMap);
|
||||||
|
}
|
||||||
|
return mappedAnnotations;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Annotation[] synthesizedAnnotations = new Annotation[annotations.length];
|
||||||
|
for (int i = 0; i < annotations.length; i++) {
|
||||||
|
synthesizedAnnotations[i] = synthesizeAnnotation(annotatedElement, annotations[i]);
|
||||||
|
}
|
||||||
|
return synthesizedAnnotations;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fallback
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -803,7 +931,7 @@ public abstract class AnnotationUtils {
|
||||||
* @see #getValue(Annotation)
|
* @see #getValue(Annotation)
|
||||||
*/
|
*/
|
||||||
public static Object getValue(Annotation annotation, String attributeName) {
|
public static Object getValue(Annotation annotation, String attributeName) {
|
||||||
if (annotation == null || !StringUtils.hasLength(attributeName)) {
|
if (annotation == null || !StringUtils.hasText(attributeName)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -861,7 +989,7 @@ public abstract class AnnotationUtils {
|
||||||
* @see #getDefaultValue(Annotation, String)
|
* @see #getDefaultValue(Annotation, String)
|
||||||
*/
|
*/
|
||||||
public static Object getDefaultValue(Class<? extends Annotation> annotationType, String attributeName) {
|
public static Object getDefaultValue(Class<? extends Annotation> annotationType, String attributeName) {
|
||||||
if (annotationType == null || !StringUtils.hasLength(attributeName)) {
|
if (annotationType == null || !StringUtils.hasText(attributeName)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -872,14 +1000,343 @@ public abstract class AnnotationUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO Document synthesizeAnnotation().
|
||||||
|
*
|
||||||
|
* @param annotation the annotation to synthesize
|
||||||
|
* @since 4.2
|
||||||
|
* @see #synthesizeAnnotation(AnnotatedElement, Annotation)
|
||||||
|
*/
|
||||||
|
public static <A extends Annotation> A synthesizeAnnotation(A annotation) {
|
||||||
|
return synthesizeAnnotation(null, annotation);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log an introspection failure (in particular {@code TypeNotPresentExceptions}) -
|
* TODO Document synthesizeAnnotation().
|
||||||
* before moving on, pretending there were no annotations on this specific element.
|
*
|
||||||
|
* @param annotatedElement the element that is annotated with the supplied
|
||||||
|
* annotation, used for contextual logging; may be {@code null} if unknown
|
||||||
|
* @param annotation the annotation to synthesize
|
||||||
|
* @since 4.2
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <A extends Annotation> A synthesizeAnnotation(AnnotatedElement annotatedElement, A annotation) {
|
||||||
|
if (annotation == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (annotation instanceof SynthesizedAnnotation) {
|
||||||
|
return annotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<? extends Annotation> annotationType = annotation.annotationType();
|
||||||
|
|
||||||
|
// No need to synthesize?
|
||||||
|
if (!isSynthesizable(annotationType)) {
|
||||||
|
return annotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
InvocationHandler handler = new SynthesizedAnnotationInvocationHandler(annotatedElement, annotation, getAliasMap(annotationType));
|
||||||
|
A synthesizedAnnotation = (A) Proxy.newProxyInstance(ClassUtils.getDefaultClassLoader(), new Class<?>[] {
|
||||||
|
(Class<A>) annotationType, SynthesizedAnnotation.class }, handler);
|
||||||
|
|
||||||
|
return synthesizedAnnotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO Document getAliasMap().
|
||||||
|
* @since 4.2
|
||||||
|
*/
|
||||||
|
private static Map<String, String> getAliasMap(Class<? extends Annotation> annotationType) {
|
||||||
|
if (annotationType == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> map = new HashMap<String, String>();
|
||||||
|
for (Method attribute : getAttributeMethods(annotationType)) {
|
||||||
|
String attributeName = attribute.getName();
|
||||||
|
String aliasedAttributeName = getAliasedAttributeName(attribute);
|
||||||
|
if (aliasedAttributeName != null) {
|
||||||
|
map.put(attributeName, aliasedAttributeName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO Document isSynthesizable().
|
||||||
|
* @since 4.2
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static boolean isSynthesizable(Class<? extends Annotation> annotationType) {
|
||||||
|
|
||||||
|
for (Method attribute : getAttributeMethods(annotationType)) {
|
||||||
|
if (getAliasedAttributeName(attribute) != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> returnType = attribute.getReturnType();
|
||||||
|
|
||||||
|
if (Annotation[].class.isAssignableFrom(returnType)) {
|
||||||
|
Class<? extends Annotation> nestedAnnotationType = (Class<? extends Annotation>) returnType.getComponentType();
|
||||||
|
if (isSynthesizable(nestedAnnotationType)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Annotation.class.isAssignableFrom(returnType)) {
|
||||||
|
Class<? extends Annotation> nestedAnnotationType = (Class<? extends Annotation>) returnType;
|
||||||
|
if (isSynthesizable(nestedAnnotationType)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of the aliased attribute configured via
|
||||||
|
* {@link AliasFor @AliasFor} on the supplied annotation {@code attribute}.
|
||||||
|
*
|
||||||
|
* <p>This method does not resolve aliases in other annotations. In
|
||||||
|
* other words, if {@code @AliasFor} is present on the supplied
|
||||||
|
* {@code attribute} but {@linkplain AliasFor#annotation references an
|
||||||
|
* annotation} other than {@link Annotation}, this method will return
|
||||||
|
* {@code null} immediately.
|
||||||
|
*
|
||||||
|
* @param attribute the attribute to find an alias for
|
||||||
|
* @return the name of the aliased attribute, or {@code null} if not found
|
||||||
|
* @throws IllegalArgumentException if the supplied attribute method is
|
||||||
|
* not from an annotation, or if the supplied target type is {@link Annotation}
|
||||||
|
* @throws AnnotationConfigurationException if invalid configuration of
|
||||||
|
* {@code @AliasFor} is detected
|
||||||
|
* @see #getAliasedAttributeName(Method, Class)
|
||||||
|
* @since 4.2
|
||||||
|
*/
|
||||||
|
static String getAliasedAttributeName(Method attribute) {
|
||||||
|
return getAliasedAttributeName(attribute, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of the aliased attribute configured via
|
||||||
|
* {@link AliasFor @AliasFor} on the supplied annotation {@code attribute}.
|
||||||
|
*
|
||||||
|
* @param attribute the attribute to find an alias for
|
||||||
|
* @param targetAnnotationType the type of annotation in which the
|
||||||
|
* aliased attribute is allowed to be declared; {@code null} implies
|
||||||
|
* <em>within the same annotation</em>
|
||||||
|
* @return the name of the aliased attribute, or {@code null} if not found
|
||||||
|
* @throws IllegalArgumentException if the supplied attribute method is
|
||||||
|
* not from an annotation, or if the supplied target type is {@link Annotation}
|
||||||
|
* @throws AnnotationConfigurationException if invalid configuration of
|
||||||
|
* {@code @AliasFor} is detected
|
||||||
|
* @since 4.2
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
static String getAliasedAttributeName(Method attribute, Class<? extends Annotation> targetAnnotationType) {
|
||||||
|
Class<?> declaringClass = attribute.getDeclaringClass();
|
||||||
|
Assert.isTrue(declaringClass.isAnnotation(), "attribute method must be from an annotation");
|
||||||
|
Assert.isTrue(!Annotation.class.equals(targetAnnotationType),
|
||||||
|
"targetAnnotationType must not be java.lang.annotation.Annotation");
|
||||||
|
|
||||||
|
AliasFor aliasFor = attribute.getAnnotation(AliasFor.class);
|
||||||
|
|
||||||
|
// Nothing to check
|
||||||
|
if (aliasFor == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<? extends Annotation> sourceAnnotationType = (Class<? extends Annotation>) declaringClass;
|
||||||
|
Class<? extends Annotation> aliasedAnnotationType = aliasFor.annotation();
|
||||||
|
|
||||||
|
boolean searchWithinSameAnnotation = (targetAnnotationType == null);
|
||||||
|
boolean sameTargetDeclared = (sourceAnnotationType.equals(aliasedAnnotationType) || Annotation.class.equals(aliasedAnnotationType));
|
||||||
|
|
||||||
|
// Wrong search scope?
|
||||||
|
if (searchWithinSameAnnotation && !sameTargetDeclared) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String attributeName = attribute.getName();
|
||||||
|
String aliasedAttributeName = aliasFor.attribute();
|
||||||
|
|
||||||
|
if (!StringUtils.hasText(aliasedAttributeName)) {
|
||||||
|
String msg = String.format(
|
||||||
|
"@AliasFor declaration on attribute [%s] in annotation [%s] is missing required 'attribute' value.",
|
||||||
|
attributeName, sourceAnnotationType.getName());
|
||||||
|
throw new AnnotationConfigurationException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sameTargetDeclared) {
|
||||||
|
aliasedAnnotationType = sourceAnnotationType;
|
||||||
|
}
|
||||||
|
|
||||||
|
Method aliasedAttribute = null;
|
||||||
|
try {
|
||||||
|
aliasedAttribute = aliasedAnnotationType.getDeclaredMethod(aliasedAttributeName);
|
||||||
|
}
|
||||||
|
catch (NoSuchMethodException e) {
|
||||||
|
String msg = String.format(
|
||||||
|
"Attribute [%s] in annotation [%s] is declared as an @AliasFor nonexistent attribute [%s] in annotation [%s].",
|
||||||
|
attributeName, sourceAnnotationType.getName(), aliasedAttributeName, aliasedAnnotationType.getName());
|
||||||
|
throw new AnnotationConfigurationException(msg, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sameTargetDeclared) {
|
||||||
|
AliasFor mirrorAliasFor = aliasedAttribute.getAnnotation(AliasFor.class);
|
||||||
|
if (mirrorAliasFor == null) {
|
||||||
|
String msg = String.format("Attribute [%s] in annotation [%s] must be declared as an @AliasFor [%s].",
|
||||||
|
aliasedAttributeName, sourceAnnotationType.getName(), attributeName);
|
||||||
|
throw new AnnotationConfigurationException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
String mirrorAliasedAttributeName = mirrorAliasFor.attribute();
|
||||||
|
if (!attributeName.equals(mirrorAliasedAttributeName)) {
|
||||||
|
String msg = String.format(
|
||||||
|
"Attribute [%s] in annotation [%s] must be declared as an @AliasFor [%s], not [%s].",
|
||||||
|
aliasedAttributeName, sourceAnnotationType.getName(), attributeName, mirrorAliasedAttributeName);
|
||||||
|
throw new AnnotationConfigurationException(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> returnType = attribute.getReturnType();
|
||||||
|
Class<?> aliasedReturnType = aliasedAttribute.getReturnType();
|
||||||
|
if (!returnType.equals(aliasedReturnType)) {
|
||||||
|
String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] "
|
||||||
|
+ "and attribute [%s] in annotation [%s] must declare the same return type.", attributeName,
|
||||||
|
sourceAnnotationType.getName(), aliasedAttributeName, aliasedAnnotationType.getName());
|
||||||
|
throw new AnnotationConfigurationException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sameTargetDeclared) {
|
||||||
|
Object defaultValue = attribute.getDefaultValue();
|
||||||
|
Object aliasedDefaultValue = aliasedAttribute.getDefaultValue();
|
||||||
|
|
||||||
|
if ((defaultValue == null) || (aliasedDefaultValue == null)) {
|
||||||
|
String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] "
|
||||||
|
+ "and attribute [%s] in annotation [%s] must declare default values.", attributeName,
|
||||||
|
sourceAnnotationType.getName(), aliasedAttributeName, aliasedAnnotationType.getName());
|
||||||
|
throw new AnnotationConfigurationException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ObjectUtils.nullSafeEquals(defaultValue, aliasedDefaultValue)) {
|
||||||
|
String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] "
|
||||||
|
+ "and attribute [%s] in annotation [%s] must declare the same default value.", attributeName,
|
||||||
|
sourceAnnotationType.getName(), aliasedAttributeName, aliasedAnnotationType.getName());
|
||||||
|
throw new AnnotationConfigurationException(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return aliasedAttributeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO Document getAttributeMethods().
|
||||||
|
*
|
||||||
|
* @since 4.2
|
||||||
|
*/
|
||||||
|
static List<Method> getAttributeMethods(Class<? extends Annotation> annotationType) {
|
||||||
|
List<Method> methods = new ArrayList<Method>();
|
||||||
|
for (Method method : annotationType.getDeclaredMethods()) {
|
||||||
|
if ((method.getParameterTypes().length == 0) && (method.getReturnType() != void.class)) {
|
||||||
|
methods.add(method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return methods;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO Document postProcessAnnotationAttributes().
|
||||||
|
*
|
||||||
|
* @param annotatedElement the element that is annotated with the supplied
|
||||||
|
* annotation, used for contextual logging; may be {@code null} if unknown
|
||||||
|
* @param attributes the annotation attributes to validate
|
||||||
|
* @since 4.2
|
||||||
|
*/
|
||||||
|
static void postProcessAnnotationAttributes(AnnotatedElement element, AnnotationAttributes attributes,
|
||||||
|
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
||||||
|
|
||||||
|
// Abort?
|
||||||
|
if (attributes == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<? extends Annotation> annotationType = attributes.annotationType();
|
||||||
|
Map<String, String> aliasMap = getAliasMap(annotationType);
|
||||||
|
|
||||||
|
// Validate @AliasFor configuration
|
||||||
|
if (aliasMap != null) {
|
||||||
|
Set<String> validated = new HashSet<String>();
|
||||||
|
|
||||||
|
for (String attributeName : aliasMap.keySet()) {
|
||||||
|
String aliasedAttributeName = aliasMap.get(attributeName);
|
||||||
|
|
||||||
|
if (validated.add(attributeName) && validated.add(aliasedAttributeName)) {
|
||||||
|
Object value = attributes.get(attributeName);
|
||||||
|
Object aliasedValue = attributes.get(aliasedAttributeName);
|
||||||
|
|
||||||
|
if (!ObjectUtils.nullSafeEquals(value, aliasedValue) && !DEFAULT_VALUE_PLACEHOLDER.equals(value)
|
||||||
|
&& !DEFAULT_VALUE_PLACEHOLDER.equals(aliasedValue)) {
|
||||||
|
String elementAsString = (element == null ? "unknown element" : element.toString());
|
||||||
|
String msg = String.format(
|
||||||
|
"In AnnotationAttributes for annotation [%s] declared on [%s], attribute [%s] and its alias [%s] are "
|
||||||
|
+ "declared with values of [%s] and [%s], but only one declaration is permitted.",
|
||||||
|
annotationType.getName(), elementAsString, attributeName, aliasedAttributeName,
|
||||||
|
ObjectUtils.nullSafeToString(value), ObjectUtils.nullSafeToString(aliasedValue));
|
||||||
|
throw new AnnotationConfigurationException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace default values with aliased values...
|
||||||
|
if (DEFAULT_VALUE_PLACEHOLDER.equals(value)) {
|
||||||
|
attributes.put(attributeName,
|
||||||
|
adaptValue(element, aliasedValue, classValuesAsString, nestedAnnotationsAsMap));
|
||||||
|
}
|
||||||
|
if (DEFAULT_VALUE_PLACEHOLDER.equals(aliasedValue)) {
|
||||||
|
attributes.put(aliasedAttributeName,
|
||||||
|
adaptValue(element, value, classValuesAsString, nestedAnnotationsAsMap));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String attributeName : attributes.keySet()) {
|
||||||
|
Object value = attributes.get(attributeName);
|
||||||
|
if (DEFAULT_VALUE_PLACEHOLDER.equals(value)) {
|
||||||
|
attributes.put(attributeName,
|
||||||
|
adaptValue(element, getDefaultValue(annotationType, attributeName), classValuesAsString,
|
||||||
|
nestedAnnotationsAsMap));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>If the supplied throwable is an {@link AnnotationConfigurationException},
|
||||||
|
* it will be cast to an {@code AnnotationConfigurationException} and thrown,
|
||||||
|
* allowing it to propagate to the caller.
|
||||||
|
* <p>Otherwise, this method does nothing.
|
||||||
|
* @since 4.2
|
||||||
|
*/
|
||||||
|
static void rethrowAnnotationConfigurationException(Throwable t) {
|
||||||
|
if (t instanceof AnnotationConfigurationException) {
|
||||||
|
throw (AnnotationConfigurationException) t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the supplied annotation introspection exception.
|
||||||
|
* <p>If the supplied exception is an {@link AnnotationConfigurationException},
|
||||||
|
* it will simply be thrown, allowing it to propagate to the caller, and
|
||||||
|
* nothing will be logged.
|
||||||
|
* <p>Otherwise, this method logs an introspection failure (in particular
|
||||||
|
* {@code TypeNotPresentExceptions}) — before moving on, pretending
|
||||||
|
* there were no annotations on this specific element.
|
||||||
* @param element the element that we tried to introspect annotations on
|
* @param element the element that we tried to introspect annotations on
|
||||||
* @param ex the exception that we encountered
|
* @param ex the exception that we encountered
|
||||||
|
* @see #rethrowAnnotationConfigurationException
|
||||||
*/
|
*/
|
||||||
static void logIntrospectionFailure(AnnotatedElement element, Exception ex) {
|
static void handleIntrospectionFailure(AnnotatedElement element, Exception ex) {
|
||||||
|
|
||||||
|
rethrowAnnotationConfigurationException(ex);
|
||||||
|
|
||||||
Log loggerToUse = logger;
|
Log loggerToUse = logger;
|
||||||
if (loggerToUse == null) {
|
if (loggerToUse == null) {
|
||||||
loggerToUse = LogFactory.getLog(AnnotationUtils.class);
|
loggerToUse = LogFactory.getLog(AnnotationUtils.class);
|
||||||
|
@ -896,7 +1353,6 @@ public abstract class AnnotationUtils {
|
||||||
if (loggerToUse.isInfoEnabled()) {
|
if (loggerToUse.isInfoEnabled()) {
|
||||||
logger.info("Failed to introspect annotations on [" + element + "]: " + ex);
|
logger.info("Failed to introspect annotations on [" + element + "]: " + ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -962,10 +1418,10 @@ public abstract class AnnotationUtils {
|
||||||
for (Annotation ann : element.getAnnotations()) {
|
for (Annotation ann : element.getAnnotations()) {
|
||||||
Class<? extends Annotation> currentAnnotationType = ann.annotationType();
|
Class<? extends Annotation> currentAnnotationType = ann.annotationType();
|
||||||
if (ObjectUtils.nullSafeEquals(this.annotationType, currentAnnotationType)) {
|
if (ObjectUtils.nullSafeEquals(this.annotationType, currentAnnotationType)) {
|
||||||
this.result.add((A) ann);
|
this.result.add(synthesizeAnnotation(element, (A) ann));
|
||||||
}
|
}
|
||||||
else if (ObjectUtils.nullSafeEquals(this.containerAnnotationType, currentAnnotationType)) {
|
else if (ObjectUtils.nullSafeEquals(this.containerAnnotationType, currentAnnotationType)) {
|
||||||
this.result.addAll(getValue(ann));
|
this.result.addAll(getValue(element, ann));
|
||||||
}
|
}
|
||||||
else if (!isInJavaLangAnnotationPackage(ann)) {
|
else if (!isInJavaLangAnnotationPackage(ann)) {
|
||||||
process(currentAnnotationType);
|
process(currentAnnotationType);
|
||||||
|
@ -973,17 +1429,23 @@ public abstract class AnnotationUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
logIntrospectionFailure(element, ex);
|
handleIntrospectionFailure(element, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private List<A> getValue(Annotation annotation) {
|
private List<A> getValue(AnnotatedElement element, Annotation annotation) {
|
||||||
try {
|
try {
|
||||||
Method method = annotation.annotationType().getDeclaredMethod("value");
|
Method method = annotation.annotationType().getDeclaredMethod("value");
|
||||||
ReflectionUtils.makeAccessible(method);
|
ReflectionUtils.makeAccessible(method);
|
||||||
return Arrays.asList((A[]) method.invoke(annotation));
|
A[] annotations = (A[]) method.invoke(annotation);
|
||||||
|
|
||||||
|
List<A> synthesizedAnnotations = new ArrayList<A>();
|
||||||
|
for (A anno : annotations) {
|
||||||
|
synthesizedAnnotations.add(synthesizeAnnotation(element, anno));
|
||||||
|
}
|
||||||
|
return synthesizedAnnotations;
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
// Unable to read value from repeating annotation container -> ignore it.
|
// Unable to read value from repeating annotation container -> ignore it.
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker interface implemented by synthesized annotation proxies.
|
||||||
|
*
|
||||||
|
* <p>Used to detect whether an annotation has already been synthesized.
|
||||||
|
*
|
||||||
|
* @author Sam Brannen
|
||||||
|
* @since 4.2
|
||||||
|
*/
|
||||||
|
interface SynthesizedAnnotation {
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
/*
|
||||||
|
* 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.AnnotatedElement;
|
||||||
|
import java.lang.reflect.InvocationHandler;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.util.ObjectUtils;
|
||||||
|
import org.springframework.util.ReflectionUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO Document SynthesizedAnnotationInvocationHandler.
|
||||||
|
*
|
||||||
|
* @author Sam Brannen
|
||||||
|
* @since 4.2
|
||||||
|
*/
|
||||||
|
class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
|
||||||
|
|
||||||
|
private final AnnotatedElement annotatedElement;
|
||||||
|
|
||||||
|
private final Annotation annotation;
|
||||||
|
|
||||||
|
private final Map<String, String> aliasPairs;
|
||||||
|
|
||||||
|
|
||||||
|
public SynthesizedAnnotationInvocationHandler(Annotation annotation, Map<String, String> aliasPairs) {
|
||||||
|
this(null, annotation, aliasPairs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SynthesizedAnnotationInvocationHandler(AnnotatedElement annotatedElement, Annotation annotation,
|
||||||
|
Map<String, String> aliasPairs) {
|
||||||
|
this.annotatedElement = annotatedElement;
|
||||||
|
this.annotation = annotation;
|
||||||
|
this.aliasPairs = aliasPairs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||||
|
String attributeName = method.getName();
|
||||||
|
Class<?> returnType = method.getReturnType();
|
||||||
|
boolean nestedAnnotation = (Annotation[].class.isAssignableFrom(returnType) || Annotation.class.isAssignableFrom(returnType));
|
||||||
|
String aliasedAttributeName = aliasPairs.get(attributeName);
|
||||||
|
boolean aliasPresent = aliasedAttributeName != null;
|
||||||
|
|
||||||
|
ReflectionUtils.makeAccessible(method);
|
||||||
|
Object value = ReflectionUtils.invokeMethod(method, this.annotation, args);
|
||||||
|
|
||||||
|
// Nothing special to do?
|
||||||
|
if (!aliasPresent && !nestedAnnotation) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aliasPresent) {
|
||||||
|
Method aliasedMethod = null;
|
||||||
|
try {
|
||||||
|
aliasedMethod = annotation.annotationType().getDeclaredMethod(aliasedAttributeName);
|
||||||
|
}
|
||||||
|
catch (NoSuchMethodException e) {
|
||||||
|
String msg = String.format("In annotation [%s], attribute [%s] is declared as an @AliasFor [%s], "
|
||||||
|
+ "but attribute [%s] does not exist.", annotation.annotationType().getName(), attributeName,
|
||||||
|
aliasedAttributeName, aliasedAttributeName);
|
||||||
|
throw new AnnotationConfigurationException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
ReflectionUtils.makeAccessible(aliasedMethod);
|
||||||
|
Object aliasedValue = ReflectionUtils.invokeMethod(aliasedMethod, this.annotation, args);
|
||||||
|
Object defaultValue = AnnotationUtils.getDefaultValue(annotation, attributeName);
|
||||||
|
|
||||||
|
if (!ObjectUtils.nullSafeEquals(value, aliasedValue) && !ObjectUtils.nullSafeEquals(value, defaultValue)
|
||||||
|
&& !ObjectUtils.nullSafeEquals(aliasedValue, defaultValue)) {
|
||||||
|
String elementAsString = (annotatedElement == null ? "unknown element" : annotatedElement.toString());
|
||||||
|
String msg = String.format(
|
||||||
|
"In annotation [%s] declared on [%s], attribute [%s] and its alias [%s] are "
|
||||||
|
+ "declared with values of [%s] and [%s], but only one declaration is permitted.",
|
||||||
|
annotation.annotationType().getName(), elementAsString, attributeName, aliasedAttributeName,
|
||||||
|
ObjectUtils.nullSafeToString(value), ObjectUtils.nullSafeToString(aliasedValue));
|
||||||
|
throw new AnnotationConfigurationException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the user didn't declare the annotation with an explicit value, return
|
||||||
|
// the value of the alias.
|
||||||
|
if (ObjectUtils.nullSafeEquals(value, defaultValue)) {
|
||||||
|
value = aliasedValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synthesize nested annotations before returning them.
|
||||||
|
if (value instanceof Annotation) {
|
||||||
|
value = AnnotationUtils.synthesizeAnnotation(annotatedElement, (Annotation) value);
|
||||||
|
}
|
||||||
|
else if (value instanceof Annotation[]) {
|
||||||
|
Annotation[] annotations = (Annotation[]) value;
|
||||||
|
for (int i = 0; i < annotations.length; i++) {
|
||||||
|
annotations[i] = AnnotationUtils.synthesizeAnnotation(annotatedElement, annotations[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
/**
|
/**
|
||||||
* Core support package for Java 5 annotations.
|
* Core support package for annotations, meta-annotations, and composed
|
||||||
|
* annotations with attribute overrides.
|
||||||
*/
|
*/
|
||||||
package org.springframework.core.annotation;
|
package org.springframework.core.annotation;
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
package org.springframework.core.annotation;
|
package org.springframework.core.annotation;
|
||||||
|
|
||||||
import java.lang.annotation.Documented;
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Inherited;
|
import java.lang.annotation.Inherited;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
|
@ -26,11 +25,14 @@ import java.lang.reflect.Method;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.*;
|
||||||
|
|
||||||
import static java.util.Arrays.*;
|
import static java.util.Arrays.*;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
import static org.springframework.core.annotation.AnnotatedElementUtils.*;
|
import static org.springframework.core.annotation.AnnotatedElementUtils.*;
|
||||||
|
@ -46,9 +48,8 @@ public class AnnotatedElementUtilsTests {
|
||||||
|
|
||||||
private static final String TX_NAME = Transactional.class.getName();
|
private static final String TX_NAME = Transactional.class.getName();
|
||||||
|
|
||||||
private Set<String> names(Class<?>... classes) {
|
@Rule
|
||||||
return stream(classes).map(clazz -> clazz.getName()).collect(Collectors.toSet());
|
public final ExpectedException exception = ExpectedException.none();
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getMetaAnnotationTypesOnNonAnnotatedClass() {
|
public void getMetaAnnotationTypesOnNonAnnotatedClass() {
|
||||||
|
@ -180,7 +181,8 @@ public class AnnotatedElementUtilsTests {
|
||||||
public void getAllAnnotationAttributesOnClassWithMultipleComposedAnnotations() {
|
public void getAllAnnotationAttributesOnClassWithMultipleComposedAnnotations() {
|
||||||
MultiValueMap<String, Object> attributes = getAllAnnotationAttributes(TxFromMultipleComposedAnnotations.class, TX_NAME);
|
MultiValueMap<String, Object> attributes = getAllAnnotationAttributes(TxFromMultipleComposedAnnotations.class, TX_NAME);
|
||||||
assertNotNull("Annotation attributes map for @Transactional on TxFromMultipleComposedAnnotations", attributes);
|
assertNotNull("Annotation attributes map for @Transactional on TxFromMultipleComposedAnnotations", attributes);
|
||||||
assertEquals("value for TxFromMultipleComposedAnnotations.", asList("TxComposed1", "TxComposed2"), attributes.get("value"));
|
assertEquals("value for TxFromMultipleComposedAnnotations.", asList("TxInheritedComposed", "TxComposed"),
|
||||||
|
attributes.get("value"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -274,6 +276,77 @@ public class AnnotatedElementUtilsTests {
|
||||||
assertTrue(isAnnotated(element, name));
|
assertTrue(isAnnotated(element, name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getAnnotationAttributesWithConventionBasedComposedAnnotation() {
|
||||||
|
Class<?> element = ConventionBasedComposedContextConfigClass.class;
|
||||||
|
String name = ContextConfig.class.getName();
|
||||||
|
AnnotationAttributes attributes = getAnnotationAttributes(element, name);
|
||||||
|
|
||||||
|
assertNotNull("Should find @ContextConfig on " + element.getSimpleName(), attributes);
|
||||||
|
assertArrayEquals("locations", new String[] { "explicitDeclaration" }, attributes.getStringArray("locations"));
|
||||||
|
assertArrayEquals("value", new String[] { "explicitDeclaration" }, attributes.getStringArray("value"));
|
||||||
|
|
||||||
|
// Verify contracts between utility methods:
|
||||||
|
assertTrue(isAnnotated(element, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getAnnotationAttributesWithAliasedComposedAnnotation() {
|
||||||
|
Class<?> element = AliasedComposedContextConfigClass.class;
|
||||||
|
String name = ContextConfig.class.getName();
|
||||||
|
AnnotationAttributes attributes = getAnnotationAttributes(element, name);
|
||||||
|
|
||||||
|
assertNotNull("Should find @ContextConfig on " + element.getSimpleName(), attributes);
|
||||||
|
assertArrayEquals("value", new String[] { "test.xml" }, attributes.getStringArray("value"));
|
||||||
|
assertArrayEquals("locations", new String[] { "test.xml" }, attributes.getStringArray("locations"));
|
||||||
|
|
||||||
|
// Verify contracts between utility methods:
|
||||||
|
assertTrue(isAnnotated(element, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getAnnotationAttributesWithAliasedValueComposedAnnotation() {
|
||||||
|
Class<?> element = AliasedValueComposedContextConfigClass.class;
|
||||||
|
String name = ContextConfig.class.getName();
|
||||||
|
AnnotationAttributes attributes = getAnnotationAttributes(element, name);
|
||||||
|
|
||||||
|
assertNotNull("Should find @ContextConfig on " + element.getSimpleName(), attributes);
|
||||||
|
assertArrayEquals("locations", new String[] { "test.xml" }, attributes.getStringArray("locations"));
|
||||||
|
assertArrayEquals("value", new String[] { "test.xml" }, attributes.getStringArray("value"));
|
||||||
|
|
||||||
|
// Verify contracts between utility methods:
|
||||||
|
assertTrue(isAnnotated(element, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getAnnotationAttributesWithInvalidConventionBasedComposedAnnotation() {
|
||||||
|
Class<?> element = InvalidConventionBasedComposedContextConfigClass.class;
|
||||||
|
String name = ContextConfig.class.getName();
|
||||||
|
|
||||||
|
exception.expect(AnnotationConfigurationException.class);
|
||||||
|
exception.expectMessage(either(containsString("attribute [value] and its alias [locations]")).or(
|
||||||
|
containsString("attribute [locations] and its alias [value]")));
|
||||||
|
exception.expectMessage(either(
|
||||||
|
containsString("values of [{duplicateDeclaration}] and [{requiredLocationsDeclaration}]")).or(
|
||||||
|
containsString("values of [{requiredLocationsDeclaration}] and [{duplicateDeclaration}]")));
|
||||||
|
exception.expectMessage(containsString("but only one declaration is permitted"));
|
||||||
|
getAnnotationAttributes(element, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getAnnotationAttributesWithInvalidAliasedComposedAnnotation() {
|
||||||
|
Class<?> element = InvalidAliasedComposedContextConfigClass.class;
|
||||||
|
String name = ContextConfig.class.getName();
|
||||||
|
|
||||||
|
exception.expect(AnnotationConfigurationException.class);
|
||||||
|
exception.expectMessage(either(containsString("attribute [value] and its alias [locations]")).or(
|
||||||
|
containsString("attribute [locations] and its alias [value]")));
|
||||||
|
exception.expectMessage(either(containsString("values of [{duplicateDeclaration}] and [{test.xml}]")).or(
|
||||||
|
containsString("values of [{test.xml}] and [{duplicateDeclaration}]")));
|
||||||
|
exception.expectMessage(containsString("but only one declaration is permitted"));
|
||||||
|
getAnnotationAttributes(element, name);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void findAnnotationAttributesOnInheritedAnnotationInterface() {
|
public void findAnnotationAttributesOnInheritedAnnotationInterface() {
|
||||||
AnnotationAttributes attributes = findAnnotationAttributes(InheritedAnnotationInterface.class, Transactional.class);
|
AnnotationAttributes attributes = findAnnotationAttributes(InheritedAnnotationInterface.class, Transactional.class);
|
||||||
|
@ -375,27 +448,39 @@ public class AnnotatedElementUtilsTests {
|
||||||
assertEquals("TX qualifier for MetaAndLocalTxConfigClass.", "localTxMgr", attributes.getString("qualifier"));
|
assertEquals("TX qualifier for MetaAndLocalTxConfigClass.", "localTxMgr", attributes.getString("qualifier"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findAnnotationAttributesOnClassWithAttributeAliasesInTargetAnnotation() {
|
||||||
|
AnnotationAttributes attributes = findAnnotationAttributes(AliasedTransactionalComponentClass.class,
|
||||||
|
AliasedTransactional.class);
|
||||||
|
assertNotNull("Should find @AliasedTransactional on AliasedTransactionalComponentClass", attributes);
|
||||||
|
assertEquals("TX value for AliasedTransactionalComponentClass.", "aliasForQualifier",
|
||||||
|
attributes.getString("value"));
|
||||||
|
assertEquals("TX qualifier for AliasedTransactionalComponentClass.", "aliasForQualifier",
|
||||||
|
attributes.getString("qualifier"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> names(Class<?>... classes) {
|
||||||
|
return stream(classes).map(clazz -> clazz.getName()).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
@MetaCycle3
|
@MetaCycle3
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.ANNOTATION_TYPE)
|
@Target(ElementType.ANNOTATION_TYPE)
|
||||||
@Documented
|
|
||||||
@interface MetaCycle1 {
|
@interface MetaCycle1 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@MetaCycle1
|
@MetaCycle1
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.ANNOTATION_TYPE)
|
@Target(ElementType.ANNOTATION_TYPE)
|
||||||
@Documented
|
|
||||||
@interface MetaCycle2 {
|
@interface MetaCycle2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@MetaCycle2
|
@MetaCycle2
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
@Documented
|
|
||||||
@interface MetaCycle3 {
|
@interface MetaCycle3 {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -407,7 +492,6 @@ public class AnnotatedElementUtilsTests {
|
||||||
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||||
@Documented
|
|
||||||
@Inherited
|
@Inherited
|
||||||
@interface Transactional {
|
@interface Transactional {
|
||||||
|
|
||||||
|
@ -418,19 +502,29 @@ public class AnnotatedElementUtilsTests {
|
||||||
boolean readOnly() default false;
|
boolean readOnly() default false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||||
|
@Inherited
|
||||||
|
@interface AliasedTransactional {
|
||||||
|
|
||||||
|
@AliasFor(attribute = "qualifier")
|
||||||
|
String value() default "";
|
||||||
|
|
||||||
|
@AliasFor(attribute = "value")
|
||||||
|
String qualifier() default "";
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional(qualifier = "composed1")
|
@Transactional(qualifier = "composed1")
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
@Documented
|
|
||||||
@Inherited
|
@Inherited
|
||||||
@interface Composed1 {
|
@interface InheritedComposed {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(qualifier = "composed2", readOnly = true)
|
@Transactional(qualifier = "composed2", readOnly = true)
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
@Documented
|
@interface Composed {
|
||||||
@interface Composed2 {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
|
@ -440,14 +534,14 @@ public class AnnotatedElementUtilsTests {
|
||||||
String qualifier() default "txMgr";
|
String qualifier() default "txMgr";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional("TxComposed1")
|
@Transactional("TxInheritedComposed")
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@interface TxComposed1 {
|
@interface TxInheritedComposed {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional("TxComposed2")
|
@Transactional("TxComposed")
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@interface TxComposed2 {
|
@interface TxComposed {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
|
@ -461,6 +555,12 @@ public class AnnotatedElementUtilsTests {
|
||||||
@interface ComposedTransactionalComponent {
|
@interface ComposedTransactionalComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AliasedTransactional(value = "aliasForQualifier")
|
||||||
|
@Component
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@interface AliasedTransactionalComponent {
|
||||||
|
}
|
||||||
|
|
||||||
@TxComposedWithOverride
|
@TxComposedWithOverride
|
||||||
// Override default "txMgr" from @TxComposedWithOverride with "localTxMgr"
|
// Override default "txMgr" from @TxComposedWithOverride with "localTxMgr"
|
||||||
@Transactional(qualifier = "localTxMgr")
|
@Transactional(qualifier = "localTxMgr")
|
||||||
|
@ -469,6 +569,63 @@ public class AnnotatedElementUtilsTests {
|
||||||
@interface MetaAndLocalTxConfig {
|
@interface MetaAndLocalTxConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock of {@link org.springframework.test.context.ContextConfiguration}.
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
static @interface ContextConfig {
|
||||||
|
|
||||||
|
@AliasFor(attribute = "locations")
|
||||||
|
String[] value() default {};
|
||||||
|
|
||||||
|
@AliasFor(attribute = "value")
|
||||||
|
String[] locations() default {};
|
||||||
|
}
|
||||||
|
|
||||||
|
@ContextConfig
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
static @interface ConventionBasedComposedContextConfig {
|
||||||
|
|
||||||
|
String[] locations() default {};
|
||||||
|
}
|
||||||
|
|
||||||
|
@ContextConfig(value = "duplicateDeclaration")
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
static @interface InvalidConventionBasedComposedContextConfig {
|
||||||
|
|
||||||
|
String[] locations();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ContextConfig
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
static @interface AliasedComposedContextConfig {
|
||||||
|
|
||||||
|
@AliasFor(annotation = ContextConfig.class, attribute = "locations")
|
||||||
|
String[] xmlConfigFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ContextConfig
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
static @interface AliasedValueComposedContextConfig {
|
||||||
|
|
||||||
|
@AliasFor(annotation = ContextConfig.class, attribute = "value")
|
||||||
|
String[] locations();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalid because the configuration declares a value for 'value' and
|
||||||
|
* requires a value for the aliased 'locations'. So we likely end up with
|
||||||
|
* both 'value' and 'locations' being present in {@link AnnotationAttributes}
|
||||||
|
* but with different values, which violates the contract of {@code @AliasFor}.
|
||||||
|
*/
|
||||||
|
@ContextConfig(value = "duplicateDeclaration")
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
static @interface InvalidAliasedComposedContextConfig {
|
||||||
|
|
||||||
|
@AliasFor(annotation = ContextConfig.class, attribute = "locations")
|
||||||
|
String[] xmlConfigFiles();
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
static class NonAnnotatedClass {
|
static class NonAnnotatedClass {
|
||||||
|
@ -485,22 +642,26 @@ public class AnnotatedElementUtilsTests {
|
||||||
static class ComposedTransactionalComponentClass {
|
static class ComposedTransactionalComponentClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AliasedTransactionalComponent
|
||||||
|
static class AliasedTransactionalComponentClass {
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
static class ClassWithInheritedAnnotation {
|
static class ClassWithInheritedAnnotation {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composed2
|
@Composed
|
||||||
static class SubClassWithInheritedAnnotation extends ClassWithInheritedAnnotation {
|
static class SubClassWithInheritedAnnotation extends ClassWithInheritedAnnotation {
|
||||||
}
|
}
|
||||||
|
|
||||||
static class SubSubClassWithInheritedAnnotation extends SubClassWithInheritedAnnotation {
|
static class SubSubClassWithInheritedAnnotation extends SubClassWithInheritedAnnotation {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composed1
|
@InheritedComposed
|
||||||
static class ClassWithInheritedComposedAnnotation {
|
static class ClassWithInheritedComposedAnnotation {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composed2
|
@Composed
|
||||||
static class SubClassWithInheritedComposedAnnotation extends ClassWithInheritedComposedAnnotation {
|
static class SubClassWithInheritedComposedAnnotation extends ClassWithInheritedComposedAnnotation {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -519,8 +680,8 @@ public class AnnotatedElementUtilsTests {
|
||||||
static class DerivedTxConfig extends TxConfig {
|
static class DerivedTxConfig extends TxConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
@TxComposed1
|
@TxInheritedComposed
|
||||||
@TxComposed2
|
@TxComposed
|
||||||
static class TxFromMultipleComposedAnnotations {
|
static class TxFromMultipleComposedAnnotations {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -595,4 +756,23 @@ public class AnnotatedElementUtilsTests {
|
||||||
public static interface SubSubNonInheritedAnnotationInterface extends SubNonInheritedAnnotationInterface {
|
public static interface SubSubNonInheritedAnnotationInterface extends SubNonInheritedAnnotationInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ConventionBasedComposedContextConfig(locations = "explicitDeclaration")
|
||||||
|
static class ConventionBasedComposedContextConfigClass {
|
||||||
|
}
|
||||||
|
|
||||||
|
@InvalidConventionBasedComposedContextConfig(locations = "requiredLocationsDeclaration")
|
||||||
|
static class InvalidConventionBasedComposedContextConfigClass {
|
||||||
|
}
|
||||||
|
|
||||||
|
@AliasedComposedContextConfig(xmlConfigFiles = "test.xml")
|
||||||
|
static class AliasedComposedContextConfigClass {
|
||||||
|
}
|
||||||
|
|
||||||
|
@AliasedValueComposedContextConfig(locations = "test.xml")
|
||||||
|
static class AliasedValueComposedContextConfigClass {
|
||||||
|
}
|
||||||
|
|
||||||
|
@InvalidAliasedComposedContextConfig(xmlConfigFiles = "test.xml")
|
||||||
|
static class InvalidAliasedComposedContextConfigClass {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,11 +23,13 @@ import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
import org.springframework.core.annotation.subpackage.NonPublicAnnotatedClass;
|
import org.springframework.core.annotation.subpackage.NonPublicAnnotatedClass;
|
||||||
|
@ -48,6 +50,9 @@ import static org.springframework.core.annotation.AnnotationUtils.*;
|
||||||
*/
|
*/
|
||||||
public class AnnotationUtilsTests {
|
public class AnnotationUtilsTests {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final ExpectedException exception = ExpectedException.none();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void findMethodAnnotationOnLeaf() throws Exception {
|
public void findMethodAnnotationOnLeaf() throws Exception {
|
||||||
Method m = Leaf.class.getMethod("annotatedOnLeaf");
|
Method m = Leaf.class.getMethod("annotatedOnLeaf");
|
||||||
|
@ -154,7 +159,8 @@ public class AnnotationUtilsTests {
|
||||||
/** @since 4.1.2 */
|
/** @since 4.1.2 */
|
||||||
@Test
|
@Test
|
||||||
public void findClassAnnotationFavorsMoreLocallyDeclaredComposedAnnotationsOverAnnotationsOnInterfaces() {
|
public void findClassAnnotationFavorsMoreLocallyDeclaredComposedAnnotationsOverAnnotationsOnInterfaces() {
|
||||||
Component component = AnnotationUtils.findAnnotation(ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, Component.class);
|
Component component = findAnnotation(ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class,
|
||||||
|
Component.class);
|
||||||
assertNotNull(component);
|
assertNotNull(component);
|
||||||
assertEquals("meta2", component.value());
|
assertEquals("meta2", component.value());
|
||||||
}
|
}
|
||||||
|
@ -162,7 +168,7 @@ public class AnnotationUtilsTests {
|
||||||
/** @since 4.0.3 */
|
/** @since 4.0.3 */
|
||||||
@Test
|
@Test
|
||||||
public void findClassAnnotationFavorsMoreLocallyDeclaredComposedAnnotationsOverInheritedAnnotations() {
|
public void findClassAnnotationFavorsMoreLocallyDeclaredComposedAnnotationsOverInheritedAnnotations() {
|
||||||
Transactional transactional = AnnotationUtils.findAnnotation(SubSubClassWithInheritedAnnotation.class, Transactional.class);
|
Transactional transactional = findAnnotation(SubSubClassWithInheritedAnnotation.class, Transactional.class);
|
||||||
assertNotNull(transactional);
|
assertNotNull(transactional);
|
||||||
assertTrue("readOnly flag for SubSubClassWithInheritedAnnotation", transactional.readOnly());
|
assertTrue("readOnly flag for SubSubClassWithInheritedAnnotation", transactional.readOnly());
|
||||||
}
|
}
|
||||||
|
@ -170,21 +176,21 @@ public class AnnotationUtilsTests {
|
||||||
/** @since 4.0.3 */
|
/** @since 4.0.3 */
|
||||||
@Test
|
@Test
|
||||||
public void findClassAnnotationFavorsMoreLocallyDeclaredComposedAnnotationsOverInheritedComposedAnnotations() {
|
public void findClassAnnotationFavorsMoreLocallyDeclaredComposedAnnotationsOverInheritedComposedAnnotations() {
|
||||||
Component component = AnnotationUtils.findAnnotation(SubSubClassWithInheritedMetaAnnotation.class, Component.class);
|
Component component = findAnnotation(SubSubClassWithInheritedMetaAnnotation.class, Component.class);
|
||||||
assertNotNull(component);
|
assertNotNull(component);
|
||||||
assertEquals("meta2", component.value());
|
assertEquals("meta2", component.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void findClassAnnotationOnMetaMetaAnnotatedClass() {
|
public void findClassAnnotationOnMetaMetaAnnotatedClass() {
|
||||||
Component component = AnnotationUtils.findAnnotation(MetaMetaAnnotatedClass.class, Component.class);
|
Component component = findAnnotation(MetaMetaAnnotatedClass.class, Component.class);
|
||||||
assertNotNull("Should find meta-annotation on composed annotation on class", component);
|
assertNotNull("Should find meta-annotation on composed annotation on class", component);
|
||||||
assertEquals("meta2", component.value());
|
assertEquals("meta2", component.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void findClassAnnotationOnMetaMetaMetaAnnotatedClass() {
|
public void findClassAnnotationOnMetaMetaMetaAnnotatedClass() {
|
||||||
Component component = AnnotationUtils.findAnnotation(MetaMetaMetaAnnotatedClass.class, Component.class);
|
Component component = findAnnotation(MetaMetaMetaAnnotatedClass.class, Component.class);
|
||||||
assertNotNull("Should find meta-annotation on meta-annotation on composed annotation on class", component);
|
assertNotNull("Should find meta-annotation on meta-annotation on composed annotation on class", component);
|
||||||
assertEquals("meta2", component.value());
|
assertEquals("meta2", component.value());
|
||||||
}
|
}
|
||||||
|
@ -192,55 +198,55 @@ public class AnnotationUtilsTests {
|
||||||
@Test
|
@Test
|
||||||
public void findClassAnnotationOnAnnotatedClassWithMissingTargetMetaAnnotation() {
|
public void findClassAnnotationOnAnnotatedClassWithMissingTargetMetaAnnotation() {
|
||||||
// TransactionalClass is NOT annotated or meta-annotated with @Component
|
// TransactionalClass is NOT annotated or meta-annotated with @Component
|
||||||
Component component = AnnotationUtils.findAnnotation(TransactionalClass.class, Component.class);
|
Component component = findAnnotation(TransactionalClass.class, Component.class);
|
||||||
assertNull("Should not find @Component on TransactionalClass", component);
|
assertNull("Should not find @Component on TransactionalClass", component);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void findClassAnnotationOnMetaCycleAnnotatedClassWithMissingTargetMetaAnnotation() {
|
public void findClassAnnotationOnMetaCycleAnnotatedClassWithMissingTargetMetaAnnotation() {
|
||||||
Component component = AnnotationUtils.findAnnotation(MetaCycleAnnotatedClass.class, Component.class);
|
Component component = findAnnotation(MetaCycleAnnotatedClass.class, Component.class);
|
||||||
assertNull("Should not find @Component on MetaCycleAnnotatedClass", component);
|
assertNull("Should not find @Component on MetaCycleAnnotatedClass", component);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @since 4.2 */
|
/** @since 4.2 */
|
||||||
@Test
|
@Test
|
||||||
public void findClassAnnotationOnInheritedAnnotationInterface() {
|
public void findClassAnnotationOnInheritedAnnotationInterface() {
|
||||||
Transactional tx = AnnotationUtils.findAnnotation(InheritedAnnotationInterface.class, Transactional.class);
|
Transactional tx = findAnnotation(InheritedAnnotationInterface.class, Transactional.class);
|
||||||
assertNotNull("Should find @Transactional on InheritedAnnotationInterface", tx);
|
assertNotNull("Should find @Transactional on InheritedAnnotationInterface", tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @since 4.2 */
|
/** @since 4.2 */
|
||||||
@Test
|
@Test
|
||||||
public void findClassAnnotationOnSubInheritedAnnotationInterface() {
|
public void findClassAnnotationOnSubInheritedAnnotationInterface() {
|
||||||
Transactional tx = AnnotationUtils.findAnnotation(SubInheritedAnnotationInterface.class, Transactional.class);
|
Transactional tx = findAnnotation(SubInheritedAnnotationInterface.class, Transactional.class);
|
||||||
assertNotNull("Should find @Transactional on SubInheritedAnnotationInterface", tx);
|
assertNotNull("Should find @Transactional on SubInheritedAnnotationInterface", tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @since 4.2 */
|
/** @since 4.2 */
|
||||||
@Test
|
@Test
|
||||||
public void findClassAnnotationOnSubSubInheritedAnnotationInterface() {
|
public void findClassAnnotationOnSubSubInheritedAnnotationInterface() {
|
||||||
Transactional tx = AnnotationUtils.findAnnotation(SubSubInheritedAnnotationInterface.class, Transactional.class);
|
Transactional tx = findAnnotation(SubSubInheritedAnnotationInterface.class, Transactional.class);
|
||||||
assertNotNull("Should find @Transactional on SubSubInheritedAnnotationInterface", tx);
|
assertNotNull("Should find @Transactional on SubSubInheritedAnnotationInterface", tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @since 4.2 */
|
/** @since 4.2 */
|
||||||
@Test
|
@Test
|
||||||
public void findClassAnnotationOnNonInheritedAnnotationInterface() {
|
public void findClassAnnotationOnNonInheritedAnnotationInterface() {
|
||||||
Order order = AnnotationUtils.findAnnotation(NonInheritedAnnotationInterface.class, Order.class);
|
Order order = findAnnotation(NonInheritedAnnotationInterface.class, Order.class);
|
||||||
assertNotNull("Should find @Order on NonInheritedAnnotationInterface", order);
|
assertNotNull("Should find @Order on NonInheritedAnnotationInterface", order);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @since 4.2 */
|
/** @since 4.2 */
|
||||||
@Test
|
@Test
|
||||||
public void findClassAnnotationOnSubNonInheritedAnnotationInterface() {
|
public void findClassAnnotationOnSubNonInheritedAnnotationInterface() {
|
||||||
Order order = AnnotationUtils.findAnnotation(SubNonInheritedAnnotationInterface.class, Order.class);
|
Order order = findAnnotation(SubNonInheritedAnnotationInterface.class, Order.class);
|
||||||
assertNotNull("Should find @Order on SubNonInheritedAnnotationInterface", order);
|
assertNotNull("Should find @Order on SubNonInheritedAnnotationInterface", order);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @since 4.2 */
|
/** @since 4.2 */
|
||||||
@Test
|
@Test
|
||||||
public void findClassAnnotationOnSubSubNonInheritedAnnotationInterface() {
|
public void findClassAnnotationOnSubSubNonInheritedAnnotationInterface() {
|
||||||
Order order = AnnotationUtils.findAnnotation(SubSubNonInheritedAnnotationInterface.class, Order.class);
|
Order order = findAnnotation(SubSubNonInheritedAnnotationInterface.class, Order.class);
|
||||||
assertNotNull("Should find @Order on SubSubNonInheritedAnnotationInterface", order);
|
assertNotNull("Should find @Order on SubSubNonInheritedAnnotationInterface", order);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -375,13 +381,53 @@ public class AnnotationUtilsTests {
|
||||||
assertFalse(isAnnotationInherited(Order.class, SubNonInheritedAnnotationClass.class));
|
assertFalse(isAnnotationInherited(Order.class, SubNonInheritedAnnotationClass.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getAnnotationAttributesWithoutAttributeAliases() {
|
||||||
|
Component component = WebController.class.getAnnotation(Component.class);
|
||||||
|
assertNotNull(component);
|
||||||
|
|
||||||
|
AnnotationAttributes attributes = (AnnotationAttributes) getAnnotationAttributes(component);
|
||||||
|
assertNotNull(attributes);
|
||||||
|
assertEquals("value attribute: ", "webController", attributes.getString(VALUE));
|
||||||
|
assertEquals(Component.class, attributes.annotationType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getAnnotationAttributesWithAttributeAliases() throws Exception {
|
||||||
|
Method method = WebController.class.getMethod("handleMappedWithValueAttribute");
|
||||||
|
WebMapping webMapping = method.getAnnotation(WebMapping.class);
|
||||||
|
AnnotationAttributes attributes = (AnnotationAttributes) getAnnotationAttributes(webMapping);
|
||||||
|
assertNotNull(attributes);
|
||||||
|
assertEquals(WebMapping.class, attributes.annotationType());
|
||||||
|
assertEquals("name attribute: ", "foo", attributes.getString("name"));
|
||||||
|
assertEquals("value attribute: ", "/test", attributes.getString(VALUE));
|
||||||
|
assertEquals("path attribute: ", "/test", attributes.getString("path"));
|
||||||
|
|
||||||
|
method = WebController.class.getMethod("handleMappedWithPathAttribute");
|
||||||
|
webMapping = method.getAnnotation(WebMapping.class);
|
||||||
|
attributes = (AnnotationAttributes) getAnnotationAttributes(webMapping);
|
||||||
|
assertNotNull(attributes);
|
||||||
|
assertEquals(WebMapping.class, attributes.annotationType());
|
||||||
|
assertEquals("name attribute: ", "bar", attributes.getString("name"));
|
||||||
|
assertEquals("value attribute: ", "/test", attributes.getString(VALUE));
|
||||||
|
assertEquals("path attribute: ", "/test", attributes.getString("path"));
|
||||||
|
|
||||||
|
method = WebController.class.getMethod("handleMappedWithPathValueAndAttributes");
|
||||||
|
webMapping = method.getAnnotation(WebMapping.class);
|
||||||
|
exception.expect(AnnotationConfigurationException.class);
|
||||||
|
exception.expectMessage(containsString("attribute [value] and its alias [path]"));
|
||||||
|
exception.expectMessage(containsString("values of [/enigma] and [/test]"));
|
||||||
|
exception.expectMessage(containsString("but only one declaration is permitted"));
|
||||||
|
getAnnotationAttributes(webMapping);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getValueFromAnnotation() throws Exception {
|
public void getValueFromAnnotation() throws Exception {
|
||||||
Method method = SimpleFoo.class.getMethod("something", Object.class);
|
Method method = SimpleFoo.class.getMethod("something", Object.class);
|
||||||
Order order = findAnnotation(method, Order.class);
|
Order order = findAnnotation(method, Order.class);
|
||||||
|
|
||||||
assertEquals(1, AnnotationUtils.getValue(order, AnnotationUtils.VALUE));
|
assertEquals(1, getValue(order, VALUE));
|
||||||
assertEquals(1, AnnotationUtils.getValue(order));
|
assertEquals(1, getValue(order));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -391,8 +437,8 @@ public class AnnotationUtilsTests {
|
||||||
Annotation annotation = declaredAnnotations[0];
|
Annotation annotation = declaredAnnotations[0];
|
||||||
assertNotNull(annotation);
|
assertNotNull(annotation);
|
||||||
assertEquals("NonPublicAnnotation", annotation.annotationType().getSimpleName());
|
assertEquals("NonPublicAnnotation", annotation.annotationType().getSimpleName());
|
||||||
assertEquals(42, AnnotationUtils.getValue(annotation, AnnotationUtils.VALUE));
|
assertEquals(42, getValue(annotation, VALUE));
|
||||||
assertEquals(42, AnnotationUtils.getValue(annotation));
|
assertEquals(42, getValue(annotation));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -400,8 +446,8 @@ public class AnnotationUtilsTests {
|
||||||
Method method = SimpleFoo.class.getMethod("something", Object.class);
|
Method method = SimpleFoo.class.getMethod("something", Object.class);
|
||||||
Order order = findAnnotation(method, Order.class);
|
Order order = findAnnotation(method, Order.class);
|
||||||
|
|
||||||
assertEquals(Ordered.LOWEST_PRECEDENCE, AnnotationUtils.getDefaultValue(order, AnnotationUtils.VALUE));
|
assertEquals(Ordered.LOWEST_PRECEDENCE, getDefaultValue(order, VALUE));
|
||||||
assertEquals(Ordered.LOWEST_PRECEDENCE, AnnotationUtils.getDefaultValue(order));
|
assertEquals(Ordered.LOWEST_PRECEDENCE, getDefaultValue(order));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -411,14 +457,14 @@ public class AnnotationUtilsTests {
|
||||||
Annotation annotation = declaredAnnotations[0];
|
Annotation annotation = declaredAnnotations[0];
|
||||||
assertNotNull(annotation);
|
assertNotNull(annotation);
|
||||||
assertEquals("NonPublicAnnotation", annotation.annotationType().getSimpleName());
|
assertEquals("NonPublicAnnotation", annotation.annotationType().getSimpleName());
|
||||||
assertEquals(-1, AnnotationUtils.getDefaultValue(annotation, AnnotationUtils.VALUE));
|
assertEquals(-1, getDefaultValue(annotation, VALUE));
|
||||||
assertEquals(-1, AnnotationUtils.getDefaultValue(annotation));
|
assertEquals(-1, getDefaultValue(annotation));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getDefaultValueFromAnnotationType() throws Exception {
|
public void getDefaultValueFromAnnotationType() throws Exception {
|
||||||
assertEquals(Ordered.LOWEST_PRECEDENCE, AnnotationUtils.getDefaultValue(Order.class, AnnotationUtils.VALUE));
|
assertEquals(Ordered.LOWEST_PRECEDENCE, getDefaultValue(Order.class, VALUE));
|
||||||
assertEquals(Ordered.LOWEST_PRECEDENCE, AnnotationUtils.getDefaultValue(Order.class));
|
assertEquals(Ordered.LOWEST_PRECEDENCE, getDefaultValue(Order.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -431,14 +477,174 @@ public class AnnotationUtilsTests {
|
||||||
@Test
|
@Test
|
||||||
public void getRepeatableFromMethod() throws Exception {
|
public void getRepeatableFromMethod() throws Exception {
|
||||||
Method method = InterfaceWithRepeated.class.getMethod("foo");
|
Method method = InterfaceWithRepeated.class.getMethod("foo");
|
||||||
Set<MyRepeatable> annotions = AnnotationUtils.getRepeatableAnnotation(method,
|
Set<MyRepeatable> annotations = getRepeatableAnnotation(method, MyRepeatableContainer.class, MyRepeatable.class);
|
||||||
MyRepeatableContainer.class, MyRepeatable.class);
|
assertNotNull(annotations);
|
||||||
Set<String> values = new HashSet<String>();
|
List<String> values = annotations.stream().map(MyRepeatable::value).collect(Collectors.toList());
|
||||||
for (MyRepeatable myRepeatable : annotions) {
|
assertThat(values, equalTo(Arrays.asList("a", "b", "c", "meta")));
|
||||||
values.add(myRepeatable.value());
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getRepeatableWithAttributeAliases() throws Exception {
|
||||||
|
Set<ContextConfig> annotations = getRepeatableAnnotation(TestCase.class, Hierarchy.class, ContextConfig.class);
|
||||||
|
assertNotNull(annotations);
|
||||||
|
|
||||||
|
List<String> locations = annotations.stream().map(ContextConfig::locations).collect(Collectors.toList());
|
||||||
|
assertThat(locations, equalTo(Arrays.asList("A", "B")));
|
||||||
|
|
||||||
|
List<String> values = annotations.stream().map(ContextConfig::value).collect(Collectors.toList());
|
||||||
|
assertThat(values, equalTo(Arrays.asList("A", "B")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getAliasedAttributeNameFromAliasedComposedAnnotation() throws Exception {
|
||||||
|
Method attribute = AliasedComposedContextConfig.class.getDeclaredMethod("xmlConfigFile");
|
||||||
|
assertEquals("locations", getAliasedAttributeName(attribute, ContextConfig.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void synthesizeAnnotationWithoutAttributeAliases() throws Exception {
|
||||||
|
Component component = findAnnotation(WebController.class, Component.class);
|
||||||
|
assertNotNull(component);
|
||||||
|
Component synthesizedComponent = synthesizeAnnotation(component);
|
||||||
|
assertNotNull(synthesizedComponent);
|
||||||
|
assertSame(component, synthesizedComponent);
|
||||||
|
assertEquals("value attribute: ", "webController", synthesizedComponent.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void synthesizeAnnotationWithAttributeAliasForNonexistentAttribute() throws Exception {
|
||||||
|
AliasForNonexistentAttribute annotation = AliasForNonexistentAttributeClass.class.getAnnotation(AliasForNonexistentAttribute.class);
|
||||||
|
exception.expect(AnnotationConfigurationException.class);
|
||||||
|
exception.expectMessage(containsString("Attribute [foo] in"));
|
||||||
|
exception.expectMessage(containsString(AliasForNonexistentAttribute.class.getName()));
|
||||||
|
exception.expectMessage(containsString("is declared as an @AliasFor nonexistent attribute [bar]"));
|
||||||
|
synthesizeAnnotation(annotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void synthesizeAnnotationWithAttributeAliasWithoutMirroredAliasFor() throws Exception {
|
||||||
|
AliasForWithoutMirroredAliasFor annotation = AliasForWithoutMirroredAliasForClass.class.getAnnotation(AliasForWithoutMirroredAliasFor.class);
|
||||||
|
exception.expect(AnnotationConfigurationException.class);
|
||||||
|
exception.expectMessage(containsString("Attribute [bar] in"));
|
||||||
|
exception.expectMessage(containsString(AliasForWithoutMirroredAliasFor.class.getName()));
|
||||||
|
exception.expectMessage(containsString("must be declared as an @AliasFor [foo]"));
|
||||||
|
synthesizeAnnotation(annotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void synthesizeAnnotationWithAttributeAliasWithMirroredAliasForWrongAttribute() throws Exception {
|
||||||
|
AliasForWithMirroredAliasForWrongAttribute annotation = AliasForWithMirroredAliasForWrongAttributeClass.class.getAnnotation(AliasForWithMirroredAliasForWrongAttribute.class);
|
||||||
|
|
||||||
|
// Since JDK 7+ does not guarantee consistent ordering of methods returned using
|
||||||
|
// reflection, we cannot make the test dependent on any specific ordering.
|
||||||
|
//
|
||||||
|
// In other words, we can't be certain which type of exception message we'll get,
|
||||||
|
// so we allow for both possibilities.
|
||||||
|
exception.expect(AnnotationConfigurationException.class);
|
||||||
|
exception.expectMessage(containsString("Attribute [bar] in"));
|
||||||
|
exception.expectMessage(containsString(AliasForWithMirroredAliasForWrongAttribute.class.getName()));
|
||||||
|
exception.expectMessage(either(containsString("must be declared as an @AliasFor [foo], not [quux]")).
|
||||||
|
or(containsString("is declared as an @AliasFor nonexistent attribute [quux]")));
|
||||||
|
synthesizeAnnotation(annotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void synthesizeAnnotationWithAttributeAliasForAttributeOfDifferentType() throws Exception {
|
||||||
|
AliasForAttributeOfDifferentType annotation = AliasForAttributeOfDifferentTypeClass.class.getAnnotation(AliasForAttributeOfDifferentType.class);
|
||||||
|
exception.expect(AnnotationConfigurationException.class);
|
||||||
|
exception.expectMessage(startsWith("Misconfigured aliases"));
|
||||||
|
exception.expectMessage(containsString(AliasForAttributeOfDifferentType.class.getName()));
|
||||||
|
// Since JDK 7+ does not guarantee consistent ordering of methods returned using
|
||||||
|
// reflection, we cannot make the test dependent on any specific ordering.
|
||||||
|
//
|
||||||
|
// In other words, we don't know if "foo" or "bar" will come first.
|
||||||
|
exception.expectMessage(containsString("attribute [foo]"));
|
||||||
|
exception.expectMessage(containsString("attribute [bar]"));
|
||||||
|
exception.expectMessage(containsString("must declare the same return type"));
|
||||||
|
synthesizeAnnotation(annotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void synthesizeAnnotationWithAttributeAliasForWithMissingDefaultValues() throws Exception {
|
||||||
|
AliasForWithMissingDefaultValues annotation = AliasForWithMissingDefaultValuesClass.class.getAnnotation(AliasForWithMissingDefaultValues.class);
|
||||||
|
exception.expectMessage(startsWith("Misconfigured aliases"));
|
||||||
|
exception.expectMessage(containsString(AliasForWithMissingDefaultValues.class.getName()));
|
||||||
|
// Since JDK 7+ does not guarantee consistent ordering of methods returned using
|
||||||
|
// reflection, we cannot make the test dependent on any specific ordering.
|
||||||
|
//
|
||||||
|
// In other words, we don't know if "foo" or "bar" will come first.
|
||||||
|
exception.expectMessage(containsString("attribute [foo]"));
|
||||||
|
exception.expectMessage(containsString("attribute [bar]"));
|
||||||
|
exception.expectMessage(containsString("must declare default values"));
|
||||||
|
synthesizeAnnotation(annotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void synthesizeAnnotationWithAttributeAliasForAttributeWithDifferentDefaultValue() throws Exception {
|
||||||
|
AliasForAttributeWithDifferentDefaultValue annotation = AliasForAttributeWithDifferentDefaultValueClass.class.getAnnotation(AliasForAttributeWithDifferentDefaultValue.class);
|
||||||
|
exception.expectMessage(startsWith("Misconfigured aliases"));
|
||||||
|
exception.expectMessage(containsString(AliasForAttributeWithDifferentDefaultValue.class.getName()));
|
||||||
|
// Since JDK 7+ does not guarantee consistent ordering of methods returned using
|
||||||
|
// reflection, we cannot make the test dependent on any specific ordering.
|
||||||
|
//
|
||||||
|
// In other words, we don't know if "foo" or "bar" will come first.
|
||||||
|
exception.expectMessage(containsString("attribute [foo]"));
|
||||||
|
exception.expectMessage(containsString("attribute [bar]"));
|
||||||
|
exception.expectMessage(containsString("must declare the same default value"));
|
||||||
|
synthesizeAnnotation(annotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void synthesizeAnnotationWithAttributeAliases() throws Exception {
|
||||||
|
Method method = WebController.class.getMethod("handleMappedWithValueAttribute");
|
||||||
|
WebMapping webMapping = method.getAnnotation(WebMapping.class);
|
||||||
|
assertNotNull(webMapping);
|
||||||
|
WebMapping synthesizedWebMapping = synthesizeAnnotation(webMapping);
|
||||||
|
assertNotSame(webMapping, synthesizedWebMapping);
|
||||||
|
assertThat(synthesizedWebMapping, instanceOf(SynthesizedAnnotation.class));
|
||||||
|
|
||||||
|
assertNotNull(synthesizedWebMapping);
|
||||||
|
assertEquals("name attribute: ", "foo", synthesizedWebMapping.name());
|
||||||
|
assertEquals("aliased path attribute: ", "/test", synthesizedWebMapping.path());
|
||||||
|
assertEquals("actual value attribute: ", "/test", synthesizedWebMapping.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void synthesizeAnnotationWithAttributeAliasesInNestedAnnotations() throws Exception {
|
||||||
|
Hierarchy hierarchy = TestCase.class.getAnnotation(Hierarchy.class);
|
||||||
|
assertNotNull(hierarchy);
|
||||||
|
Hierarchy synthesizedHierarchy = synthesizeAnnotation(hierarchy);
|
||||||
|
assertNotSame(hierarchy, synthesizedHierarchy);
|
||||||
|
assertThat(synthesizedHierarchy, instanceOf(SynthesizedAnnotation.class));
|
||||||
|
|
||||||
|
ContextConfig[] configs = synthesizedHierarchy.value();
|
||||||
|
assertNotNull(configs);
|
||||||
|
for (ContextConfig contextConfig : configs) {
|
||||||
|
assertThat(contextConfig, instanceOf(SynthesizedAnnotation.class));
|
||||||
}
|
}
|
||||||
assertThat(values, equalTo((Set<String>) new HashSet<String>(
|
|
||||||
Arrays.asList("a", "b", "c", "meta"))));
|
List<String> locations = Arrays.stream(configs).map(ContextConfig::locations).collect(Collectors.toList());
|
||||||
|
assertThat(locations, equalTo(Arrays.asList("A", "B")));
|
||||||
|
|
||||||
|
List<String> values = Arrays.stream(configs).map(ContextConfig::value).collect(Collectors.toList());
|
||||||
|
assertThat(values, equalTo(Arrays.asList("A", "B")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void synthesizeAlreadySynthesizedAnnotation() throws Exception {
|
||||||
|
Method method = WebController.class.getMethod("handleMappedWithValueAttribute");
|
||||||
|
WebMapping webMapping = method.getAnnotation(WebMapping.class);
|
||||||
|
assertNotNull(webMapping);
|
||||||
|
WebMapping synthesizedWebMapping = synthesizeAnnotation(webMapping);
|
||||||
|
assertNotSame(webMapping, synthesizedWebMapping);
|
||||||
|
WebMapping synthesizedAgainWebMapping = synthesizeAnnotation(synthesizedWebMapping);
|
||||||
|
assertSame(synthesizedWebMapping, synthesizedAgainWebMapping);
|
||||||
|
assertThat(synthesizedAgainWebMapping, instanceOf(SynthesizedAnnotation.class));
|
||||||
|
|
||||||
|
assertNotNull(synthesizedAgainWebMapping);
|
||||||
|
assertEquals("name attribute: ", "foo", synthesizedAgainWebMapping.name());
|
||||||
|
assertEquals("aliased path attribute: ", "/test", synthesizedAgainWebMapping.path());
|
||||||
|
assertEquals("actual value attribute: ", "/test", synthesizedAgainWebMapping.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -710,4 +916,149 @@ public class AnnotationUtilsTests {
|
||||||
void foo();
|
void foo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock of {@link org.springframework.web.bind.annotation.RequestMapping}.
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@interface WebMapping {
|
||||||
|
|
||||||
|
String name();
|
||||||
|
|
||||||
|
@AliasFor(attribute = "path")
|
||||||
|
String value() default "";
|
||||||
|
|
||||||
|
@AliasFor(attribute = "value")
|
||||||
|
String path() default "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component("webController")
|
||||||
|
static class WebController {
|
||||||
|
|
||||||
|
@WebMapping(value = "/test", name = "foo")
|
||||||
|
public void handleMappedWithValueAttribute() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@WebMapping(path = "/test", name = "bar")
|
||||||
|
public void handleMappedWithPathAttribute() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@WebMapping(value = "/enigma", path = "/test", name = "baz")
|
||||||
|
public void handleMappedWithPathValueAndAttributes() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock of {@link org.springframework.test.context.ContextConfiguration}.
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
static @interface ContextConfig {
|
||||||
|
|
||||||
|
@AliasFor(attribute = "locations")
|
||||||
|
String value() default "";
|
||||||
|
|
||||||
|
@AliasFor(attribute = "value")
|
||||||
|
String locations() default "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock of {@link org.springframework.test.context.ContextHierarchy}.
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
static @interface Hierarchy {
|
||||||
|
|
||||||
|
ContextConfig[] value();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Hierarchy({ @ContextConfig("A"), @ContextConfig(locations = "B") })
|
||||||
|
static class TestCase {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
static @interface AliasForNonexistentAttribute {
|
||||||
|
|
||||||
|
@AliasFor(attribute = "bar")
|
||||||
|
String foo() default "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@AliasForNonexistentAttribute
|
||||||
|
static class AliasForNonexistentAttributeClass {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
static @interface AliasForWithoutMirroredAliasFor {
|
||||||
|
|
||||||
|
@AliasFor(attribute = "bar")
|
||||||
|
String foo() default "";
|
||||||
|
|
||||||
|
String bar() default "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@AliasForWithoutMirroredAliasFor
|
||||||
|
static class AliasForWithoutMirroredAliasForClass {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
static @interface AliasForWithMirroredAliasForWrongAttribute {
|
||||||
|
|
||||||
|
@AliasFor(attribute = "bar")
|
||||||
|
String[] foo() default "";
|
||||||
|
|
||||||
|
@AliasFor(attribute = "quux")
|
||||||
|
String[] bar() default "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@AliasForWithMirroredAliasForWrongAttribute
|
||||||
|
static class AliasForWithMirroredAliasForWrongAttributeClass {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
static @interface AliasForAttributeOfDifferentType {
|
||||||
|
|
||||||
|
@AliasFor(attribute = "bar")
|
||||||
|
String[] foo() default "";
|
||||||
|
|
||||||
|
@AliasFor(attribute = "foo")
|
||||||
|
boolean bar() default true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@AliasForAttributeOfDifferentType
|
||||||
|
static class AliasForAttributeOfDifferentTypeClass {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
static @interface AliasForWithMissingDefaultValues {
|
||||||
|
|
||||||
|
@AliasFor(attribute = "bar")
|
||||||
|
String foo();
|
||||||
|
|
||||||
|
@AliasFor(attribute = "foo")
|
||||||
|
String bar();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AliasForWithMissingDefaultValues(foo = "foo", bar = "bar")
|
||||||
|
static class AliasForWithMissingDefaultValuesClass {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
static @interface AliasForAttributeWithDifferentDefaultValue {
|
||||||
|
|
||||||
|
@AliasFor(attribute = "bar")
|
||||||
|
String foo() default "X";
|
||||||
|
|
||||||
|
@AliasFor(attribute = "foo")
|
||||||
|
String bar() default "Z";
|
||||||
|
}
|
||||||
|
|
||||||
|
@AliasForAttributeWithDifferentDefaultValue
|
||||||
|
static class AliasForAttributeWithDifferentDefaultValueClass {
|
||||||
|
}
|
||||||
|
|
||||||
|
@ContextConfig
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
static @interface AliasedComposedContextConfig {
|
||||||
|
|
||||||
|
@AliasFor(annotation = ContextConfig.class, attribute = "locations")
|
||||||
|
String xmlConfigFile();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -23,6 +23,8 @@ import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AliasFor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@code ActiveProfiles} is a class-level annotation that is used to declare
|
* {@code ActiveProfiles} is a class-level annotation that is used to declare
|
||||||
* which <em>active bean definition profiles</em> should be used when loading
|
* which <em>active bean definition profiles</em> should be used when loading
|
||||||
|
@ -53,6 +55,7 @@ public @interface ActiveProfiles {
|
||||||
* <p>This attribute may <strong>not</strong> be used in conjunction with
|
* <p>This attribute may <strong>not</strong> be used in conjunction with
|
||||||
* {@link #profiles}, but it may be used <em>instead</em> of {@link #profiles}.
|
* {@link #profiles}, but it may be used <em>instead</em> of {@link #profiles}.
|
||||||
*/
|
*/
|
||||||
|
@AliasFor(attribute = "profiles")
|
||||||
String[] value() default {};
|
String[] value() default {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -61,6 +64,7 @@ public @interface ActiveProfiles {
|
||||||
* <p>This attribute may <strong>not</strong> be used in conjunction with
|
* <p>This attribute may <strong>not</strong> be used in conjunction with
|
||||||
* {@link #value}, but it may be used <em>instead</em> of {@link #value}.
|
* {@link #value}, but it may be used <em>instead</em> of {@link #value}.
|
||||||
*/
|
*/
|
||||||
|
@AliasFor(attribute = "value")
|
||||||
String[] profiles() default {};
|
String[] profiles() default {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -25,6 +25,7 @@ import java.lang.annotation.Target;
|
||||||
|
|
||||||
import org.springframework.context.ApplicationContextInitializer;
|
import org.springframework.context.ApplicationContextInitializer;
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
|
import org.springframework.core.annotation.AliasFor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@code @ContextConfiguration} defines class-level metadata that is used to determine
|
* {@code @ContextConfiguration} defines class-level metadata that is used to determine
|
||||||
|
@ -97,6 +98,7 @@ public @interface ContextConfiguration {
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
* @see #inheritLocations
|
* @see #inheritLocations
|
||||||
*/
|
*/
|
||||||
|
@AliasFor(attribute = "locations")
|
||||||
String[] value() default {};
|
String[] value() default {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -127,6 +129,7 @@ public @interface ContextConfiguration {
|
||||||
* @since 2.5
|
* @since 2.5
|
||||||
* @see #inheritLocations
|
* @see #inheritLocations
|
||||||
*/
|
*/
|
||||||
|
@AliasFor(attribute = "value")
|
||||||
String[] locations() default {};
|
String[] locations() default {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -68,7 +68,7 @@ public class ContextConfigurationAttributes {
|
||||||
* @param contextConfiguration the annotation from which to retrieve the attributes
|
* @param contextConfiguration the annotation from which to retrieve the attributes
|
||||||
*/
|
*/
|
||||||
public ContextConfigurationAttributes(Class<?> declaringClass, ContextConfiguration contextConfiguration) {
|
public ContextConfigurationAttributes(Class<?> declaringClass, ContextConfiguration contextConfiguration) {
|
||||||
this(declaringClass, resolveLocations(declaringClass, contextConfiguration), contextConfiguration.classes(),
|
this(declaringClass, contextConfiguration.locations(), contextConfiguration.classes(),
|
||||||
contextConfiguration.inheritLocations(), contextConfiguration.initializers(),
|
contextConfiguration.inheritLocations(), contextConfiguration.initializers(),
|
||||||
contextConfiguration.inheritInitializers(), contextConfiguration.name(), contextConfiguration.loader());
|
contextConfiguration.inheritInitializers(), contextConfiguration.name(), contextConfiguration.loader());
|
||||||
}
|
}
|
||||||
|
@ -83,12 +83,9 @@ public class ContextConfigurationAttributes {
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public ContextConfigurationAttributes(Class<?> declaringClass, AnnotationAttributes annAttrs) {
|
public ContextConfigurationAttributes(Class<?> declaringClass, AnnotationAttributes annAttrs) {
|
||||||
this(declaringClass,
|
this(declaringClass, annAttrs.getStringArray("locations"), annAttrs.getClassArray("classes"), annAttrs.getBoolean("inheritLocations"),
|
||||||
resolveLocations(declaringClass, annAttrs.getStringArray("locations"), annAttrs.getStringArray("value")),
|
(Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>[]) annAttrs.getClassArray("initializers"),
|
||||||
annAttrs.getClassArray("classes"), annAttrs.getBoolean("inheritLocations"),
|
annAttrs.getBoolean("inheritInitializers"), annAttrs.getString("name"), (Class<? extends ContextLoader>) annAttrs.getClass("loader"));
|
||||||
(Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>[]) annAttrs.getClassArray("initializers"),
|
|
||||||
annAttrs.getBoolean("inheritInitializers"), annAttrs.getString("name"),
|
|
||||||
(Class<? extends ContextLoader>) annAttrs.getClass("loader"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -159,37 +156,6 @@ public class ContextConfigurationAttributes {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve resource locations from the {@link ContextConfiguration#locations() locations}
|
|
||||||
* and {@link ContextConfiguration#value() value} attributes of the supplied
|
|
||||||
* {@link ContextConfiguration} annotation.
|
|
||||||
* @throws IllegalStateException if both the locations and value attributes have been declared
|
|
||||||
*/
|
|
||||||
private static String[] resolveLocations(Class<?> declaringClass, ContextConfiguration contextConfiguration) {
|
|
||||||
return resolveLocations(declaringClass, contextConfiguration.locations(), 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(value) && !ObjectUtils.isEmpty(locations)) {
|
|
||||||
throw new IllegalStateException(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(value), ObjectUtils.nullSafeToString(locations)));
|
|
||||||
}
|
|
||||||
else if (!ObjectUtils.isEmpty(value)) {
|
|
||||||
locations = value;
|
|
||||||
}
|
|
||||||
return locations;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the {@linkplain Class class} that declared the
|
* Get the {@linkplain Class class} that declared the
|
||||||
* {@link ContextConfiguration @ContextConfiguration} annotation.
|
* {@link ContextConfiguration @ContextConfiguration} annotation.
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -23,6 +23,8 @@ import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AliasFor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@code TestExecutionListeners} defines class-level metadata for configuring
|
* {@code TestExecutionListeners} defines class-level metadata for configuring
|
||||||
* which {@link TestExecutionListener TestExecutionListeners} should be
|
* which {@link TestExecutionListener TestExecutionListeners} should be
|
||||||
|
@ -85,6 +87,7 @@ public @interface TestExecutionListeners {
|
||||||
* <p>This attribute may <strong>not</strong> be used in conjunction with
|
* <p>This attribute may <strong>not</strong> be used in conjunction with
|
||||||
* {@link #listeners}, but it may be used instead of {@link #listeners}.
|
* {@link #listeners}, but it may be used instead of {@link #listeners}.
|
||||||
*/
|
*/
|
||||||
|
@AliasFor(attribute = "listeners")
|
||||||
Class<? extends TestExecutionListener>[] value() default {};
|
Class<? extends TestExecutionListener>[] value() default {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -100,6 +103,7 @@ public @interface TestExecutionListeners {
|
||||||
* @see org.springframework.test.context.transaction.TransactionalTestExecutionListener
|
* @see org.springframework.test.context.transaction.TransactionalTestExecutionListener
|
||||||
* @see org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener
|
* @see org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener
|
||||||
*/
|
*/
|
||||||
|
@AliasFor(attribute = "value")
|
||||||
Class<? extends TestExecutionListener>[] listeners() default {};
|
Class<? extends TestExecutionListener>[] listeners() default {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -23,6 +23,8 @@ import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AliasFor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@code @TestPropertySource} is a class-level annotation that is used to
|
* {@code @TestPropertySource} is a class-level annotation that is used to
|
||||||
* configure the {@link #locations} of properties files and inlined
|
* configure the {@link #locations} of properties files and inlined
|
||||||
|
@ -94,6 +96,7 @@ public @interface TestPropertySource {
|
||||||
*
|
*
|
||||||
* @see #locations
|
* @see #locations
|
||||||
*/
|
*/
|
||||||
|
@AliasFor(attribute = "locations")
|
||||||
String[] value() default {};
|
String[] value() default {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -141,6 +144,7 @@ public @interface TestPropertySource {
|
||||||
* @see #properties
|
* @see #properties
|
||||||
* @see org.springframework.core.env.PropertySource
|
* @see org.springframework.core.env.PropertySource
|
||||||
*/
|
*/
|
||||||
|
@AliasFor(attribute = "value")
|
||||||
String[] locations() default {};
|
String[] locations() default {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -22,6 +22,8 @@ import java.lang.annotation.Repeatable;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AliasFor;
|
||||||
|
|
||||||
import static java.lang.annotation.ElementType.*;
|
import static java.lang.annotation.ElementType.*;
|
||||||
import static java.lang.annotation.RetentionPolicy.*;
|
import static java.lang.annotation.RetentionPolicy.*;
|
||||||
|
|
||||||
|
@ -93,6 +95,7 @@ public @interface Sql {
|
||||||
* <p>This attribute may <strong>not</strong> be used in conjunction with
|
* <p>This attribute may <strong>not</strong> be used in conjunction with
|
||||||
* {@link #scripts}, but it may be used instead of {@link #scripts}.
|
* {@link #scripts}, but it may be used instead of {@link #scripts}.
|
||||||
*/
|
*/
|
||||||
|
@AliasFor(attribute = "scripts")
|
||||||
String[] value() default {};
|
String[] value() default {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -126,6 +129,7 @@ public @interface Sql {
|
||||||
* {@code "classpath:com/example/MyTest.testMethod.sql"}.</li>
|
* {@code "classpath:com/example/MyTest.testMethod.sql"}.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
|
@AliasFor(attribute = "value")
|
||||||
String[] scripts() default {};
|
String[] scripts() default {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -255,24 +255,6 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
|
||||||
|
|
||||||
private String[] getScripts(Sql sql, TestContext testContext, boolean classLevel) {
|
private String[] getScripts(Sql sql, TestContext testContext, boolean classLevel) {
|
||||||
String[] scripts = sql.scripts();
|
String[] scripts = sql.scripts();
|
||||||
String[] value = sql.value();
|
|
||||||
boolean scriptsDeclared = !ObjectUtils.isEmpty(scripts);
|
|
||||||
boolean valueDeclared = !ObjectUtils.isEmpty(value);
|
|
||||||
|
|
||||||
if (valueDeclared && scriptsDeclared) {
|
|
||||||
String elementType = (classLevel ? "class" : "method");
|
|
||||||
String elementName = (classLevel ? testContext.getTestClass().getName()
|
|
||||||
: testContext.getTestMethod().toString());
|
|
||||||
String msg = String.format("Test %s [%s] has been configured with @Sql's 'value' [%s] "
|
|
||||||
+ "and 'scripts' [%s] attributes. Only one declaration of SQL script "
|
|
||||||
+ "paths is permitted per @Sql annotation.", elementType, elementName,
|
|
||||||
ObjectUtils.nullSafeToString(value), ObjectUtils.nullSafeToString(scripts));
|
|
||||||
logger.error(msg);
|
|
||||||
throw new IllegalStateException(msg);
|
|
||||||
}
|
|
||||||
if (valueDeclared) {
|
|
||||||
scripts = value;
|
|
||||||
}
|
|
||||||
if (ObjectUtils.isEmpty(scripts)) {
|
if (ObjectUtils.isEmpty(scripts)) {
|
||||||
scripts = new String[] { detectDefaultScript(testContext, classLevel) };
|
scripts = new String[] { detectDefaultScript(testContext, classLevel) };
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,6 @@ import org.springframework.test.util.MetaAnnotationUtils;
|
||||||
import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor;
|
import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
import org.springframework.util.ObjectUtils;
|
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -147,18 +146,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
|
||||||
declaringClass.getName()));
|
declaringClass.getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Class<? extends TestExecutionListener>[] valueListenerClasses = (Class<? extends TestExecutionListener>[]) annAttrs.getClassArray("value");
|
|
||||||
Class<? extends TestExecutionListener>[] listenerClasses = (Class<? extends TestExecutionListener>[]) annAttrs.getClassArray("listeners");
|
Class<? extends TestExecutionListener>[] listenerClasses = (Class<? extends TestExecutionListener>[]) annAttrs.getClassArray("listeners");
|
||||||
if (!ObjectUtils.isEmpty(valueListenerClasses) && !ObjectUtils.isEmpty(listenerClasses)) {
|
|
||||||
throw new IllegalStateException(String.format(
|
|
||||||
"Class [%s] configured with @TestExecutionListeners' "
|
|
||||||
+ "'value' [%s] and 'listeners' [%s] attributes. Use one or the other, but not both.",
|
|
||||||
declaringClass.getName(), ObjectUtils.nullSafeToString(valueListenerClasses),
|
|
||||||
ObjectUtils.nullSafeToString(listenerClasses)));
|
|
||||||
}
|
|
||||||
else if (!ObjectUtils.isEmpty(valueListenerClasses)) {
|
|
||||||
listenerClasses = valueListenerClasses;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean inheritListeners = annAttrs.getBoolean("inheritListeners");
|
boolean inheritListeners = annAttrs.getBoolean("inheritListeners");
|
||||||
AnnotationDescriptor<TestExecutionListeners> superDescriptor = MetaAnnotationUtils.findAnnotationDescriptor(
|
AnnotationDescriptor<TestExecutionListeners> superDescriptor = MetaAnnotationUtils.findAnnotationDescriptor(
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -29,7 +29,6 @@ import org.springframework.test.context.ActiveProfilesResolver;
|
||||||
import org.springframework.test.util.MetaAnnotationUtils;
|
import org.springframework.test.util.MetaAnnotationUtils;
|
||||||
import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor;
|
import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.ObjectUtils;
|
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -94,7 +93,6 @@ abstract class ActiveProfilesUtils {
|
||||||
logger.trace(String.format("Retrieved @ActiveProfiles attributes [%s] for declaring class [%s].",
|
logger.trace(String.format("Retrieved @ActiveProfiles attributes [%s] for declaring class [%s].",
|
||||||
annAttrs, declaringClass.getName()));
|
annAttrs, declaringClass.getName()));
|
||||||
}
|
}
|
||||||
validateActiveProfilesConfiguration(declaringClass, annAttrs);
|
|
||||||
|
|
||||||
Class<? extends ActiveProfilesResolver> resolverClass = annAttrs.getClass("resolver");
|
Class<? extends ActiveProfilesResolver> resolverClass = annAttrs.getClass("resolver");
|
||||||
if (ActiveProfilesResolver.class == resolverClass) {
|
if (ActiveProfilesResolver.class == resolverClass) {
|
||||||
|
@ -134,20 +132,4 @@ abstract class ActiveProfilesUtils {
|
||||||
return StringUtils.toStringArray(activeProfiles);
|
return StringUtils.toStringArray(activeProfiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void validateActiveProfilesConfiguration(Class<?> declaringClass, AnnotationAttributes annAttrs) {
|
|
||||||
String[] valueProfiles = annAttrs.getStringArray("value");
|
|
||||||
String[] profiles = annAttrs.getStringArray("profiles");
|
|
||||||
boolean valueDeclared = !ObjectUtils.isEmpty(valueProfiles);
|
|
||||||
boolean profilesDeclared = !ObjectUtils.isEmpty(profiles);
|
|
||||||
|
|
||||||
if (valueDeclared && profilesDeclared) {
|
|
||||||
String msg = String.format("Class [%s] has been configured with @ActiveProfiles' 'value' [%s] "
|
|
||||||
+ "and 'profiles' [%s] attributes. Only one declaration of active bean "
|
|
||||||
+ "definition profiles is permitted per @ActiveProfiles annotation.", declaringClass.getName(),
|
|
||||||
ObjectUtils.nullSafeToString(valueProfiles), ObjectUtils.nullSafeToString(profiles));
|
|
||||||
logger.error(msg);
|
|
||||||
throw new IllegalStateException(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,6 @@ import org.springframework.core.annotation.AnnotationAttributes;
|
||||||
import org.springframework.test.context.ActiveProfiles;
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
import org.springframework.test.context.ActiveProfilesResolver;
|
import org.springframework.test.context.ActiveProfilesResolver;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.ObjectUtils;
|
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import static org.springframework.test.util.MetaAnnotationUtils.*;
|
import static org.springframework.test.util.MetaAnnotationUtils.*;
|
||||||
|
@ -81,25 +80,7 @@ public class DefaultActiveProfilesResolver implements ActiveProfilesResolver {
|
||||||
annAttrs, declaringClass.getName()));
|
annAttrs, declaringClass.getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
String[] profiles = annAttrs.getStringArray("profiles");
|
for (String profile : annAttrs.getStringArray("profiles")) {
|
||||||
String[] valueProfiles = annAttrs.getStringArray("value");
|
|
||||||
boolean valueDeclared = !ObjectUtils.isEmpty(valueProfiles);
|
|
||||||
boolean profilesDeclared = !ObjectUtils.isEmpty(profiles);
|
|
||||||
|
|
||||||
if (valueDeclared && profilesDeclared) {
|
|
||||||
String msg = String.format("Class [%s] has been configured with @ActiveProfiles' 'value' [%s] "
|
|
||||||
+ "and 'profiles' [%s] attributes. Only one declaration of active bean "
|
|
||||||
+ "definition profiles is permitted per @ActiveProfiles annotation.", declaringClass.getName(),
|
|
||||||
ObjectUtils.nullSafeToString(valueProfiles), ObjectUtils.nullSafeToString(profiles));
|
|
||||||
logger.error(msg);
|
|
||||||
throw new IllegalStateException(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (valueDeclared) {
|
|
||||||
profiles = valueProfiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String profile : profiles) {
|
|
||||||
if (StringUtils.hasText(profile)) {
|
if (StringUtils.hasText(profile)) {
|
||||||
activeProfiles.add(profile.trim());
|
activeProfiles.add(profile.trim());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -67,8 +67,7 @@ class TestPropertySourceAttributes {
|
||||||
* @param annAttrs the annotation attributes from which to retrieve the attributes
|
* @param annAttrs the annotation attributes from which to retrieve the attributes
|
||||||
*/
|
*/
|
||||||
TestPropertySourceAttributes(Class<?> declaringClass, AnnotationAttributes annAttrs) {
|
TestPropertySourceAttributes(Class<?> declaringClass, AnnotationAttributes annAttrs) {
|
||||||
this(declaringClass, resolveLocations(declaringClass, annAttrs.getStringArray("locations"),
|
this(declaringClass, annAttrs.getStringArray("locations"), annAttrs.getBoolean("inheritLocations"),
|
||||||
annAttrs.getStringArray("value")), annAttrs.getBoolean("inheritLocations"),
|
|
||||||
annAttrs.getStringArray("properties"), annAttrs.getBoolean("inheritProperties"));
|
annAttrs.getStringArray("properties"), annAttrs.getBoolean("inheritProperties"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,31 +155,6 @@ class TestPropertySourceAttributes {
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve resource locations from the supplied {@code locations} and
|
|
||||||
* {@code value} arrays, which correspond to attributes of the same names in
|
|
||||||
* the {@link TestPropertySource} 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(value) && !ObjectUtils.isEmpty(locations)) {
|
|
||||||
String msg = String.format("Class [%s] has been configured with @TestPropertySource's 'value' [%s] "
|
|
||||||
+ "and 'locations' [%s] attributes. Only one declaration of resource "
|
|
||||||
+ "locations is permitted per @TestPropertySource annotation.", declaringClass.getName(),
|
|
||||||
ObjectUtils.nullSafeToString(value), ObjectUtils.nullSafeToString(locations));
|
|
||||||
logger.error(msg);
|
|
||||||
throw new IllegalStateException(msg);
|
|
||||||
}
|
|
||||||
else if (!ObjectUtils.isEmpty(value)) {
|
|
||||||
locations = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return locations;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detect a default properties file for the supplied class, as specified
|
* Detect a default properties file for the supplied class, as specified
|
||||||
* in the class-level Javadoc for {@link TestPropertySource}.
|
* in the class-level Javadoc for {@link TestPropertySource}.
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -24,6 +24,7 @@ import java.util.stream.Collectors;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||||
import org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener;
|
import org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener;
|
||||||
import org.springframework.test.context.support.AbstractTestExecutionListener;
|
import org.springframework.test.context.support.AbstractTestExecutionListener;
|
||||||
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
||||||
|
@ -185,7 +186,7 @@ public class TestExecutionListenersTests {
|
||||||
testContextManager.getTestExecutionListeners().size());
|
testContextManager.getTestExecutionListeners().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalStateException.class)
|
@Test(expected = AnnotationConfigurationException.class)
|
||||||
public void listenersAndValueAttributesDeclared() {
|
public void listenersAndValueAttributesDeclared() {
|
||||||
new TestContextManager(DuplicateListenersConfigTestCase.class);
|
new TestContextManager(DuplicateListenersConfigTestCase.class);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,15 +16,20 @@
|
||||||
|
|
||||||
package org.springframework.test.context.jdbc;
|
package org.springframework.test.context.jdbc;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
|
||||||
import org.mockito.BDDMockito;
|
import org.mockito.BDDMockito;
|
||||||
|
|
||||||
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.test.context.TestContext;
|
import org.springframework.test.context.TestContext;
|
||||||
import org.springframework.test.context.jdbc.SqlConfig.TransactionMode;
|
import org.springframework.test.context.jdbc.SqlConfig.TransactionMode;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
import static org.mockito.BDDMockito.*;
|
import static org.mockito.BDDMockito.*;
|
||||||
|
|
||||||
|
@ -40,6 +45,9 @@ public class SqlScriptsTestExecutionListenerTests {
|
||||||
|
|
||||||
private final TestContext testContext = mock(TestContext.class);
|
private final TestContext testContext = mock(TestContext.class);
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final ExpectedException exception = ExpectedException.none();
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void missingValueAndScriptsAtClassLevel() throws Exception {
|
public void missingValueAndScriptsAtClassLevel() throws Exception {
|
||||||
|
@ -65,7 +73,14 @@ public class SqlScriptsTestExecutionListenerTests {
|
||||||
BDDMockito.<Class<?>> given(testContext.getTestClass()).willReturn(clazz);
|
BDDMockito.<Class<?>> given(testContext.getTestClass()).willReturn(clazz);
|
||||||
given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("foo"));
|
given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("foo"));
|
||||||
|
|
||||||
assertExceptionContains("Only one declaration of SQL script paths is permitted");
|
exception.expect(AnnotationConfigurationException.class);
|
||||||
|
exception.expectMessage(either(
|
||||||
|
containsString("attribute [value] and its alias [scripts]")).or(
|
||||||
|
containsString("attribute [scripts] and its alias [value]")));
|
||||||
|
exception.expectMessage(either(containsString("values of [{foo}] and [{bar}]")).or(
|
||||||
|
containsString("values of [{bar}] and [{foo}]")));
|
||||||
|
exception.expectMessage(containsString("but only one declaration is permitted"));
|
||||||
|
listener.beforeTestMethod(testContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -28,6 +28,7 @@ import java.util.Set;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||||
import org.springframework.test.context.ActiveProfiles;
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
import org.springframework.test.context.ActiveProfilesResolver;
|
import org.springframework.test.context.ActiveProfilesResolver;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
@ -190,7 +191,7 @@ public class ActiveProfilesUtilsTests extends AbstractContextConfigurationUtilsT
|
||||||
/**
|
/**
|
||||||
* @since 4.0
|
* @since 4.0
|
||||||
*/
|
*/
|
||||||
@Test(expected = IllegalStateException.class)
|
@Test(expected = AnnotationConfigurationException.class)
|
||||||
public void resolveActiveProfilesWithConflictingProfilesAndValue() {
|
public void resolveActiveProfilesWithConflictingProfilesAndValue() {
|
||||||
resolveActiveProfiles(ConflictingProfilesAndValueTestCase.class);
|
resolveActiveProfiles(ConflictingProfilesAndValueTestCase.class);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -18,12 +18,16 @@ package org.springframework.test.context.support;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
import org.springframework.test.context.ContextConfigurationAttributes;
|
import org.springframework.test.context.ContextConfigurationAttributes;
|
||||||
import org.springframework.test.context.ContextLoader;
|
import org.springframework.test.context.ContextLoader;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
import static org.springframework.test.context.support.ContextLoaderUtils.*;
|
import static org.springframework.test.context.support.ContextLoaderUtils.*;
|
||||||
|
|
||||||
|
@ -35,6 +39,10 @@ import static org.springframework.test.context.support.ContextLoaderUtils.*;
|
||||||
*/
|
*/
|
||||||
public class ContextLoaderUtilsConfigurationAttributesTests extends AbstractContextConfigurationUtilsTests {
|
public class ContextLoaderUtilsConfigurationAttributesTests extends AbstractContextConfigurationUtilsTests {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final ExpectedException exception = ExpectedException.none();
|
||||||
|
|
||||||
|
|
||||||
private void assertLocationsFooAttributes(ContextConfigurationAttributes attributes) {
|
private void assertLocationsFooAttributes(ContextConfigurationAttributes attributes) {
|
||||||
assertAttributes(attributes, LocationsFoo.class, new String[] { "/foo.xml" }, EMPTY_CLASS_ARRAY,
|
assertAttributes(attributes, LocationsFoo.class, new String[] { "/foo.xml" }, EMPTY_CLASS_ARRAY,
|
||||||
ContextLoader.class, false);
|
ContextLoader.class, false);
|
||||||
|
@ -55,8 +63,17 @@ public class ContextLoaderUtilsConfigurationAttributesTests extends AbstractCont
|
||||||
AnnotationConfigContextLoader.class, true);
|
AnnotationConfigContextLoader.class, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalStateException.class)
|
@Test
|
||||||
public void resolveConfigAttributesWithConflictingLocations() {
|
public void resolveConfigAttributesWithConflictingLocations() {
|
||||||
|
exception.expect(AnnotationConfigurationException.class);
|
||||||
|
exception.expectMessage(containsString(ConflictingLocations.class.getName()));
|
||||||
|
exception.expectMessage(either(
|
||||||
|
containsString("attribute [value] and its alias [locations]")).or(
|
||||||
|
containsString("attribute [locations] and its alias [value]")));
|
||||||
|
exception.expectMessage(either(
|
||||||
|
containsString("values of [{x}] and [{y}]")).or(
|
||||||
|
containsString("values of [{y}] and [{x}]")));
|
||||||
|
exception.expectMessage(containsString("but only one declaration is permitted"));
|
||||||
resolveContextConfigurationAttributes(ConflictingLocations.class);
|
resolveContextConfigurationAttributes(ConflictingLocations.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.junit.Test;
|
||||||
import org.junit.rules.ExpectedException;
|
import org.junit.rules.ExpectedException;
|
||||||
|
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
|
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||||
import org.springframework.core.env.ConfigurableEnvironment;
|
import org.springframework.core.env.ConfigurableEnvironment;
|
||||||
import org.springframework.core.env.MutablePropertySources;
|
import org.springframework.core.env.MutablePropertySources;
|
||||||
import org.springframework.mock.env.MockEnvironment;
|
import org.springframework.mock.env.MockEnvironment;
|
||||||
|
@ -81,7 +82,7 @@ public class TestPropertySourceUtilsTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void locationsAndValueAttributes() {
|
public void locationsAndValueAttributes() {
|
||||||
expectedException.expect(IllegalStateException.class);
|
expectedException.expect(AnnotationConfigurationException.class);
|
||||||
buildMergedTestPropertySources(LocationsAndValuePropertySources.class);
|
buildMergedTestPropertySources(LocationsAndValuePropertySources.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -23,6 +23,7 @@ import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AliasFor;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,6 +51,7 @@ import org.springframework.stereotype.Component;
|
||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
* @author Brian Clozel
|
* @author Brian Clozel
|
||||||
|
* @author Sam Brannen
|
||||||
* @since 3.2
|
* @since 3.2
|
||||||
*/
|
*/
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
|
@ -59,26 +61,28 @@ import org.springframework.stereotype.Component;
|
||||||
public @interface ControllerAdvice {
|
public @interface ControllerAdvice {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alias for the {@link #basePackages()} attribute.
|
* Alias for the {@link #basePackages} attribute.
|
||||||
* Allows for more concise annotation declarations e.g.:
|
* <p>Allows for more concise annotation declarations e.g.:
|
||||||
* {@code @ControllerAdvice("org.my.pkg")} is equivalent to
|
* {@code @ControllerAdvice("org.my.pkg")} is equivalent to
|
||||||
* {@code @ControllerAdvice(basePackages="org.my.pkg")}.
|
* {@code @ControllerAdvice(basePackages="org.my.pkg")}.
|
||||||
* @since 4.0
|
* @since 4.0
|
||||||
* @see #basePackages()
|
* @see #basePackages()
|
||||||
*/
|
*/
|
||||||
|
@AliasFor(attribute = "basePackages")
|
||||||
String[] value() default {};
|
String[] value() default {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Array of base packages.
|
* Array of base packages.
|
||||||
* Controllers that belong to those base packages or sub-packages thereof
|
* <p>Controllers that belong to those base packages or sub-packages thereof
|
||||||
* will be included, e.g.: {@code @ControllerAdvice(basePackages="org.my.pkg")}
|
* will be included, e.g.: {@code @ControllerAdvice(basePackages="org.my.pkg")}
|
||||||
* or {@code @ControllerAdvice(basePackages={"org.my.pkg", "org.my.other.pkg"})}.
|
* or {@code @ControllerAdvice(basePackages={"org.my.pkg", "org.my.other.pkg"})}.
|
||||||
* <p>{@link #value()} is an alias for this attribute, simply allowing for
|
* <p>{@link #value} is an alias for this attribute, simply allowing for
|
||||||
* more concise use of the annotation.
|
* more concise use of the annotation.
|
||||||
* <p>Also consider using {@link #basePackageClasses()} as a type-safe
|
* <p>Also consider using {@link #basePackageClasses()} as a type-safe
|
||||||
* alternative to String-based package names.
|
* alternative to String-based package names.
|
||||||
* @since 4.0
|
* @since 4.0
|
||||||
*/
|
*/
|
||||||
|
@AliasFor(attribute = "value")
|
||||||
String[] basePackages() default {};
|
String[] basePackages() default {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -93,7 +97,7 @@ public @interface ControllerAdvice {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Array of classes.
|
* Array of classes.
|
||||||
* Controllers that are assignable to at least one of the given types
|
* <p>Controllers that are assignable to at least one of the given types
|
||||||
* will be assisted by the {@code @ControllerAdvice} annotated class.
|
* will be assisted by the {@code @ControllerAdvice} annotated class.
|
||||||
* @since 4.0
|
* @since 4.0
|
||||||
*/
|
*/
|
||||||
|
@ -101,7 +105,7 @@ public @interface ControllerAdvice {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Array of annotations.
|
* Array of annotations.
|
||||||
* Controllers that are annotated with this/one of those annotation(s)
|
* <p>Controllers that are annotated with this/one of those annotation(s)
|
||||||
* will be assisted by the {@code @ControllerAdvice} annotated class.
|
* will be assisted by the {@code @ControllerAdvice} annotated class.
|
||||||
* <p>Consider creating a special annotation or use a predefined one,
|
* <p>Consider creating a special annotation or use a predefined one,
|
||||||
* like {@link RestController @RestController}.
|
* like {@link RestController @RestController}.
|
||||||
|
|
|
@ -23,6 +23,8 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AliasFor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Annotation for mapping web requests onto specific handler classes and/or
|
* Annotation for mapping web requests onto specific handler classes and/or
|
||||||
* handler methods. Provides a consistent style between Servlet and Portlet
|
* handler methods. Provides a consistent style between Servlet and Portlet
|
||||||
|
@ -298,7 +300,7 @@ public @interface RequestMapping {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The primary mapping expressed by this annotation.
|
* The primary mapping expressed by this annotation.
|
||||||
* <p>In a Servlet environment this is an alias for {@link #path()}.
|
* <p>In a Servlet environment this is an alias for {@link #path}.
|
||||||
* For example {@code @RequestMapping("/foo")} is equivalent to
|
* For example {@code @RequestMapping("/foo")} is equivalent to
|
||||||
* {@code @RequestMapping(path="/foo")}.
|
* {@code @RequestMapping(path="/foo")}.
|
||||||
* <p>In a Portlet environment this is the mapped portlet modes
|
* <p>In a Portlet environment this is the mapped portlet modes
|
||||||
|
@ -307,6 +309,7 @@ public @interface RequestMapping {
|
||||||
* When used at the type level, all method-level mappings inherit
|
* When used at the type level, all method-level mappings inherit
|
||||||
* this primary mapping, narrowing it for a specific handler method.
|
* this primary mapping, narrowing it for a specific handler method.
|
||||||
*/
|
*/
|
||||||
|
@AliasFor(attribute = "path")
|
||||||
String[] value() default {};
|
String[] value() default {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -321,6 +324,7 @@ public @interface RequestMapping {
|
||||||
* @see org.springframework.web.bind.annotation.ValueConstants#DEFAULT_NONE
|
* @see org.springframework.web.bind.annotation.ValueConstants#DEFAULT_NONE
|
||||||
* @since 4.2
|
* @since 4.2
|
||||||
*/
|
*/
|
||||||
|
@AliasFor(attribute = "value")
|
||||||
String[] path() default {};
|
String[] path() default {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -223,11 +223,6 @@ public class ControllerAdviceBean implements Ordered {
|
||||||
|
|
||||||
private static Set<String> initBasePackages(ControllerAdvice annotation) {
|
private static Set<String> initBasePackages(ControllerAdvice annotation) {
|
||||||
Set<String> basePackages = new LinkedHashSet<String>();
|
Set<String> basePackages = new LinkedHashSet<String>();
|
||||||
for (String basePackage : annotation.value()) {
|
|
||||||
if (StringUtils.hasText(basePackage)) {
|
|
||||||
basePackages.add(adaptBasePackage(basePackage));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (String basePackage : annotation.basePackages()) {
|
for (String basePackage : annotation.basePackages()) {
|
||||||
if (StringUtils.hasText(basePackage)) {
|
if (StringUtils.hasText(basePackage)) {
|
||||||
basePackages.add(adaptBasePackage(basePackage));
|
basePackages.add(adaptBasePackage(basePackage));
|
||||||
|
|
Loading…
Reference in New Issue