This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push:
new 54635bc626 org.apache.juneau.common.reflect API improvements
54635bc626 is described below
commit 54635bc626c348ddf6287d8e32bfff0778170f64
Author: James Bognar <[email protected]>
AuthorDate: Wed Nov 19 16:44:11 2025 -0500
org.apache.juneau.common.reflect API improvements
---
.../juneau/common/reflect/AnnotationProvider.java | 448 ++++++++++++++++++++-
.../juneau/common/reflect/AnnotationTraversal.java | 21 +
.../juneau/common/reflect/ExecutableInfo.java | 3 +-
.../apache/juneau/common/reflect/MethodInfo.java | 18 -
.../src/main/java/org/apache/juneau/Context.java | 9 +-
.../httppart/bean/RequestBeanPropertyMeta.java | 5 +-
.../juneau/httppart/bean/ResponseBeanMeta.java | 5 +-
.../rest/client/remote/RemoteOperationMeta.java | 128 +-----
.../rest/client/remote/RemoteOperationReturn.java | 5 +-
.../java/org/apache/juneau/rest/RestContext.java | 6 +-
.../java/org/apache/juneau/rest/RestOpContext.java | 58 +--
.../juneau/rest/debug/BasicDebugEnablement.java | 3 +-
12 files changed, 539 insertions(+), 170 deletions(-)
diff --git
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationProvider.java
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationProvider.java
index 32c1766c43..928651b803 100644
---
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationProvider.java
+++
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationProvider.java
@@ -518,6 +518,101 @@ public class AnnotationProvider {
});
}
+ /**
+ * Streams annotations from a class that belong to the specified
annotation group.
+ *
+ * <p>
+ * This method is similar to {@link #find(Class, ClassInfo,
AnnotationTraversal...)} but filters
+ * annotations based on their {@link
org.apache.juneau.annotation.AnnotationGroup} membership rather
+ * than their exact type.
+ *
+ * <p>
+ * An annotation belongs to a group if it has an {@code
@AnnotationGroup} meta-annotation with the
+ * specified group class. This allows you to find all annotations that
are logically related.
+ *
+ * <h5 class='section'>Supported Traversal Types:</h5>
+ * <ul>
+ * <li>{@link AnnotationTraversal#SELF SELF} - Annotations
declared directly on this class
+ * <li>{@link AnnotationTraversal#PARENTS PARENTS} - Parent
classes and interfaces (child-to-parent order)
+ * <li>{@link AnnotationTraversal#PACKAGE PACKAGE} - The package
annotations
+ * </ul>
+ *
+ * <p>
+ * <b>Default:</b> If no traversals are specified, defaults to: {@code
PARENTS, PACKAGE}
+ * <br>Note: {@code PARENTS} includes the class itself plus all parent
classes and interfaces.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Find all bean-related annotations (that belong to the
Bean group)</jc>
+ * Stream<AnnotationInfo<?>> <jv>annotations</jv> =
+ * annotationProvider.findGroup(Bean.<jk>class</jk>,
<jv>classInfo</jv>);
+ * </p>
+ *
+ * @param <A> The annotation group type.
+ * @param group The annotation group class to filter by.
+ * @param clazz The class to search.
+ * @param traversals
+ * The traversal options. If not specified, defaults to {@code
PARENTS, PACKAGE}.
+ * <br>Valid values: {@link AnnotationTraversal#SELF SELF}, {@link
AnnotationTraversal#PARENTS PARENTS},
+ * {@link AnnotationTraversal#PACKAGE PACKAGE}
+ * @return A stream of {@link AnnotationInfo} objects belonging to the
specified group. Never <jk>null</jk>.
+ */
+ public <A extends Annotation> Stream<AnnotationInfo<?>>
findGroup(Class<A> group, ClassInfo clazz, AnnotationTraversal... traversals) {
+ assertArgNotNull("group", group);
+ assertArgNotNull("clazz", clazz);
+ if (traversals.length == 0)
+ traversals = a(PARENTS, PACKAGE);
+
+ return Arrays.stream(traversals)
+
.sorted(Comparator.comparingInt(AnnotationTraversal::getOrder))
+ .flatMap(traversal -> {
+ if (traversal == SELF) {
+ return concat(
+
classAnnnotations.get(clazz.inner()).stream(),
+
clazz.getDeclaredAnnotations().stream().map(a -> (AnnotationInfo<?>)a)
+ )
+ .filter(a -> a.isInGroup(group));
+ } else if (traversal == PARENTS) {
+ return
clazz.getParentsAndInterfaces().stream().flatMap(x ->
+ concat(
+
classAnnnotations.get(x.inner()).stream(),
+
x.getDeclaredAnnotations().stream().map(a -> (AnnotationInfo<?>)a)
+ ).filter(a ->
a.isInGroup(group))
+ );
+ } else if (traversal == PACKAGE) {
+ return opt(clazz.getPackage()).map(x ->
x.getAnnotations().stream().map(a -> (AnnotationInfo<?>)a).filter(a ->
a.isInGroup(group))).orElse(Stream.empty());
+ }
+ throw illegalArg("Invalid traversal type for
class annotations: {0}", traversal);
+ });
+ }
+
+ /**
+ * Streams annotations from a class that belong to the specified
annotation group in parent-first order.
+ *
+ * <p>
+ * This is equivalent to calling {@link #findGroup(Class, ClassInfo,
AnnotationTraversal...)}
+ * and reversing the result.
+ *
+ * <p>
+ * Use this when you need parent annotations to take precedence over
child annotations.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Get bean-group annotations in parent-first order</jc>
+ * Stream<AnnotationInfo<?>> <jv>annotations</jv> =
+ *
annotationProvider.findGroupTopDown(Bean.<jk>class</jk>, <jv>classInfo</jv>);
+ * </p>
+ *
+ * @param <A> The annotation group type.
+ * @param group The annotation group class to filter by.
+ * @param clazz The class to search.
+ * @param traversals The traversal options.
+ * @return A stream of {@link AnnotationInfo} objects in parent-first
order. Never <jk>null</jk>.
+ */
+ public <A extends Annotation> Stream<AnnotationInfo<?>>
findGroupTopDown(Class<A> group, ClassInfo clazz, AnnotationTraversal...
traversals) {
+ return rstream(findGroup(group, clazz, traversals).toList());
+ }
+
/**
* Streams all annotations from a class using configurable traversal
options, without filtering by annotation type.
*
@@ -686,6 +781,8 @@ public class AnnotationProvider {
m.getDeclaredAnnotations().stream()
).filter(a ->
a.isType(type)).map(a -> (AnnotationInfo<A>)a)
);
+ } else if (traversal == DECLARING_CLASS) {
+ return find(type,
method.getDeclaringClass(), PARENTS);
} else if (traversal == RETURN_TYPE) {
return find(type,
method.getReturnType().unwrap(Value.class, Optional.class), PARENTS);
} else if (traversal == PACKAGE) {
@@ -695,6 +792,106 @@ public class AnnotationProvider {
});
}
+ /**
+ * Streams annotations from a method that belong to the specified
annotation group.
+ *
+ * <p>
+ * This method is similar to {@link #find(Class, MethodInfo,
AnnotationTraversal...)} but filters
+ * annotations based on their {@link
org.apache.juneau.annotation.AnnotationGroup} membership rather
+ * than their exact type.
+ *
+ * <p>
+ * An annotation belongs to a group if it has an {@code
@AnnotationGroup} meta-annotation with the
+ * specified group class. This allows you to find all annotations that
are logically related.
+ *
+ * <h5 class='section'>Supported Traversal Types:</h5>
+ * <ul>
+ * <li>{@link AnnotationTraversal#SELF SELF} - Annotations
declared directly on this method
+ * <li>{@link AnnotationTraversal#MATCHING_METHODS
MATCHING_METHODS} - Matching methods in parent classes (child-to-parent)
+ * <li>{@link AnnotationTraversal#DECLARING_CLASS DECLARING_CLASS}
- The declaring class hierarchy
+ * <li>{@link AnnotationTraversal#RETURN_TYPE RETURN_TYPE} - The
return type hierarchy (includes class parents and package)
+ * <li>{@link AnnotationTraversal#PACKAGE PACKAGE} - The declaring
class's package annotations
+ * </ul>
+ *
+ * <p>
+ * <b>Default:</b> If no traversals are specified, defaults to: {@code
SELF, MATCHING_METHODS, RETURN_TYPE, PACKAGE}
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Find all REST operation annotations (that belong to the
RestOp group)</jc>
+ * Stream<AnnotationInfo<?>> <jv>annotations</jv> =
+ * annotationProvider.findGroup(RestOp.<jk>class</jk>,
<jv>methodInfo</jv>);
+ * </p>
+ *
+ * @param <A> The annotation group type.
+ * @param group The annotation group class to filter by.
+ * @param method The method to search.
+ * @param traversals
+ * The traversal options. If not specified, defaults to {@code
SELF, MATCHING_METHODS, RETURN_TYPE, PACKAGE}.
+ * <br>Valid values: {@link AnnotationTraversal#SELF SELF}, {@link
AnnotationTraversal#MATCHING_METHODS MATCHING_METHODS},
+ * {@link AnnotationTraversal#DECLARING_CLASS DECLARING_CLASS},
{@link AnnotationTraversal#RETURN_TYPE RETURN_TYPE},
+ * {@link AnnotationTraversal#PACKAGE PACKAGE}
+ * @return A stream of {@link AnnotationInfo} objects belonging to the
specified group. Never <jk>null</jk>.
+ */
+ public <A extends Annotation> Stream<AnnotationInfo<?>>
findGroup(Class<A> group, MethodInfo method, AnnotationTraversal... traversals)
{
+ assertArgNotNull("group", group);
+ assertArgNotNull("method", method);
+ if (traversals.length == 0)
+ traversals = a(SELF, MATCHING_METHODS, RETURN_TYPE,
PACKAGE);
+
+ return Arrays.stream(traversals)
+
.sorted(Comparator.comparingInt(AnnotationTraversal::getOrder))
+ .flatMap(traversal -> {
+ if (traversal == SELF) {
+ return concat(
+
methodAnnotations.get(method.inner()).stream(),
+
method.getDeclaredAnnotations().stream().map(a -> (AnnotationInfo<?>)a)
+ ).filter(a -> a.isInGroup(group));
+ } else if (traversal == MATCHING_METHODS) {
+ return
method.getMatchingMethods().stream().skip(1).flatMap(m ->
+ concat(
+
methodAnnotations.get(m.inner()).stream(),
+
m.getDeclaredAnnotations().stream().map(a -> (AnnotationInfo<?>)a)
+ ).filter(a ->
a.isInGroup(group))
+ );
+ } else if (traversal == DECLARING_CLASS) {
+ return findGroup(group,
method.getDeclaringClass(), PARENTS);
+ } else if (traversal == RETURN_TYPE) {
+ return findGroup(group,
method.getReturnType().unwrap(Value.class, Optional.class), PARENTS);
+ } else if (traversal == PACKAGE) {
+ return
opt(method.getDeclaringClass().getPackage()).map(x ->
x.getAnnotations().stream().map(a -> (AnnotationInfo<?>)a).filter(a ->
a.isInGroup(group))).orElse(Stream.empty());
+ }
+ throw illegalArg("Invalid traversal type for
method annotations: {0}", traversal);
+ });
+ }
+
+ /**
+ * Streams annotations from a method that belong to the specified
annotation group in parent-first order.
+ *
+ * <p>
+ * This is equivalent to calling {@link #findGroup(Class, MethodInfo,
AnnotationTraversal...)}
+ * and reversing the result.
+ *
+ * <p>
+ * Use this when you need parent annotations to take precedence over
child annotations.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Get REST operation annotations in parent-first order</jc>
+ * Stream<AnnotationInfo<?>> <jv>annotations</jv> =
+ *
annotationProvider.findGroupTopDown(RestOp.<jk>class</jk>, <jv>methodInfo</jv>);
+ * </p>
+ *
+ * @param <A> The annotation group type.
+ * @param group The annotation group class to filter by.
+ * @param method The method to search.
+ * @param traversals The traversal options.
+ * @return A stream of {@link AnnotationInfo} objects in parent-first
order. Never <jk>null</jk>.
+ */
+ public <A extends Annotation> Stream<AnnotationInfo<?>>
findGroupTopDown(Class<A> group, MethodInfo method, AnnotationTraversal...
traversals) {
+ return rstream(findGroup(group, method, traversals).toList());
+ }
+
/**
* Streams all annotations from a method using configurable traversal
options, without filtering by annotation type.
*
@@ -720,7 +917,7 @@ public class AnnotationProvider {
public Stream<AnnotationInfo<Annotation>> find(MethodInfo method,
AnnotationTraversal... traversals) {
assertArgNotNull("method", method);
if (traversals.length == 0)
- traversals = a(SELF, MATCHING_METHODS, RETURN_TYPE,
PACKAGE);
+ traversals = a(SELF, MATCHING_METHODS, DECLARING_CLASS,
RETURN_TYPE, PACKAGE);
return Arrays.stream(traversals)
.sorted(Comparator.comparingInt(AnnotationTraversal::getOrder))
@@ -737,6 +934,8 @@ public class AnnotationProvider {
m.getDeclaredAnnotations().stream()
);
});
+ } else if (traversal == DECLARING_CLASS) {
+ return find(method.getDeclaringClass(),
PARENTS);
} else if (traversal == RETURN_TYPE) {
return
find(method.getReturnType().unwrap(Value.class, Optional.class), PARENTS);
} else if (traversal == PACKAGE) {
@@ -878,6 +1077,91 @@ public class AnnotationProvider {
});
}
+ /**
+ * Streams annotations from a parameter that belong to the specified
annotation group.
+ *
+ * <p>
+ * This method is similar to {@link #find(Class, ParameterInfo,
AnnotationTraversal...)} but filters
+ * annotations based on their {@link
org.apache.juneau.annotation.AnnotationGroup} membership rather
+ * than their exact type.
+ *
+ * <p>
+ * An annotation belongs to a group if it has an {@code
@AnnotationGroup} meta-annotation with the
+ * specified group class. This allows you to find all annotations that
are logically related.
+ *
+ * <h5 class='section'>Supported Traversal Types:</h5>
+ * <ul>
+ * <li>{@link AnnotationTraversal#SELF SELF} - Annotations
declared directly on this parameter
+ * <li>{@link AnnotationTraversal#MATCHING_PARAMETERS
MATCHING_PARAMETERS} - Matching parameters in parent methods/constructors
(child-to-parent)
+ * <li>{@link AnnotationTraversal#PARAMETER_TYPE PARAMETER_TYPE} -
The parameter's type hierarchy (includes class parents and package)
+ * </ul>
+ *
+ * <p>
+ * <b>Default:</b> If no traversals are specified, defaults to: {@code
SELF, MATCHING_PARAMETERS, PARAMETER_TYPE}
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Find all REST parameter annotations (that belong to a
group)</jc>
+ * Stream<AnnotationInfo<?>> <jv>annotations</jv> =
+ * annotationProvider.findGroup(Query.<jk>class</jk>,
<jv>parameterInfo</jv>);
+ * </p>
+ *
+ * @param <A> The annotation group type.
+ * @param group The annotation group class to filter by.
+ * @param parameter The parameter to search.
+ * @param traversals
+ * The traversal options. If not specified, defaults to {@code
SELF, MATCHING_PARAMETERS, PARAMETER_TYPE}.
+ * <br>Valid values: {@link AnnotationTraversal#SELF SELF}, {@link
AnnotationTraversal#MATCHING_PARAMETERS MATCHING_PARAMETERS},
+ * {@link AnnotationTraversal#PARAMETER_TYPE PARAMETER_TYPE}
+ * @return A stream of {@link AnnotationInfo} objects belonging to the
specified group. Never <jk>null</jk>.
+ */
+ public <A extends Annotation> Stream<AnnotationInfo<?>>
findGroup(Class<A> group, ParameterInfo parameter, AnnotationTraversal...
traversals) {
+ assertArgNotNull("group", group);
+ assertArgNotNull("parameter", parameter);
+ if (traversals.length == 0)
+ traversals = a(SELF, MATCHING_PARAMETERS,
PARAMETER_TYPE);
+
+ return Arrays.stream(traversals)
+
.sorted(Comparator.comparingInt(AnnotationTraversal::getOrder))
+ .flatMap(traversal -> {
+ if (traversal == SELF) {
+ return
parameter.getAnnotations().stream().map(a -> (AnnotationInfo<?>)a).filter(a ->
a.isInGroup(group));
+ } else if (traversal == MATCHING_PARAMETERS) {
+ return
parameter.getMatchingParameters().stream().skip(1).flatMap(x ->
x.getAnnotations().stream().map(a -> (AnnotationInfo<?>)a).filter(a ->
a.isInGroup(group)));
+ } else if (traversal == PARAMETER_TYPE) {
+ return findGroup(group,
parameter.getParameterType().unwrap(Value.class, Optional.class), PARENTS,
PACKAGE);
+ }
+ throw illegalArg("Invalid traversal type for
parameter annotations: {0}", traversal);
+ });
+ }
+
+ /**
+ * Streams annotations from a parameter that belong to the specified
annotation group in parent-first order.
+ *
+ * <p>
+ * This is equivalent to calling {@link #findGroup(Class,
ParameterInfo, AnnotationTraversal...)}
+ * and reversing the result.
+ *
+ * <p>
+ * Use this when you need parent annotations to take precedence over
child annotations.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Get parameter annotations in parent-first order</jc>
+ * Stream<AnnotationInfo<?>> <jv>annotations</jv> =
+ *
annotationProvider.findGroupTopDown(Query.<jk>class</jk>,
<jv>parameterInfo</jv>);
+ * </p>
+ *
+ * @param <A> The annotation group type.
+ * @param group The annotation group class to filter by.
+ * @param parameter The parameter to search.
+ * @param traversals The traversal options.
+ * @return A stream of {@link AnnotationInfo} objects in parent-first
order. Never <jk>null</jk>.
+ */
+ public <A extends Annotation> Stream<AnnotationInfo<?>>
findGroupTopDown(Class<A> group, ParameterInfo parameter,
AnnotationTraversal... traversals) {
+ return rstream(findGroup(group, parameter,
traversals).toList());
+ }
+
/**
* Streams annotations from a parameter using configurable traversal
options in parent-to-child order.
*
@@ -1066,6 +1350,87 @@ public class AnnotationProvider {
});
}
+ /**
+ * Streams annotations from a field that belong to the specified
annotation group.
+ *
+ * <p>
+ * This method is similar to {@link #find(Class, FieldInfo,
AnnotationTraversal...)} but filters
+ * annotations based on their {@link
org.apache.juneau.annotation.AnnotationGroup} membership rather
+ * than their exact type.
+ *
+ * <p>
+ * An annotation belongs to a group if it has an {@code
@AnnotationGroup} meta-annotation with the
+ * specified group class. This allows you to find all annotations that
are logically related.
+ *
+ * <h5 class='section'>Supported Traversal Types:</h5>
+ * <ul>
+ * <li>{@link AnnotationTraversal#SELF SELF} - Annotations
declared directly on this field
+ * </ul>
+ *
+ * <p>
+ * <b>Default:</b> If no traversals are specified, defaults to: {@code
SELF}
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Find all bean property annotations (that belong to the
Beanp group)</jc>
+ * Stream<AnnotationInfo<?>> <jv>annotations</jv> =
+ * annotationProvider.findGroup(Beanp.<jk>class</jk>,
<jv>fieldInfo</jv>);
+ * </p>
+ *
+ * @param <A> The annotation group type.
+ * @param group The annotation group class to filter by.
+ * @param field The field to search.
+ * @param traversals
+ * The traversal options. If not specified, defaults to {@code
SELF}.
+ * <br>Valid values: {@link AnnotationTraversal#SELF SELF}
+ * @return A stream of {@link AnnotationInfo} objects belonging to the
specified group. Never <jk>null</jk>.
+ */
+ public <A extends Annotation> Stream<AnnotationInfo<?>>
findGroup(Class<A> group, FieldInfo field, AnnotationTraversal... traversals) {
+ assertArgNotNull("group", group);
+ assertArgNotNull("field", field);
+ if (traversals.length == 0)
+ traversals = a(SELF);
+
+ return Arrays.stream(traversals)
+
.sorted(Comparator.comparingInt(AnnotationTraversal::getOrder))
+ .flatMap(traversal -> {
+ if (traversal == SELF) {
+ return concat(
+
fieldAnnotations.get(field.inner()).stream(),
+
field.getAnnotations().stream().map(a -> (AnnotationInfo<?>)a)
+ ).filter(a -> a.isInGroup(group));
+ }
+ throw illegalArg("Invalid traversal type for
field annotations: {0}", traversal);
+ });
+ }
+
+ /**
+ * Streams annotations from a field that belong to the specified
annotation group in parent-first order.
+ *
+ * <p>
+ * This is equivalent to calling {@link #findGroup(Class, FieldInfo,
AnnotationTraversal...)}
+ * and reversing the result.
+ *
+ * <p>
+ * Use this when you need parent annotations to take precedence over
child annotations.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Get bean property annotations in parent-first order</jc>
+ * Stream<AnnotationInfo<?>> <jv>annotations</jv> =
+ *
annotationProvider.findGroupTopDown(Beanp.<jk>class</jk>, <jv>fieldInfo</jv>);
+ * </p>
+ *
+ * @param <A> The annotation group type.
+ * @param group The annotation group class to filter by.
+ * @param field The field to search.
+ * @param traversals The traversal options.
+ * @return A stream of {@link AnnotationInfo} objects in parent-first
order. Never <jk>null</jk>.
+ */
+ public <A extends Annotation> Stream<AnnotationInfo<?>>
findGroupTopDown(Class<A> group, FieldInfo field, AnnotationTraversal...
traversals) {
+ return rstream(findGroup(group, field, traversals).toList());
+ }
+
/**
* Streams all annotations from a field using configurable traversal
options, without filtering by annotation type.
*
@@ -1208,6 +1573,87 @@ public class AnnotationProvider {
});
}
+ /**
+ * Streams annotations from a constructor that belong to the specified
annotation group.
+ *
+ * <p>
+ * This method is similar to {@link #find(Class, ConstructorInfo,
AnnotationTraversal...)} but filters
+ * annotations based on their {@link
org.apache.juneau.annotation.AnnotationGroup} membership rather
+ * than their exact type.
+ *
+ * <p>
+ * An annotation belongs to a group if it has an {@code
@AnnotationGroup} meta-annotation with the
+ * specified group class. This allows you to find all annotations that
are logically related.
+ *
+ * <h5 class='section'>Supported Traversal Types:</h5>
+ * <ul>
+ * <li>{@link AnnotationTraversal#SELF SELF} - Annotations
declared directly on this constructor
+ * </ul>
+ *
+ * <p>
+ * <b>Default:</b> If no traversals are specified, defaults to: {@code
SELF}
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Find all bean-related annotations on constructor</jc>
+ * Stream<AnnotationInfo<?>> <jv>annotations</jv> =
+ * annotationProvider.findGroup(Bean.<jk>class</jk>,
<jv>constructorInfo</jv>);
+ * </p>
+ *
+ * @param <A> The annotation group type.
+ * @param group The annotation group class to filter by.
+ * @param constructor The constructor to search.
+ * @param traversals
+ * The traversal options. If not specified, defaults to {@code
SELF}.
+ * <br>Valid values: {@link AnnotationTraversal#SELF SELF}
+ * @return A stream of {@link AnnotationInfo} objects belonging to the
specified group. Never <jk>null</jk>.
+ */
+ public <A extends Annotation> Stream<AnnotationInfo<?>>
findGroup(Class<A> group, ConstructorInfo constructor, AnnotationTraversal...
traversals) {
+ assertArgNotNull("group", group);
+ assertArgNotNull("constructor", constructor);
+ if (traversals.length == 0)
+ traversals = a(SELF);
+
+ return Arrays.stream(traversals)
+
.sorted(Comparator.comparingInt(AnnotationTraversal::getOrder))
+ .flatMap(traversal -> {
+ if (traversal == SELF) {
+ return concat(
+
constructorAnnotations.get(constructor.inner()).stream(),
+
constructor.getDeclaredAnnotations().stream().map(a -> (AnnotationInfo<?>)a)
+ ).filter(a -> a.isInGroup(group));
+ }
+ throw illegalArg("Invalid traversal type for
constructor annotations: {0}", traversal);
+ });
+ }
+
+ /**
+ * Streams annotations from a constructor that belong to the specified
annotation group in parent-first order.
+ *
+ * <p>
+ * This is equivalent to calling {@link #findGroup(Class,
ConstructorInfo, AnnotationTraversal...)}
+ * and reversing the result.
+ *
+ * <p>
+ * Use this when you need parent annotations to take precedence over
child annotations.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Get bean-related annotations in parent-first order</jc>
+ * Stream<AnnotationInfo<?>> <jv>annotations</jv> =
+ *
annotationProvider.findGroupTopDown(Bean.<jk>class</jk>,
<jv>constructorInfo</jv>);
+ * </p>
+ *
+ * @param <A> The annotation group type.
+ * @param group The annotation group class to filter by.
+ * @param constructor The constructor to search.
+ * @param traversals The traversal options.
+ * @return A stream of {@link AnnotationInfo} objects in parent-first
order. Never <jk>null</jk>.
+ */
+ public <A extends Annotation> Stream<AnnotationInfo<?>>
findGroupTopDown(Class<A> group, ConstructorInfo constructor,
AnnotationTraversal... traversals) {
+ return rstream(findGroup(group, constructor,
traversals).toList());
+ }
+
/**
* Streams all annotations from a constructor using configurable
traversal options, without filtering by annotation type.
*
diff --git
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationTraversal.java
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationTraversal.java
index a9a9afb4ab..85c18eb4e1 100644
---
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationTraversal.java
+++
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationTraversal.java
@@ -159,6 +159,27 @@ public enum AnnotationTraversal {
*/
RETURN_TYPE(30),
+ /**
+ * Include the declaring class hierarchy in the traversal.
+ *
+ * <p>
+ * For methods, fields, and constructors: Searches annotations on the
declaring class and its parent hierarchy.
+ * Automatically includes the declaring class and all its parents and
interfaces.
+ *
+ * <h5 class='section'>Applicable to:</h5>
+ * Methods, fields, and constructors.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Given: class Child extends Parent { void method() {...}
}</jc>
+ * <jc>// Searches: Child hierarchy (Child, Parent, interfaces,
etc.)</jc>
+ * </p>
+ *
+ * <h5 class='section'>Order:</h5>
+ * Precedence: 35
+ */
+ DECLARING_CLASS(35),
+
/**
* Include the parameter type in the traversal.
*
diff --git
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ExecutableInfo.java
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ExecutableInfo.java
index 0640d58312..f8c03b3e8a 100644
---
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ExecutableInfo.java
+++
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ExecutableInfo.java
@@ -19,6 +19,7 @@ package org.apache.juneau.common.reflect;
import static org.apache.juneau.common.reflect.ClassArrayFormat.*;
import static org.apache.juneau.common.reflect.ClassNameFormat.*;
import static org.apache.juneau.common.utils.AssertionUtils.*;
+import static org.apache.juneau.common.utils.ClassUtils.*;
import static org.apache.juneau.common.utils.CollectionUtils.*;
import static org.apache.juneau.common.utils.StringUtils.*;
import static org.apache.juneau.common.utils.Utils.*;
@@ -64,7 +65,7 @@ public abstract class ExecutableInfo extends AccessibleInfo {
this.isConstructor = inner instanceof Constructor;
this.parameters = memoize(this::findParameters);
this.exceptions = memoize(() ->
stream(inner.getExceptionTypes()).map(ClassInfo::of).toList());
- this.declaredAnnotations = memoize(() ->
stream(inner.getDeclaredAnnotations()).map(a ->
AnnotationInfo.of((Annotatable)this, a)).toList());
+ this.declaredAnnotations = memoize(() ->
stream(inner.getDeclaredAnnotations()).flatMap(a -> streamRepeated(a)).map(a ->
AnnotationInfo.of((Annotatable)this, a)).toList());
this.shortName = memoize(() -> f("{0}({1})", getSimpleName(),
getParameters().stream().map(p ->
p.getParameterType().getNameSimple()).collect(joining(","))));
this.fullName = memoize(this::findFullName);
}
diff --git
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/MethodInfo.java
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/MethodInfo.java
index 5bd6ff5a62..cd6cff422a 100644
---
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/MethodInfo.java
+++
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/MethodInfo.java
@@ -223,24 +223,6 @@ public class MethodInfo extends ExecutableInfo implements
Comparable<MethodInfo>
return allAnnotationInfos.get();
}
- /**
- * Returns all annotations of the specified type on the declaring
class, this method and parent overridden methods, return type, and declaring
class's package.
- *
- * <p>
- * See {@link #getAllAnnotations()} for ordering details.
- *
- * @param <A> The annotation type.
- * @param type The annotation type to filter by.
- * @return A stream of matching annotation infos.
- */
- @SuppressWarnings("unchecked")
- public <A extends Annotation> Stream<AnnotationInfo<A>>
getAllAnnotations(Class<A> type) {
- assertArgNotNull("type", type);
- return getAllAnnotations().stream()
- .filter(a -> a.isType(type))
- .map(a -> (AnnotationInfo<A>)a);
- }
-
private List<MethodInfo> findMatchingMethods() {
var result = new ArrayList<MethodInfo>();
result.add(this); // 1. This method
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Context.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Context.java
index 2d29052855..c8f4992485 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Context.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Context.java
@@ -669,15 +669,16 @@ public abstract class Context {
}
private static AnnotationWorkList traverse(AnnotationWorkList
work, Object x) {
+ AnnotationProvider ap = AnnotationProvider.INSTANCE;
CollectionUtils.traverse(x, y -> {
if (x instanceof Class<?> x2)
-
work.add(rstream(ClassInfo.of(x2).getAnnotations()).filter(CONTEXT_APPLY_FILTER).map(ai
-> (AnnotationInfo<?>)ai));
+
work.add(ap.findTopDown(ClassInfo.of(x2)).filter(CONTEXT_APPLY_FILTER).map(ai
-> (AnnotationInfo<?>)ai));
else if (x instanceof ClassInfo x2)
-
work.add(rstream(x2.getAnnotations()).filter(CONTEXT_APPLY_FILTER).map(ai ->
(AnnotationInfo<?>)ai));
+
work.add(ap.findTopDown(x2).filter(CONTEXT_APPLY_FILTER).map(ai ->
(AnnotationInfo<?>)ai));
else if (x instanceof Method x2)
-
work.add(rstream(MethodInfo.of(x2).getAllAnnotations()).filter(CONTEXT_APPLY_FILTER).map(ai
-> (AnnotationInfo<?>)ai));
+
work.add(ap.findTopDown(MethodInfo.of(x2)).filter(CONTEXT_APPLY_FILTER).map(ai
-> (AnnotationInfo<?>)ai));
else if (x instanceof MethodInfo x2)
-
work.add(rstream(x2.getAllAnnotations()).filter(CONTEXT_APPLY_FILTER).map(ai ->
(AnnotationInfo<?>)ai));
+
work.add(ap.findTopDown(x2).filter(CONTEXT_APPLY_FILTER).map(ai ->
(AnnotationInfo<?>)ai));
else
illegalArg("Invalid type passed to
applyAnnotations: {0}", cn(x));
});
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/bean/RequestBeanPropertyMeta.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/bean/RequestBeanPropertyMeta.java
index f6749143ca..8b3bcf1fd0 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/bean/RequestBeanPropertyMeta.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/bean/RequestBeanPropertyMeta.java
@@ -65,8 +65,9 @@ public class RequestBeanPropertyMeta {
static RequestBeanPropertyMeta.Builder create(HttpPartType partType,
Class<? extends Annotation> c, MethodInfo m) {
HttpPartSchema.Builder sb =
HttpPartSchema.create().name(m.getPropertyName());
- rstream(m.getAllAnnotations()).map(x ->
x.cast(Schema.class)).filter(Objects::nonNull).map(AnnotationInfo::inner).forEach(x
-> sb.apply(x));
- rstream(m.getAllAnnotations()).map(x ->
x.cast(c)).filter(Objects::nonNull).map(AnnotationInfo::inner).forEach(x ->
sb.apply(x));
+ AnnotationProvider ap = AnnotationProvider.INSTANCE;
+ ap.findTopDown(m).map(x ->
x.cast(Schema.class)).filter(Objects::nonNull).map(AnnotationInfo::inner).forEach(x
-> sb.apply(x));
+ ap.findTopDown(m).map(x ->
x.cast(c)).filter(Objects::nonNull).map(AnnotationInfo::inner).forEach(x ->
sb.apply(x));
return new
Builder().partType(partType).schema(sb.build()).getter(m.inner());
}
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/bean/ResponseBeanMeta.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/bean/ResponseBeanMeta.java
index 8a4e54f6c8..f0757bfee8 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/bean/ResponseBeanMeta.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/bean/ResponseBeanMeta.java
@@ -127,8 +127,9 @@ public class ResponseBeanMeta {
return null;
var b = new Builder(annotations);
b.apply(m.getReturnType().unwrap(Value.class,
Optional.class).innerType());
- rstream(m.getAllAnnotations()).map(x ->
x.cast(Response.class)).filter(Objects::nonNull).map(AnnotationInfo::inner).forEach(x
-> b.apply(x));
- rstream(m.getAllAnnotations()).map(x ->
x.cast(StatusCode.class)).filter(Objects::nonNull).map(AnnotationInfo::inner).forEach(x
-> b.apply(x));
+ AnnotationProvider ap = AnnotationProvider.INSTANCE;
+ ap.findTopDown(m).map(x ->
x.cast(Response.class)).filter(Objects::nonNull).map(AnnotationInfo::inner).forEach(x
-> b.apply(x));
+ ap.findTopDown(m).map(x ->
x.cast(StatusCode.class)).filter(Objects::nonNull).map(AnnotationInfo::inner).forEach(x
-> b.apply(x));
return b.build();
}
diff --git
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteOperationMeta.java
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteOperationMeta.java
index 9808de8688..430fef4ba8 100644
---
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteOperationMeta.java
+++
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteOperationMeta.java
@@ -63,8 +63,9 @@ public class RemoteOperationMeta {
Builder(String parentPath, Method m, String defaultMethod) {
var mi = MethodInfo.of(m);
+ AnnotationProvider ap = AnnotationProvider.INSTANCE;
- List<AnnotationInfo<?>> al =
rstream(mi.getAllAnnotations()).filter(REMOTE_OP_GROUP).map(ai ->
(AnnotationInfo<?>)ai).collect(Collectors.toList());
+ List<AnnotationInfo<?>> al =
ap.findTopDown(mi).filter(REMOTE_OP_GROUP).map(ai ->
(AnnotationInfo<?>)ai).collect(Collectors.toList());
if (al.isEmpty())
al =
rstream(mi.getReturnType().unwrap(Value.class,
Optional.class).getAnnotations()).filter(REMOTE_OP_GROUP).map(ai ->
(AnnotationInfo<?>)ai).collect(Collectors.toList());
@@ -145,138 +146,49 @@ public class RemoteOperationMeta {
// These handle both individual annotations and repeated
annotation arrays
private void processContentDefaults(MethodInfo mi) {
- rstream(mi.getAllAnnotations())
+ AnnotationProvider.INSTANCE.find(mi)
.map(x -> x.cast(Content.class))
.filter(Objects::nonNull)
- .map(AnnotationInfo::inner)
- .forEach(c -> {
- String def = c.def();
- if (isNotEmpty(def)) {
- contentDefault = def;
- }
- });
- rstream(mi.getAllAnnotations())
- .map(x -> x.cast(ContentAnnotation.Array.class))
- .filter(Objects::nonNull)
- .map(AnnotationInfo::inner)
- .forEach(x -> {
- for (var c : x.value()) {
- String def = c.def();
- if (isNotEmpty(def)) {
- contentDefault = def;
- }
- }
- });
+ .map(x -> x.inner().def())
+ .filter(x -> isNotBlank(x))
+ .findFirst()
+ .ifPresent(x -> contentDefault = x);
}
private static void processFormDataDefaults(MethodInfo mi,
Map<String,String> defaults) {
- rstream(mi.getAllAnnotations())
+ AnnotationProvider.INSTANCE.findTopDown(mi)
.map(x -> x.cast(FormData.class))
.filter(Objects::nonNull)
.map(AnnotationInfo::inner)
- .forEach(fd -> {
- String name = firstNonEmpty(fd.name(),
fd.value());
- String def = fd.def();
- if (isNotEmpty(name) &&
isNotEmpty(def)) {
- defaults.put(name, def);
- }
- });
- rstream(mi.getAllAnnotations())
- .map(x ->
x.cast(FormDataAnnotation.Array.class))
- .filter(Objects::nonNull)
- .map(AnnotationInfo::inner)
- .forEach(x -> {
- for (var fd : x.value()) {
- String name =
firstNonEmpty(fd.name(), fd.value());
- String def = fd.def();
- if (isNotEmpty(name) &&
isNotEmpty(def)) {
- defaults.put(name, def);
- }
- }
- });
+ .filter(x -> isAnyNotEmpty(x.name(), x.value())
&& isNotEmpty(x.def()))
+ .forEach(x ->
defaults.put(firstNonEmpty(x.name(), x.value()), x.def()));
}
private static void processHeaderDefaults(MethodInfo mi,
Map<String,String> defaults) {
- // Check for individual @Header annotations
- rstream(mi.getAllAnnotations())
+ AnnotationProvider.INSTANCE.findTopDown(mi)
.map(x -> x.cast(Header.class))
.filter(Objects::nonNull)
.map(AnnotationInfo::inner)
- .forEach(h -> {
- String name = firstNonEmpty(h.name(),
h.value());
- String def = h.def();
- if (isNotEmpty(name) &&
isNotEmpty(def)) {
- defaults.put(name, def);
- }
- });
- // Check for @Header.Array (repeated annotations)
- rstream(mi.getAllAnnotations())
- .map(x -> x.cast(HeaderAnnotation.Array.class))
- .filter(Objects::nonNull)
- .map(AnnotationInfo::inner)
- .forEach(x -> {
- for (var h : x.value()) {
- String name =
firstNonEmpty(h.name(), h.value());
- String def = h.def();
- if (isNotEmpty(name) &&
isNotEmpty(def)) {
- defaults.put(name, def);
- }
- }
- });
+ .filter(x -> isAnyNotEmpty(x.name(), x.value())
&& isNotEmpty(x.def()))
+ .forEach(x ->
defaults.put(firstNonEmpty(x.name(), x.value()), x.def()));
}
-
+
private static void processPathDefaults(MethodInfo mi,
Map<String,String> defaults) {
- rstream(mi.getAllAnnotations())
+ AnnotationProvider.INSTANCE.findTopDown(mi)
.map(x -> x.cast(Path.class))
.filter(Objects::nonNull)
.map(AnnotationInfo::inner)
- .forEach(p -> {
- String name = firstNonEmpty(p.name(),
p.value());
- String def = p.def();
- if (isNotEmpty(name) && ne(NONE, def)) {
- defaults.put(name, def);
- }
- });
- rstream(mi.getAllAnnotations())
- .map(x -> x.cast(PathAnnotation.Array.class))
- .filter(Objects::nonNull)
- .map(AnnotationInfo::inner)
- .forEach(x -> {
- for (var p : x.value()) {
- String name =
firstNonEmpty(p.name(), p.value());
- String def = p.def();
- if (isNotEmpty(name) &&
ne(NONE, def)) {
- defaults.put(name, def);
- }
- }
- });
+ .filter(x -> isAnyNotEmpty(x.name(), x.value())
&& ne(NONE, x.def()))
+ .forEach(x ->
defaults.put(firstNonEmpty(x.name(), x.value()), x.def()));
}
private static void processQueryDefaults(MethodInfo mi,
Map<String,String> defaults) {
- rstream(mi.getAllAnnotations())
+ AnnotationProvider.INSTANCE.findTopDown(mi)
.map(x -> x.cast(Query.class))
.filter(Objects::nonNull)
.map(AnnotationInfo::inner)
- .forEach(q -> {
- String name = firstNonEmpty(q.name(),
q.value());
- String def = q.def();
- if (isNotEmpty(name) &&
isNotEmpty(def)) {
- defaults.put(name, def);
- }
- });
- rstream(mi.getAllAnnotations())
- .map(x -> x.cast(QueryAnnotation.Array.class))
- .filter(Objects::nonNull)
- .map(AnnotationInfo::inner)
- .forEach(x -> {
- for (var q : x.value()) {
- String name =
firstNonEmpty(q.name(), q.value());
- String def = q.def();
- if (isNotEmpty(name) &&
isNotEmpty(def)) {
- defaults.put(name, def);
- }
- }
- });
+ .filter(x -> isAnyNotEmpty(x.name(), x.value())
&& isNotEmpty(x.def()))
+ .forEach(x ->
defaults.put(firstNonEmpty(x.name(), x.value()), x.def()));
}
}
diff --git
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteOperationReturn.java
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteOperationReturn.java
index 05149ec5e2..4525f54135 100644
---
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteOperationReturn.java
+++
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteOperationReturn.java
@@ -49,9 +49,8 @@ public class RemoteOperationReturn {
RemoteOperationReturn(MethodInfo m) {
ClassInfo rt = m.getReturnType();
- List<AnnotationInfo<?>> al =
rstream(m.getAllAnnotations()).filter(REMOTE_OP_GROUP).map(ai ->
(AnnotationInfo<?>)ai).collect(Collectors.toList());
- if (al.isEmpty())
- al = rstream(m.getReturnType().unwrap(Value.class,
Optional.class).getAnnotations()).filter(REMOTE_OP_GROUP).map(ai ->
(AnnotationInfo<?>)ai).collect(Collectors.toList());
+ var ap = AnnotationProvider.INSTANCE;
+ var al = ap.findTopDown(m).filter(REMOTE_OP_GROUP).toList();
RemoteReturn rv = null;
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
index bdce59ca4d..90cef99500 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
@@ -201,11 +201,12 @@ public class RestContext extends Context {
private static <T extends Annotation> MethodList
getAnnotatedMethods(Supplier<?> resource, Class<T> annotation, Predicate<T>
predicate) {
Map<String,Method> x = map();
var r = resource.get();
+ AnnotationProvider ap = AnnotationProvider.INSTANCE;
// @formatter:off
ClassInfo.ofProxy(r).getAllMethodsTopDown().stream()
.filter(y -> y.hasAnnotation(annotation))
- .forEach(y ->
rstream(y.getAllAnnotations()).map(ai ->
ai.cast(annotation)).filter(Objects::nonNull).map(AnnotationInfo::inner)
+ .forEach(y -> ap.findTopDown(y).map(ai ->
ai.cast(annotation)).filter(Objects::nonNull).map(AnnotationInfo::inner)
.filter(z -> predicate == null ||
predicate.test(z))
.forEach(z -> x.put(y.getSignature(),
y.accessible().inner())));
// @formatter:on
@@ -4566,6 +4567,7 @@ public class RestContext extends Context {
// Default value.
Value<RestOperations.Builder> v =
Value.of(RestOperations.create(beanStore));
+ var ap =
restContext.getBeanContext().getAnnotationProvider();
var rci = ClassInfo.of(resource.get());
@@ -4581,7 +4583,7 @@ public class RestContext extends Context {
// @formatter:on
for (var mi : rci.getPublicMethods()) {
- List<AnnotationInfo<?>> al =
rstream(mi.getAllAnnotations()).filter(REST_OP_GROUP).map(ai ->
(AnnotationInfo<?>)ai).collect(Collectors.toList());
+ List<AnnotationInfo<?>> al =
ap.findTopDown(mi).filter(REST_OP_GROUP).map(ai ->
(AnnotationInfo<?>)ai).collect(Collectors.toList());
// Also include methods on @Rest-annotated
interfaces.
if (al.isEmpty()) {
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOpContext.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOpContext.java
index 65e8d96592..13ac3d41b5 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOpContext.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOpContext.java
@@ -17,6 +17,7 @@
package org.apache.juneau.rest;
import static org.apache.juneau.collections.JsonMap.*;
+import static org.apache.juneau.common.reflect.AnnotationTraversal.*;
import static org.apache.juneau.common.utils.CollectionUtils.*;
import static org.apache.juneau.common.utils.StringUtils.*;
import static org.apache.juneau.common.utils.StringUtils.compare;
@@ -126,6 +127,7 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
this.restMethod = method;
this.beanStore = BeanStore.of(context.getBeanStore(),
context.builder.resource().get()).addBean(java.lang.reflect.Method.class,
method);
+ var ap =
context.getBeanContext().getAnnotationProvider();
var mi = MethodInfo.of(context.getResourceClass(),
method);
@@ -133,7 +135,7 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
var vr = context.getVarResolver();
var vrs = vr.createSession();
- var work = AnnotationWorkList.of(vrs,
rstream(mi.getAllAnnotations()).filter(CONTEXT_APPLY_FILTER).map(ai ->
(AnnotationInfo<?>)ai));
+ var work = AnnotationWorkList.of(vrs,
ap.findTopDown(mi, SELF, MATCHING_METHODS, DECLARING_CLASS, RETURN_TYPE,
PACKAGE).filter(CONTEXT_APPLY_FILTER).map(ai -> (AnnotationInfo<?>)ai));
apply(work);
@@ -1921,37 +1923,37 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
*
* @return The path matchers for this method.
*/
- protected UrlPathMatcherList getPathMatchers() {
+ protected UrlPathMatcherList getPathMatchers() {
- var v = Value.of(UrlPathMatcherList.create());
+ var v = Value.of(UrlPathMatcherList.create());
- if (nn(path)) {
- for (var p : path) {
- if (dotAll && ! p.endsWith("/*"))
- p += "/*";
- v.get().add(UrlPathMatcher.of(p));
+ if (nn(path)) {
+ for (var p : path) {
+ if (dotAll && !
p.endsWith("/*"))
+ p += "/*";
+
v.get().add(UrlPathMatcher.of(p));
+ }
}
- }
- if (v.get().isEmpty()) {
- var mi = MethodInfo.of(restMethod);
- String p = null;
- String httpMethod = null;
- if (mi.hasAnnotation(RestGet.class))
- httpMethod = "get";
- else if (mi.hasAnnotation(RestPut.class))
- httpMethod = "put";
- else if (mi.hasAnnotation(RestPost.class))
- httpMethod = "post";
- else if (mi.hasAnnotation(RestDelete.class))
- httpMethod = "delete";
- else if (mi.hasAnnotation(RestOp.class)) {
- var _httpMethod = Value.<String>empty();
- rstream(mi.getAllAnnotations()).map(x ->
x.cast(RestOp.class)).filter(Objects::nonNull).map(AnnotationInfo::inner)
- .filter(x -> isNotEmpty(x.method()))
- .forEach(x ->
_httpMethod.set(x.method()));
- httpMethod = _httpMethod.orElse(null);
- }
+ if (v.get().isEmpty()) {
+ var mi = MethodInfo.of(restMethod);
+ String p = null;
+ String httpMethod = null;
+ if (mi.hasAnnotation(RestGet.class))
+ httpMethod = "get";
+ else if (mi.hasAnnotation(RestPut.class))
+ httpMethod = "put";
+ else if (mi.hasAnnotation(RestPost.class))
+ httpMethod = "post";
+ else if (mi.hasAnnotation(RestDelete.class))
+ httpMethod = "delete";
+ else if (mi.hasAnnotation(RestOp.class)) {
+ httpMethod =
AnnotationProvider.INSTANCE.find(RestOp.class, mi)
+ .map(x -> x.inner().method())
+ .filter(x -> isNotEmpty(x))
+ .findFirst()
+ .orElse(null);
+ }
p = HttpUtils.detectHttpPath(restMethod,
httpMethod);
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/BasicDebugEnablement.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/BasicDebugEnablement.java
index eac63cc40e..5e9e5b8fc3 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/BasicDebugEnablement.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/BasicDebugEnablement.java
@@ -64,6 +64,7 @@ public class BasicDebugEnablement extends DebugEnablement {
RestContext.Builder builder =
beanStore.getBean(RestContext.Builder.class).get();
ResourceSupplier resource =
beanStore.getBean(ResourceSupplier.class).get();
VarResolver varResolver =
beanStore.getBean(VarResolver.class).get();
+ var ap = AnnotationProvider.INSTANCE;
// Default debug enablement if not overridden at class/method
level.
Enablement debugDefault = defaultSettings.get(Enablement.class,
"RestContext.debugDefault").orElse(builder.isDebug() ? Enablement.ALWAYS :
Enablement.NEVER);
@@ -83,7 +84,7 @@ public class BasicDebugEnablement extends DebugEnablement {
// Gather @RestOp(debug) settings.
// @formatter:off
ci.getPublicMethods().stream()
- .forEach(x ->
+ .forEach(x ->
rstream(x.getAllAnnotations())
.filter(REST_OP_GROUP)
.flatMap(ai ->
ai.getValue(String.class, "debug").stream())