Skip to content

Commit

Permalink
Hide MergedAnnotation Implementation Details
Browse files Browse the repository at this point in the history
Issue gh-15286
  • Loading branch information
jzheaux committed Aug 29, 2024
1 parent 095929f commit cc6de8f
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,30 @@
package org.springframework.security.core.annotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
* A strategy for synthesizing an annotation from an {@link AnnotatedElement}.
* An interface to search for and synthesize an annotation on a type, method, or method
* parameter into an annotation of type {@code <A>}.
*
* <p>
* Implementations should support meta-annotations. This is usually by way of the
* {@link org.springframework.core.annotation.MergedAnnotations} API.
*
* <p>
* Synthesis generally refers to the process of taking an annotation's meta-annotations
* and placeholders, resolving them, and then combining these elements into a facade of
* the raw annotation instance.
* </p>
*
* <p>
* Since the process of synthesizing an annotation can be expensive, it is recommended to
* Since the process of synthesizing an annotation can be expensive, it's recommended to
* cache the synthesized annotation to prevent multiple computations.
* </p>
*
* @param <A> the annotation type
* @param <A> the annotation to search for and synthesize
* @author Josh Cummings
* @since 6.4
* @see UniqueMergedAnnotationSynthesizer
Expand All @@ -48,41 +49,36 @@
public interface AnnotationSynthesizer<A extends Annotation> {

/**
* Synthesize an annotation of type {@code A} from the given {@link AnnotatedElement}.
* Synthesize an annotation of type {@code A} from the given method.
*
* <p>
* Implementations should fail if they encounter more than one annotation of that type
* on the element.
* </p>
* attributable to the method.
*
* <p>
* Implementations should describe their strategy for searching the element and any
* surrounding class, interfaces, or super-class.
* </p>
* @param element the element to search
* @param method the method to search from
* @param targetClass the target class for the method
* @return the synthesized annotation or {@code null} if not found
*/
@Nullable
default A synthesize(AnnotatedElement element, Class<?> targetClass) {
Assert.notNull(targetClass, "targetClass cannot be null");
MergedAnnotation<A> annotation = merge(element, targetClass);
if (annotation == null) {
return null;
}
return annotation.synthesize();
}
A synthesize(Method method, Class<?> targetClass);

/**
* Synthesize an annotation of type {@code A} from the given method parameter.
*
* <p>
* Implementations should fail if they encounter more than one annotation of that type
* attributable to the parameter.
*
* <p>
* Implementations should describe their strategy for searching the element and any
* surrounding class, interfaces, or super-class.
* @param element the element to search
* @return the synthesized annotation or {@code null} if not found
*/
@Nullable
default A synthesize(AnnotatedElement element) {
if (element instanceof Method method) {
return synthesize(element, method.getDeclaringClass());
}
if (element instanceof Parameter parameter) {
return synthesize(parameter, parameter.getDeclaringExecutable().getDeclaringClass());
}
throw new UnsupportedOperationException("Unsupported element of type " + element.getClass());
}

MergedAnnotation<A> merge(AnnotatedElement element, Class<?> targetClass);
A synthesize(Parameter parameter);

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,16 @@
import org.springframework.util.PropertyPlaceholderHelper;

/**
* A strategy for synthesizing an annotation from an {@link AnnotatedElement} that
* supports meta-annotations with placeholders, like the following:
* Searches for and synthesizes an annotation on a type, method, or method parameter into
* an annotation of type {@code <A>}, resolving any placeholders in the annotation value.
*
* <p>
* Note that in all cases, Spring Security does not allow for repeatable annotations. So
* this class delegates to {@link UniqueMergedAnnotationSynthesizer} in order to error if
* a repeat is discovered.
*
* <p>
* It supports meta-annotations with placeholders, like the following:
*
* <pre>
* &#64;PreAuthorize("hasRole({role})")
Expand All @@ -46,19 +54,14 @@
* {@code @HasRole} annotation found on a given {@link AnnotatedElement}.
*
* <p>
* Note that in all cases, Spring Security does not allow for repeatable annotations. So
* this class delegates to {@link UniqueMergedAnnotationSynthesizer} in order to error if
* a repeat is discovered.
*
* <p>
* Since the process of synthesis is expensive, it is recommended to cache the synthesized
* result to prevent multiple computations.
*
* @param <A> the annotation type
* @param <A> the annotation to search for and synthesize
* @author Josh Cummings
* @since 6.4
*/
final class ExpressionTemplateAnnotationSynthesizer<A extends Annotation> implements AnnotationSynthesizer<A> {
final class ExpressionTemplateAnnotationSynthesizer<A extends Annotation> extends AbstractAnnotationSynthesizer<A> {

private final Class<A> type;

Expand All @@ -79,7 +82,7 @@ final class ExpressionTemplateAnnotationSynthesizer<A extends Annotation> implem
}

@Override
public MergedAnnotation<A> merge(AnnotatedElement element, Class<?> targetClass) {
MergedAnnotation<A> merge(AnnotatedElement element, Class<?> targetClass) {
if (element instanceof Parameter parameter) {
MergedAnnotation<A> annotation = this.uniqueParameterAnnotationCache.computeIfAbsent(parameter,
(p) -> this.unique.merge(p, targetClass));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,29 +34,24 @@
import org.springframework.util.ClassUtils;

/**
* A strategy for synthesizing an annotation from an {@link AnnotatedElement} that
* supports meta-annotations, like the following:
*
* <pre>
* &#64;PreAuthorize("hasRole('ROLE_ADMIN')")
* public @annotation HasRole {
* }
* </pre>
*
* <p>
* In that case, you can use an {@link UniqueMergedAnnotationSynthesizer} of type
* {@link org.springframework.security.access.prepost.PreAuthorize} to synthesize any
* {@code @HasRole} annotation found on a given {@link AnnotatedElement}.
* Searches for and synthesizes annotations found on types, methods, or method parameters
* into an annotation of type {@code <A>}, ensuring that there is a unique match.
*
* <p>
* Note that in all cases, Spring Security does not allow for repeatable annotations. As
* such, this class errors if a repeat is discovered.
*
* <p>
* If the given annotation can be applied to types, this class will search for annotations
* across the entire {@link MergedAnnotations.SearchStrategy type hierarchy}; otherwise,
* it will only look for annotations {@link MergedAnnotations.SearchStrategy directly}
* attributed to the element.
* For example, if a class extends two interfaces, and each interface is annotated with
* `@PreAuthorize("hasRole('ADMIN')")` and `@PreAuthorize("hasRole('USER')")`
* respectively, it's not clear which of these should apply, and so this class will throw
* an exception.
*
* <p>
* If the given annotation can be applied to types or methods, this class will traverse
* the type hierarchy, starting from the target class and method; in case of a method
* parameter, it will only consider annotations on the parameter. In all cases, it will
* consider meta-annotations in its traversal.
*
* <p>
* When traversing the type hierarchy, this class will first look for annotations on the
Expand All @@ -65,14 +60,29 @@
* extends and on any interfaces that class implements.
*
* <p>
* Since the process of synthesis is expensive, it is recommended to cache the synthesized
* It supports meta-annotations, like the following:
*
* <pre>
* &#64;PreAuthorize("hasRole('ROLE_ADMIN')")
* public @annotation HasRole {
* }
* </pre>
*
* <p>
* In that case, you can use an {@link UniqueMergedAnnotationSynthesizer} of type
* {@link org.springframework.security.access.prepost.PreAuthorize} to synthesize any
* {@code @HasRole} annotation found on a given method or class into its
* {@link org.springframework.security.access.prepost.PreAuthorize} meta-annotation.
*
* <p>
* Since the process of synthesis is expensive, it's recommended to cache the synthesized
* result to prevent multiple computations.
*
* @param <A> the annotation type
* @param <A> the annotation to search for and synthesize
* @author Josh Cummings
* @since 6.4
*/
final class UniqueMergedAnnotationSynthesizer<A extends Annotation> implements AnnotationSynthesizer<A> {
final class UniqueMergedAnnotationSynthesizer<A extends Annotation> extends AbstractAnnotationSynthesizer<A> {

private final List<Class<A>> types;

Expand All @@ -87,26 +97,18 @@ final class UniqueMergedAnnotationSynthesizer<A extends Annotation> implements A
}

@Override
public MergedAnnotation<A> merge(AnnotatedElement element, Class<?> targetClass) {
MergedAnnotation<A> merge(AnnotatedElement element, Class<?> targetClass) {
if (element instanceof Parameter parameter) {
return handleParameterElement(parameter);
List<MergedAnnotation<A>> annotations = findDirectAnnotations(parameter);
return requireUnique(parameter, annotations);
}
if (element instanceof Method method) {
return handleMethodElement(method, targetClass);
List<MergedAnnotation<A>> annotations = findMethodAnnotations(method, targetClass);
return requireUnique(method, annotations);
}
throw new AnnotationConfigurationException("Unsupported element of type " + element.getClass());
}

private MergedAnnotation<A> handleParameterElement(Parameter parameter) {
List<MergedAnnotation<A>> annotations = findDirectAnnotations(parameter);
return requireUnique(parameter, annotations);
}

private MergedAnnotation<A> handleMethodElement(Method method, Class<?> targetClass) {
List<MergedAnnotation<A>> annotations = findMethodAnnotations(method, targetClass);
return requireUnique(method, annotations);
}

private MergedAnnotation<A> requireUnique(AnnotatedElement element, List<MergedAnnotation<A>> annotations) {
return switch (annotations.size()) {
case 0 -> null;
Expand Down
Loading

0 comments on commit cc6de8f

Please sign in to comment.