Skip to content

Commit 4476edd

Browse files
brenuartceki
andauthored
Search for @NoAutoStart annotations in ancestor hierarchy, implemented interfaces and meta-annotations (#531)
Adresses issue LOGBACK-1583 Signed-off-by: Bertrand Renuart <[email protected]> Co-authored-by: Ceki Gülcü <[email protected]>
1 parent a649c60 commit 4476edd

File tree

2 files changed

+150
-4
lines changed

2 files changed

+150
-4
lines changed

logback-core/src/main/java/ch/qos/logback/core/joran/spi/NoAutoStartUtil.java

+77-4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
*/
1414
package ch.qos.logback.core.joran.spi;
1515

16+
17+
import java.lang.annotation.Annotation;
18+
import java.util.HashSet;
19+
import java.util.Set;
1620
import ch.qos.logback.core.spi.LifeCycle;
1721

1822
public class NoAutoStartUtil {
@@ -24,13 +28,83 @@ public class NoAutoStartUtil {
2428
* @param o
2529
* @return true for classes not marked with the NoAutoStart annotation
2630
*/
27-
static public boolean notMarkedWithNoAutoStart(Object o) {
31+
public static boolean notMarkedWithNoAutoStart(Object o) {
32+
if (o == null) {
33+
return false;
34+
}
2835
Class<?> clazz = o.getClass();
29-
NoAutoStart a = clazz.getAnnotation(NoAutoStart.class);
36+
NoAutoStart a = findAnnotation(clazz, NoAutoStart.class);
3037
return a == null;
3138
}
3239

33-
/**
40+
/**
41+
* Find a single {@link Annotation} of {@code annotationType} on the
42+
* supplied {@link Class}, traversing its interfaces, annotations, and
43+
* superclasses if the annotation is not <em>directly present</em> on
44+
* the given class itself.
45+
* <p>This method explicitly handles class-level annotations which are not
46+
* declared as {@link java.lang.annotation.Inherited inherited} <em>as well
47+
* as meta-annotations and annotations on interfaces</em>.
48+
* <p>The algorithm operates as follows:
49+
* <ol>
50+
* <li>Search for the annotation on the given class and return it if found.
51+
* <li>Recursively search through all annotations that the given class declares.
52+
* <li>Recursively search through all interfaces that the given class declares.
53+
* <li>Recursively search through the superclass hierarchy of the given class.
54+
* </ol>
55+
* <p>Note: in this context, the term <em>recursively</em> means that the search
56+
* process continues by returning to step #1 with the current interface,
57+
* annotation, or superclass as the class to look for annotations on.
58+
* @param clazz the class to look for annotations on
59+
* @param annotationType the type of annotation to look for
60+
* @return the first matching annotation, or {@code null} if not found
61+
*/
62+
private static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType) {
63+
return findAnnotation(clazz, annotationType, new HashSet<>());
64+
}
65+
66+
/**
67+
* Perform the search algorithm for {@link #findAnnotation(Class, Class)},
68+
* avoiding endless recursion by tracking which annotations have already
69+
* been <em>visited</em>.
70+
* @param clazz the class to look for annotations on
71+
* @param annotationType the type of annotation to look for
72+
* @param visited the set of annotations that have already been visited
73+
* @return the first matching annotation, or {@code null} if not found
74+
*/
75+
@SuppressWarnings("unchecked")
76+
private static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType, Set<Annotation> visited) {
77+
78+
Annotation[] anns = clazz.getDeclaredAnnotations();
79+
for (Annotation ann : anns) {
80+
if (ann.annotationType() == annotationType) {
81+
return (A) ann;
82+
}
83+
}
84+
for (Annotation ann : anns) {
85+
if (visited.add(ann)) {
86+
A annotation = findAnnotation(ann.annotationType(), annotationType, visited);
87+
if (annotation != null) {
88+
return annotation;
89+
}
90+
}
91+
}
92+
93+
for (Class<?> ifc : clazz.getInterfaces()) {
94+
A annotation = findAnnotation(ifc, annotationType, visited);
95+
if (annotation != null) {
96+
return annotation;
97+
}
98+
}
99+
100+
Class<?> superclass = clazz.getSuperclass();
101+
if (superclass == null || Object.class == superclass) {
102+
return null;
103+
}
104+
return findAnnotation(superclass, annotationType, visited);
105+
}
106+
107+
/**
34108
* Is the object a {@link LifeCycle} and is it marked not marked with
35109
* the NoAutoStart annotation.
36110
* @param o
@@ -43,5 +117,4 @@ static public boolean shouldBeStarted(Object o) {
43117
} else
44118
return false;
45119
}
46-
47120
}

logback-core/src/test/java/ch/qos/logback/core/joran/spi/NoAutoStartUtilTest.java

+73
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@
1515

1616
import org.junit.jupiter.api.Test;
1717

18+
import java.lang.annotation.ElementType;
19+
import java.lang.annotation.Retention;
20+
import java.lang.annotation.RetentionPolicy;
21+
import java.lang.annotation.Target;
22+
23+
import org.junit.Test;
1824
import static org.junit.jupiter.api.Assertions.assertFalse;
1925
import static org.junit.jupiter.api.Assertions.assertTrue;
2026

@@ -31,4 +37,71 @@ public void markedWithNoAutoStart() {
3137
DoNotAutoStart o = new DoNotAutoStart();
3238
assertFalse(NoAutoStartUtil.notMarkedWithNoAutoStart(o));
3339
}
40+
41+
42+
43+
/*
44+
* Annotation declared on implemented interface
45+
*/
46+
@Test
47+
public void noAutoStartOnInterface() {
48+
ComponentWithNoAutoStartOnInterface o = new ComponentWithNoAutoStartOnInterface();
49+
assertFalse(NoAutoStartUtil.notMarkedWithNoAutoStart(o));
50+
}
51+
52+
@NoAutoStart
53+
public interface NoAutoStartInterface {
54+
}
55+
56+
private static class ComponentWithNoAutoStartOnInterface implements NoAutoStartInterface {
57+
}
58+
59+
60+
61+
/*
62+
* Annotation declared on ancestor
63+
*/
64+
@Test
65+
public void noAutoStartOnAncestor() {
66+
ComponentWithNoAutoStartOnAncestor o = new ComponentWithNoAutoStartOnAncestor();
67+
assertFalse(NoAutoStartUtil.notMarkedWithNoAutoStart(o));
68+
}
69+
70+
private static class ComponentWithNoAutoStartOnAncestor extends DoNotAutoStart {
71+
}
72+
73+
74+
75+
/*
76+
* Annotation declared on interface implemented by an ancestor
77+
*/
78+
@Test
79+
public void noAutoStartOnInterfaceImplementedByAncestor() {
80+
ComponentWithAncestorImplementingInterfaceWithNoAutoStart o = new ComponentWithAncestorImplementingInterfaceWithNoAutoStart();
81+
assertFalse(NoAutoStartUtil.notMarkedWithNoAutoStart(o));
82+
}
83+
84+
private static class ComponentWithAncestorImplementingInterfaceWithNoAutoStart extends ComponentWithNoAutoStartOnInterface {
85+
}
86+
87+
88+
89+
/*
90+
* Custom annotation annotated with @NoAutoStart
91+
*/
92+
@Test
93+
public void noAutoStartAsMetaAnnotation() {
94+
ComponentWithMetaAnnotation o = new ComponentWithMetaAnnotation();
95+
assertFalse(NoAutoStartUtil.notMarkedWithNoAutoStart(o));
96+
}
97+
98+
@Retention(RetentionPolicy.RUNTIME)
99+
@Target(ElementType.TYPE)
100+
@NoAutoStart
101+
public @interface MetaNoAutoStart {
102+
}
103+
104+
@MetaNoAutoStart
105+
private static class ComponentWithMetaAnnotation {
106+
}
34107
}

0 commit comments

Comments
 (0)