Skip to content

Commit

Permalink
Support Meta-Annotation Parameters on Parameter Annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
github-actions[bot] authored and jzheaux committed Dec 19, 2024
1 parent 1104b45 commit bce04da
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
Expand Down Expand Up @@ -83,6 +84,7 @@
*
* @param <A> the annotation to search for and synthesize
* @author Josh Cummings
* @author DingHao
* @since 6.4
*/
final class UniqueSecurityAnnotationScanner<A extends Annotation> extends AbstractSecurityAnnotationScanner<A> {
Expand All @@ -107,7 +109,7 @@ final class UniqueSecurityAnnotationScanner<A extends Annotation> extends Abstra
MergedAnnotation<A> merge(AnnotatedElement element, Class<?> targetClass) {
if (element instanceof Parameter parameter) {
return this.uniqueParameterAnnotationCache.computeIfAbsent(parameter, (p) -> {
List<MergedAnnotation<A>> annotations = findDirectAnnotations(p);
List<MergedAnnotation<A>> annotations = findParameterAnnotations(p);
return requireUnique(p, annotations);
});
}
Expand Down Expand Up @@ -137,6 +139,50 @@ private MergedAnnotation<A> requireUnique(AnnotatedElement element, List<MergedA
};
}

private List<MergedAnnotation<A>> findParameterAnnotations(Parameter current) {
List<MergedAnnotation<A>> directAnnotations = findDirectAnnotations(current);
if (!directAnnotations.isEmpty()) {
return directAnnotations;
}
directAnnotations = new ArrayList<>(findDirectAnnotations(current));
Executable executable = current.getDeclaringExecutable();
if (executable instanceof Method method) {
Class<?> clazz = method.getDeclaringClass();
Set<Class<?>> visited = new HashSet<>();
while (clazz != null && visited.add(clazz)) {
for (Class<?> ifc : clazz.getInterfaces()) {
directAnnotations.addAll(findParameterAnnotations(method, ifc, current));
}
clazz = clazz.getSuperclass();
if (clazz == Object.class) {
clazz = null;
}
if (clazz != null && visited.add(clazz)) {
directAnnotations.addAll(findParameterAnnotations(method, clazz, current));
}
}
}
return directAnnotations;
}

private List<MergedAnnotation<A>> findParameterAnnotations(Method method, Class<?> superOrIfc, Parameter current) {
try {
Method methodToUse = superOrIfc.getDeclaredMethod(method.getName(), method.getParameterTypes());
for (Parameter parameter : methodToUse.getParameters()) {
if (parameter.getName().equals(current.getName())) {
List<MergedAnnotation<A>> directAnnotations = findDirectAnnotations(parameter);
if (!directAnnotations.isEmpty()) {
return directAnnotations;
}
}
}
}
catch (NoSuchMethodException ex) {
// move on
}
return Collections.emptyList();
}

private List<MergedAnnotation<A>> findMethodAnnotations(Method method, Class<?> targetClass) {
// The method may be on an interface, but we need attributes from the target
// class.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@

package org.springframework.security.core.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.List;

import org.junit.jupiter.api.Test;

Expand All @@ -34,6 +40,9 @@ public class UniqueSecurityAnnotationScannerTests {
private UniqueSecurityAnnotationScanner<PreAuthorize> scanner = new UniqueSecurityAnnotationScanner<>(
PreAuthorize.class);

private UniqueSecurityAnnotationScanner<CustomParameterAnnotation> parameterScanner = new UniqueSecurityAnnotationScanner<>(
CustomParameterAnnotation.class);

@Test
void scanWhenAnnotationOnInterfaceThenResolves() throws Exception {
Method method = AnnotationOnInterface.class.getDeclaredMethod("method");
Expand Down Expand Up @@ -251,6 +260,77 @@ void scanWhenClassInheritingAbstractClassNoAnnotationsThenNoAnnotation() throws
assertThat(preAuthorize).isNull();
}

@Test
void scanParameterAnnotationWhenAnnotationOnInterface() throws Exception {
Parameter parameter = UserService.class.getDeclaredMethod("add", String.class).getParameters()[0];
CustomParameterAnnotation customParameterAnnotation = this.parameterScanner.scan(parameter);
assertThat(customParameterAnnotation.value()).isEqualTo("one");
}

@Test
void scanParameterAnnotationWhenClassInheritingInterfaceAnnotation() throws Exception {
Parameter parameter = UserServiceImpl.class.getDeclaredMethod("add", String.class).getParameters()[0];
CustomParameterAnnotation customParameterAnnotation = this.parameterScanner.scan(parameter);
assertThat(customParameterAnnotation.value()).isEqualTo("one");
}

@Test
void scanParameterAnnotationWhenClassOverridingMethodOverridingInterface() throws Exception {
Parameter parameter = UserServiceImpl.class.getDeclaredMethod("get", String.class).getParameters()[0];
CustomParameterAnnotation customParameterAnnotation = this.parameterScanner.scan(parameter);
assertThat(customParameterAnnotation.value()).isEqualTo("five");
}

@Test
void scanParameterAnnotationWhenMultipleMethodInheritanceThenException() throws Exception {
Parameter parameter = UserServiceImpl.class.getDeclaredMethod("list", String.class).getParameters()[0];
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> this.parameterScanner.scan(parameter));
}

interface UserService {

void add(@CustomParameterAnnotation("one") String user);

List<String> list(@CustomParameterAnnotation("two") String user);

String get(@CustomParameterAnnotation("three") String user);

}

interface OtherUserService {

List<String> list(@CustomParameterAnnotation("four") String user);

}

static class UserServiceImpl implements UserService, OtherUserService {

@Override
public void add(String user) {

}

@Override
public List<String> list(String user) {
return List.of(user);
}

@Override
public String get(@CustomParameterAnnotation("five") String user) {
return user;
}

}

@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@interface CustomParameterAnnotation {

String value();

}

@PreAuthorize("one")
private interface AnnotationOnInterface {

Expand Down

0 comments on commit bce04da

Please sign in to comment.