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 1d08c90aee RestOpContext: inline all findXxx() bodies into memoizer
lambdas, rename Memo fields, add Javadoc
1d08c90aee is described below
commit 1d08c90aee994b025f6a09034c81c46b61cb5de3
Author: James Bognar <[email protected]>
AuthorDate: Mon May 4 10:32:45 2026 -0400
RestOpContext: inline all findXxx() bodies into memoizer lambdas, rename
Memo fields, add Javadoc
---
.../java/org/apache/juneau/rest/RestOpContext.java | 686 ++++++++++-----------
todo/TODO.md | 2 -
2 files changed, 311 insertions(+), 377 deletions(-)
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 7ac687f5b2..d7f806147c 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
@@ -82,8 +82,7 @@ import jakarta.servlet.http.*;
* </ul>
*/
@SuppressWarnings({
- "java:S115", // Constants use UPPER_snakeCase convention (e.g.,
PROP_defaultRequestFormData)
- "java:S6539" // Legacy central operation context; decomposition is
tracked separately from current TODO scope.
+ "java:S115" // Constants use UPPER_snakeCase convention (e.g.,
PROP_defaultRequestFormData)
})
public class RestOpContext extends Context implements
Comparable<RestOpContext> {
@@ -111,13 +110,6 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
/**
* Internal construction-time state holder.
- *
- * <p>
- * Package-private since 9.5 (TODO-16 Phase D-3). Was previously {@code
public static class Builder}; the external
- * Builder surface is now deleted — {@code RestOpContext} is
instantiated via the positional
- * {@link #RestOpContext(java.lang.reflect.Method, RestContext) 2-arg
ctor} or the protected 3-arg ctor used by
- * subclasses needing a custom bean store. This class is an internal
implementation detail that may be inlined or
- * removed entirely in future phases without notice.
*/
static class Builder extends Context.Builder {
@@ -323,7 +315,7 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
protected final ResponseBeanMeta responseMeta;
// Sub-builder fields: annotation-applied copies captured from Builder
before it is discarded.
- // Non-null only when op-level annotations actually touched the
sub-builder during construction;
+ // Non-null only when op-level annotations actually touched the
sub-builder during construction.
// null means "no op annotations applied" and the memoizer falls
through to the parent context value.
private final BeanContext.Builder beanContextBuilder;
private final EncoderSet.Builder encodersBuilder;
@@ -336,250 +328,317 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
// The annotation work-list produced during construction (replaces
builder.getApplied() references).
private final AnnotationWorkList appliedAnnotations;
- // Phase 4: formerly eager scalars — now lazily resolved on first
access.
- private final Memoizer<Charset> defaultCharsetMemo =
memoizer(this::findDefaultCharset);
- private final Memoizer<Long> maxInputMemo =
memoizer(this::findMaxInput);
- private final Memoizer<DebugEnablement> debugEnablementMemo =
memoizer(this::findDebugEnablement);
- private final Memoizer<List<MediaType>> supportedAcceptTypesMemo =
memoizer(this::findSupportedAcceptTypes);
- private final Memoizer<List<MediaType>> supportedContentTypesMemo =
memoizer(this::findSupportedContentTypes);
- private final Memoizer<String> httpMethodMemo =
memoizer(this::findHttpMethod);
+ /** The effective default {@link Charset} for this operation, resolved
from op annotations, context, or env. */
+ private final Memoizer<Charset> defaultCharset = memoizer(() -> {
+ var v = findOpString(PROPERTY_defaultCharset);
+ if (v.isPresent())
+ return Charset.forName(v.get());
+ if (isInherited(PROPERTY_defaultCharset)) {
+ var rv =
restContext().mergeReplacedStringAttribute(PROPERTY_defaultCharset, null);
+ if (rv != null && !rv.isEmpty())
+ return Charset.forName(rv);
+ }
+ return envDefaultRestCharset();
+ });
+
+ /** The effective max-input byte limit for this operation. */
+ private final Memoizer<Long> maxInput = memoizer(() -> {
+ var v = findOpString(PROPERTY_maxInput);
+ if (v.isPresent())
+ return parseLongWithSuffix(v.get());
+ if (isInherited(PROPERTY_maxInput)) {
+ var rv =
restContext().mergeReplacedStringAttribute(PROPERTY_maxInput, null);
+ if (rv != null && !rv.isEmpty())
+ return parseLongWithSuffix(rv);
+ }
+ return envDefaultRestMaxInput();
+ });
+
+ /** The effective {@link DebugEnablement} for this operation. */
+ private final Memoizer<DebugEnablement> debugEnablement = memoizer(()
-> {
+ var v = findOpString(PROPERTY_debug);
+ if (v.isPresent())
+ return
DebugEnablement.create(restContext().getBeanStore()).enable(Enablement.fromString(v.get()),
"*").build();
+ if (isInherited(PROPERTY_debug))
+ return restContext().getDebugEnablement();
+ return
DebugEnablement.create(restContext().getBeanStore()).build();
+ });
+
+ /**
+ * The supported response accept types for this operation.
+ *
+ * <p>
+ * Walks op-level {@code @RestOp}-group annotations for {@code
produces} (and, when
+ * {@code noInherit} does not block it, the class-level {@code
@Rest(produces)} hierarchy).
+ * Falls back to the supported media types of the operation's {@link
SerializerSet}.
+ */
+ private final Memoizer<List<MediaType>> supportedAcceptTypes =
memoizer(() -> {
+ var result = collectAnnotationMediaTypes(PROPERTY_produces);
+ if (result.isEmpty())
+ return u(getSerializers().getSupportedMediaTypes());
+ return u(result);
+ });
+
+ /**
+ * The supported request content types for this operation.
+ *
+ * <p>
+ * Walks op-level {@code @RestOp}-group annotations for {@code
consumes} (and, when
+ * {@code noInherit} does not block it, the class-level {@code
@Rest(consumes)} hierarchy).
+ * Falls back to the supported content types of the operation's {@link
ParserSet}.
+ */
+ private final Memoizer<List<MediaType>> supportedContentTypes =
memoizer(() -> {
+ var result = collectAnnotationMediaTypes(PROPERTY_consumes);
+ if (result.isEmpty())
+ return u(getParsers().getSupportedMediaTypes());
+ return u(result);
+ });
+
+ /**
+ * The HTTP method string for this operation (e.g. {@code "GET"},
{@code "*"}).
+ *
+ * <p>
+ * Walks {@code @RestOp}-group annotations child-to-parent;
verb-specific annotations
+ * ({@link RestGet}, etc.) imply their fixed verb. Falls back to
+ * {@link HttpUtils#detectHttpMethod} when no annotation declares a
verb.
+ */
+ @SuppressWarnings("java:S3776")
+ private final Memoizer<String> httpMethod = memoizer(() -> {
+ var vr = restContext().getVarResolver();
+ for (var ai : getRestOpAnnotations()) {
+ var v = httpMethodFromAnnotation(ai.inner(), vr);
+ if (v != null && !v.isEmpty())
+ return normalizeHttpMethod(v);
+ }
+ return normalizeHttpMethod(HttpUtils.detectHttpMethod(method(),
true, "GET"));
+ });
+
+ private RestContext restContext() { return context; }
+ private Method method() { return method; }
+ private BeanContext.Builder beanContextBuilder() { return
beanContextBuilder; }
+ private EncoderSet.Builder encodersBuilder() { return encodersBuilder; }
+ private JsonSchemaGenerator.Builder jsonSchemaGeneratorBuilder() {
return jsonSchemaGeneratorBuilder; }
+ private ParserSet.Builder parsersBuilder() { return parsersBuilder; }
+ private HttpPartParser.Creator partParserCreator() { return
partParserCreator; }
+ private HttpPartSerializer.Creator partSerializerCreator() { return
partSerializerCreator; }
+ private SerializerSet.Builder serializersBuilder() { return
serializersBuilder; }
//-----------------------------------------------------------------------------------------------------------------
- // Memoized allowlist fields
+ // Memoized fields
//-----------------------------------------------------------------------------------------------------------------
- /** Memoized all {@link RestOp}-group annotations on this method,
child-to-parent order. */
- private final Memoizer<List<AnnotationInfo<?>>> restOpAnnotations =
memoizer(this::findRestOpAnnotations);
-
- private List<AnnotationInfo<?>> findRestOpAnnotations() {
- // Use the same MethodInfo as {@link Builder}'s annotation
traversal (resource class + method), not
- // {@link MethodInfo#of(Method)} alone, so method-level {@code
@RestGet}/@RestOp metadata (e.g. noInherit)
- // resolves consistently when the implementation class differs
from the method's declaring class.
- var methodInfo = MethodInfo.of(context.getResourceClass(),
method).accessible();
- return context.getAnnotationProvider().find(methodInfo, SELF,
MATCHING_METHODS).stream()
+ /**
+ * All {@link RestOp}-group annotations on this method, in
child-to-parent order.
+ *
+ * <p>
+ * Uses the same {@link MethodInfo} as the annotation traversal in the
{@link Builder}, so
+ * method-level metadata (e.g. {@code noInherit}) resolves consistently
when the implementation
+ * class differs from the method's declaring class.
+ */
+ private final Memoizer<List<AnnotationInfo<?>>> restOpAnnotations =
memoizer(() -> {
+ var methodInfo =
MethodInfo.of(restContext().getResourceClass(), method()).accessible();
+ return restContext().getAnnotationProvider().find(methodInfo,
SELF, MATCHING_METHODS).stream()
.filter(ai -> ai.isInGroup(RestOp.class))
.toList();
- }
-
- /** Memoized aggregated {@code noInherit} keys from all RestOp-group
annotations on this operation. */
- private final Memoizer<SortedSet<String>> noInheritOp =
memoizer(this::findNoInheritOp);
+ });
- private SortedSet<String> findNoInheritOp() {
+ /** Aggregated {@code noInherit} keys from all RestOp-group annotations
on this operation. */
+ private final Memoizer<SortedSet<String>> noInheritOp = memoizer(() -> {
var l = getRestOpAnnotations().stream()
.map(ai ->
ai.getStringArray("noInherit").orElse(StringUtils.EMPTY_STRING_ARRAY))
.flatMap(this::resolveCdl)
.toList();
return
Collections.unmodifiableSortedSet(treeSet(String.CASE_INSENSITIVE_ORDER, l));
- }
+ });
- /** Memoized effective allowed parser option keys for this operation. */
- private final Memoizer<SortedSet<String>> allowedParserOptions =
memoizer(this::findAllowedParserOptions);
-
- private SortedSet<String> findAllowedParserOptions() {
+ /** Effective allowed parser session-option keys for this operation. */
+ private final Memoizer<SortedSet<String>> allowedParserOptions =
memoizer(() -> {
var l = new ArrayList<String>();
var p = PROPERTY_allowedParserOptions;
if (isInherited(p))
- l.addAll(context.getAllowedParserOptions());
+ l.addAll(restContext().getAllowedParserOptions());
getRestOpAnnotations().stream()
.flatMap(ai ->
resolveCdl(ai.getStringArray(p).orElse(new String[0])))
.forEach(l::add);
return
Collections.unmodifiableSortedSet(treeSet(String.CASE_INSENSITIVE_ORDER,
removeNegations(l)));
- }
-
- /** Memoized effective allowed serializer option keys for this
operation. */
- private final Memoizer<SortedSet<String>> allowedSerializerOptions =
memoizer(this::findAllowedSerializerOptions);
+ });
- private SortedSet<String> findAllowedSerializerOptions() {
+ /** Effective allowed serializer session-option keys for this
operation. */
+ private final Memoizer<SortedSet<String>> allowedSerializerOptions =
memoizer(() -> {
var l = new ArrayList<String>();
var p = PROPERTY_allowedSerializerOptions;
if (isInherited(p))
- l.addAll(context.getAllowedSerializerOptions());
+ l.addAll(restContext().getAllowedSerializerOptions());
getRestOpAnnotations().stream()
.flatMap(ai ->
resolveCdl(ai.getStringArray(p).orElse(new String[0])))
.forEach(l::add);
return
Collections.unmodifiableSortedSet(treeSet(String.CASE_INSENSITIVE_ORDER,
removeNegations(l)));
- }
-
- private final Memoizer<BeanContext> beanContextMemo =
memoizer(this::findBeanContext);
+ });
- private BeanContext findBeanContext() {
- return beanContextBuilder != null ? beanContextBuilder.build()
: context.getBeanContext();
- }
+ /** The {@link BeanContext} for this operation (op-level annotations
applied on top of the parent context). */
+ private final Memoizer<BeanContext> beanContext = memoizer(() ->
+ beanContextBuilder() != null ? beanContextBuilder().build() :
restContext().getBeanContext()
+ );
- private final Memoizer<CallLogger> callLoggerMemo =
memoizer(this::findCallLogger);
-
- private CallLogger findCallLogger() {
- return context.getCallLogger();
- }
-
- private final Memoizer<EncoderSet> encodersMemo =
memoizer(this::findEncoders);
+ /** The call logger for this operation (delegated to the parent {@link
RestContext}). */
+ private final Memoizer<CallLogger> callLogger = memoizer(() ->
restContext().getCallLogger());
/**
- * Resolves the encoder group for this operation. Walks the {@code
@RestOp(encoders)} chain
- * (parent-to-child); each non-empty {@code encoders()} array REPLACES
the inherited set
- * (with {@link Inherit} as a sentinel that re-injects the prior set's
entries at the
- * specified position — matches the legacy {@code
EncoderSet.Builder.set(...)} semantics).
+ * The encoder group for this operation.
*
* <p>
- * If no op annotation declares encoders, falls through to the
class-level
- * {@link RestContext#getEncoders()}. An {@code @RestInject EncoderSet}
bean (matching this
- * operation's method scope) REPLACES the result entirely.
+ * Walks the {@code @RestOp(encoders)} chain (parent-to-child); each
non-empty {@code encoders()}
+ * array REPLACES the inherited set (with {@link Inherit} as a sentinel
that re-injects the prior
+ * set's entries at the specified position — matches the legacy {@code
EncoderSet.Builder.set(...)}
+ * semantics). Falls through to the class-level {@link
RestContext#getEncoders()} when no op
+ * annotation declares encoders. An {@code @RestInject EncoderSet} bean
(matching this operation's
+ * method scope) REPLACES the result entirely.
*/
- private EncoderSet findEncoders() {
- var bs = context.getBeanStore();
- // Seed from class-level builder copy so
EncoderSet.Builder.set(...)'s Inherit sentinel
- // can splice in the inherited entries. If no op annotations
touched encoders, fall through
- // to a fresh copy of the parent builder (mirrors the former
Builder.encoders() lazy-init).
- var b = (encodersBuilder != null ? encodersBuilder :
context.builder.encoders()).copy();
+ private final Memoizer<EncoderSet> encoders = memoizer(() -> {
+ var bs = restContext().getBeanStore();
+ var b = (encodersBuilder() != null ? encodersBuilder() :
restContext().builder.encoders()).copy();
getRestOpAnnotationsForProperty(PROPERTY_encoders).forEach(ai
-> {
var c = ai.getClassArray("encoders",
org.apache.juneau.encoders.Encoder.class).orElse(null);
if (nn(c) && c.length > 0)
b.set(c);
});
var v = Value.of(b.build());
- new BeanCreateMethodFinder<>(EncoderSet.class,
context.getResource(), bs)
+ new BeanCreateMethodFinder<>(EncoderSet.class,
restContext().getResource(), bs)
.find(this::matchesInjectScope)
.run(v::set);
return v.get();
- }
-
- private final Memoizer<JsonSchemaGenerator> jsonSchemaGeneratorMemo =
memoizer(this::findJsonSchemaGenerator);
+ });
- private JsonSchemaGenerator findJsonSchemaGenerator() {
- return jsonSchemaGeneratorBuilder != null ?
jsonSchemaGeneratorBuilder.build() : context.getJsonSchemaGenerator();
- }
-
- private final Memoizer<ParserSet> parsersMemo =
memoizer(this::findParsers);
+ /** The JSON-Schema generator for this operation (op-level annotations
applied on top of the parent). */
+ private final Memoizer<JsonSchemaGenerator> jsonSchemaGenerator =
memoizer(() ->
+ jsonSchemaGeneratorBuilder() != null ?
jsonSchemaGeneratorBuilder().build() : restContext().getJsonSchemaGenerator()
+ );
/**
- * Resolves the parser group for this operation. Walks the {@code
@RestOp(parsers)} chain
- * (parent-to-child); the most-derived non-empty {@code parsers()}
array REPLACES the entire
- * inherited set. If no op annotation declares parsers, falls through
to the class-level
- * {@link RestContext#getParsers()}. An {@code @RestInject ParserSet}
bean (matching this
- * operation's method scope) REPLACES the result.
+ * The parser group for this operation.
+ *
+ * <p>
+ * Walks the {@code @RestOp(parsers)} chain (parent-to-child); the
most-derived non-empty
+ * {@code parsers()} array REPLACES the entire inherited set. Falls
through to the class-level
+ * {@link RestContext#getParsers()} when no op annotation declares
parsers. An
+ * {@code @RestInject ParserSet} bean (matching this operation's method
scope) REPLACES the result.
*/
- private ParserSet findParsers() {
- var bs = context.getBeanStore();
- var b = (parsersBuilder != null ? parsersBuilder :
context.builder.parsers()).copy();
+ private final Memoizer<ParserSet> parsers = memoizer(() -> {
+ var bs = restContext().getBeanStore();
+ var b = (parsersBuilder() != null ? parsersBuilder() :
restContext().builder.parsers()).copy();
getRestOpAnnotationsForProperty(PROPERTY_parsers).forEach(ai ->
{
var c = ai.getClassArray("parsers",
java.lang.Object.class).orElse(null);
if (nn(c) && c.length > 0)
b.set(c);
});
var result = Value.of(b.build());
- new BeanCreateMethodFinder<>(ParserSet.class,
context.getResource(), bs)
+ new BeanCreateMethodFinder<>(ParserSet.class,
restContext().getResource(), bs)
.find(this::matchesInjectScope)
.run(result::set);
return result.get();
- }
-
- private final Memoizer<HttpPartParser> partParserMemo =
memoizer(this::findPartParser);
-
- private HttpPartParser findPartParser() {
- return partParserCreator != null ? partParserCreator.create() :
context.getPartParser();
- }
+ });
- private final Memoizer<HttpPartSerializer> partSerializerMemo =
memoizer(this::findPartSerializer);
+ /** The HTTP part parser for this operation (op-level creator applied
on top of the parent). */
+ private final Memoizer<HttpPartParser> partParser = memoizer(() ->
+ partParserCreator() != null ? partParserCreator().create() :
restContext().getPartParser()
+ );
- private HttpPartSerializer findPartSerializer() {
- return partSerializerCreator != null ?
partSerializerCreator.create() : context.getPartSerializer();
- }
-
- private final Memoizer<SerializerSet> serializersMemo =
memoizer(this::findSerializers);
+ /** The HTTP part serializer for this operation (op-level creator
applied on top of the parent). */
+ private final Memoizer<HttpPartSerializer> partSerializer = memoizer(()
->
+ partSerializerCreator() != null ?
partSerializerCreator().create() : restContext().getPartSerializer()
+ );
/**
- * Resolves the serializer group for this operation. Walks the {@code
@RestOp(serializers)} chain
- * (parent-to-child); the most-derived non-empty {@code serializers()}
array REPLACES the entire
- * inherited set. If no op annotation declares serializers, falls
through to the class-level
- * {@link RestContext#getSerializers()}. An {@code @RestInject
SerializerSet} bean (matching this
- * operation's method scope) REPLACES the result.
+ * The serializer group for this operation.
+ *
+ * <p>
+ * Walks the {@code @RestOp(serializers)} chain (parent-to-child); the
most-derived non-empty
+ * {@code serializers()} array REPLACES the entire inherited set. Falls
through to the class-level
+ * {@link RestContext#getSerializers()} when no op annotation declares
serializers. An
+ * {@code @RestInject SerializerSet} bean (matching this operation's
method scope) REPLACES the result.
*/
- private SerializerSet findSerializers() {
- var bs = context.getBeanStore();
- var b = (serializersBuilder != null ? serializersBuilder :
context.builder.serializers()).copy();
+ private final Memoizer<SerializerSet> serializers = memoizer(() -> {
+ var bs = restContext().getBeanStore();
+ var b = (serializersBuilder() != null ? serializersBuilder() :
restContext().builder.serializers()).copy();
getRestOpAnnotationsForProperty(PROPERTY_serializers).forEach(ai -> {
var c = ai.getClassArray("serializers",
org.apache.juneau.serializer.Serializer.class).orElse(null);
if (nn(c) && c.length > 0)
b.set(c);
});
var result = Value.of(b.build());
- new BeanCreateMethodFinder<>(SerializerSet.class,
context.getResource(), bs)
+ new BeanCreateMethodFinder<>(SerializerSet.class,
restContext().getResource(), bs)
.find(this::matchesInjectScope)
.run(result::set);
return result.get();
- }
-
- private final Memoizer<NamedAttributeMap> defaultRequestAttributesMemo
= memoizer(this::findDefaultRequestAttributes);
+ });
/**
- * Resolves the default request attribute map for this operation.
Starts with the class-level
- * value (which already incorporates {@code
@Rest(defaultRequestAttributes)}), then walks the
- * {@code @RestOp}/verb chain (parent-to-child) and adds each entry —
{@link NamedAttributeMap#add}
- * uses put-semantics so child entries override parent entries by name.
+ * The default request attribute map for this operation.
*
* <p>
- * An {@code @RestInject(name="defaultRequestAttributes")
NamedAttributeMap} bean (matching this
+ * Starts with the class-level value (which already incorporates
+ * {@code @Rest(defaultRequestAttributes)}), then walks the {@code
@RestOp}/verb chain
+ * (parent-to-child) and adds each entry — {@link
NamedAttributeMap#add} uses put-semantics so
+ * child entries override parent entries by name. An
+ * {@code @RestInject(name="defaultRequestAttributes")
NamedAttributeMap} bean (matching this
* operation's method scope) REPLACES the entire result.
*/
- private NamedAttributeMap findDefaultRequestAttributes() {
- var v = Value.of(context.getDefaultRequestAttributes().copy());
+ private final Memoizer<NamedAttributeMap> defaultRequestAttributes =
memoizer(() -> {
+ var v =
Value.of(restContext().getDefaultRequestAttributes().copy());
getRestOpAnnotationsForProperty(PROPERTY_defaultRequestAttributes).forEach(ai
-> {
for (var s :
ai.getStringArray(PROPERTY_defaultRequestAttributes).orElse(EMPTY_STRING_ARRAY))
v.get().add(BasicNamedAttribute.ofPair(s));
});
- new BeanCreateMethodFinder<>(NamedAttributeMap.class,
context.getResource(), context.getBeanStore())
+ new BeanCreateMethodFinder<>(NamedAttributeMap.class,
restContext().getResource(), restContext().getBeanStore())
.find(x -> matchesInjectScope(x,
PROPERTY_defaultRequestAttributes))
.run(v::set);
return v.get();
- }
-
- private final Memoizer<PartList> defaultRequestFormDataMemo =
memoizer(this::findDefaultRequestFormData);
+ });
/**
- * Resolves the default request form-data parts for this operation.
Walks the {@code @RestOp}/verb
- * chain (parent-to-child); each {@code defaultRequestFormData} entry
is applied with
- * {@link PartList#setDefault} (first-in-chain wins per name).
Method-parameter
- * {@link FormData @FormData} annotations with a {@link
Schema#default_()}/{@link Schema#df()}
- * default are folded in last (also {@code setDefault} = first wins).
+ * The default request form-data parts for this operation.
*
* <p>
- * An {@code @RestInject(name="defaultRequestFormData") PartList} bean
(matching this operation's
- * method scope) REPLACES the entire result.
+ * Walks the {@code @RestOp}/verb chain (parent-to-child); each {@code
defaultRequestFormData}
+ * entry is applied with {@link PartList#setDefault} (first-in-chain
wins per name).
+ * Method-parameter {@link FormData @FormData} annotations with a
+ * {@link Schema#default_()}/{@link Schema#df()} default are folded in
last (also
+ * {@code setDefault} = first wins). An {@code
@RestInject(name="defaultRequestFormData") PartList}
+ * bean (matching this operation's method scope) REPLACES the entire
result.
*/
- private PartList findDefaultRequestFormData() {
+ private final Memoizer<PartList> defaultRequestFormData = memoizer(()
-> {
var v = Value.of(PartList.create());
getRestOpAnnotationsForProperty(PROPERTY_defaultRequestFormData).forEach(ai -> {
for (var s :
ai.getStringArray(PROPERTY_defaultRequestFormData).orElse(EMPTY_STRING_ARRAY))
v.get().setDefault(basicPart(s));
});
applyParameterFormData(v.get());
- new BeanCreateMethodFinder<>(PartList.class,
context.getResource(), context.getBeanStore())
+ new BeanCreateMethodFinder<>(PartList.class,
restContext().getResource(), restContext().getBeanStore())
.find(x -> matchesInjectScope(x,
PROPERTY_defaultRequestFormData))
.run(v::set);
return v.get();
- }
-
- private final Memoizer<HeaderList> defaultRequestHeadersMemo =
memoizer(this::findDefaultRequestHeaders);
+ });
/**
- * Resolves the default request headers for this operation. Starts with
the class-level value
- * (which already incorporates {@code
@Rest(defaultRequestHeaders|defaultAccept|defaultContentType)}),
- * then walks the {@code @RestOp}/verb chain (parent-to-child); each
annotation's
- * {@code defaultRequestHeaders} entries plus its {@code defaultAccept}
/ {@code defaultContentType}
- * (folded into {@code Accept} / {@code Content-Type} headers) are
applied with
- * {@link HeaderList#setDefault} (first-in-chain wins per name —
class-level beats op-level beats
- * later op-level entries).
+ * The default request headers for this operation.
*
* <p>
- * Method-parameter {@link Header @Header} annotations with a {@link
Schema#default_()}/{@link Schema#df()}
- * default are folded in last via {@link HeaderList#set} (overrides any
prior entry with the same name).
+ * Starts with the class-level value (which already incorporates
+ * {@code
@Rest(defaultRequestHeaders|defaultAccept|defaultContentType)}), then walks the
+ * {@code @RestOp}/verb chain (parent-to-child); each annotation's
{@code defaultRequestHeaders}
+ * entries plus its {@code defaultAccept} / {@code defaultContentType}
(folded into
+ * {@code Accept} / {@code Content-Type} headers) are applied with
{@link HeaderList#setDefault}
+ * (first-in-chain wins per name — class-level beats op-level beats
later op-level entries).
*
* <p>
- * An {@code @RestInject(name="defaultRequestHeaders") HeaderList} bean
(matching this operation's
+ * Method-parameter {@link Header @Header} annotations with a
+ * {@link Schema#default_()}/{@link Schema#df()} default are folded in
last via
+ * {@link HeaderList#set} (overrides any prior entry with the same
name). An
+ * {@code @RestInject(name="defaultRequestHeaders") HeaderList} bean
(matching this operation's
* method scope) REPLACES the entire result.
*/
- private HeaderList findDefaultRequestHeaders() {
- var v = Value.of(context.getDefaultRequestHeaders().copy());
+ private final Memoizer<HeaderList> defaultRequestHeaders = memoizer(()
-> {
+ var v =
Value.of(restContext().getDefaultRequestHeaders().copy());
getRestOpAnnotationsForProperty(PROPERTY_defaultRequestHeaders).forEach(ai -> {
for (var s :
ai.getStringArray(PROPERTY_defaultRequestHeaders).orElse(EMPTY_STRING_ARRAY))
v.get().setDefault(stringHeader(s));
@@ -587,66 +646,63 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
ai.getString(PROPERTY_defaultContentType).filter(s ->
!s.isEmpty()).ifPresent(s -> v.get().setDefault(contentType(s)));
});
applyParameterHeaders(v.get());
- new BeanCreateMethodFinder<>(HeaderList.class,
context.getResource(), context.getBeanStore())
+ new BeanCreateMethodFinder<>(HeaderList.class,
restContext().getResource(), restContext().getBeanStore())
.find(x -> matchesInjectScope(x,
PROPERTY_defaultRequestHeaders))
.run(v::set);
return v.get();
- }
-
- private final Memoizer<PartList> defaultRequestQueryDataMemo =
memoizer(this::findDefaultRequestQueryData);
+ });
/**
- * Resolves the default request query-data parts for this operation.
Walks the {@code @RestOp}/verb
- * chain (parent-to-child); each {@code defaultRequestQueryData} entry
is applied with
- * {@link PartList#setDefault} (first-in-chain wins per name).
Method-parameter
- * {@link Query @Query} annotations with a {@link
Schema#default_()}/{@link Schema#df()} default
- * are folded in last (also {@code setDefault} = first wins).
+ * The default request query-data parts for this operation.
*
* <p>
- * An {@code @RestInject(name="defaultRequestQueryData") PartList} bean
(matching this operation's
- * method scope) REPLACES the entire result.
+ * Walks the {@code @RestOp}/verb chain (parent-to-child); each {@code
defaultRequestQueryData}
+ * entry is applied with {@link PartList#setDefault} (first-in-chain
wins per name).
+ * Method-parameter {@link Query @Query} annotations with a
+ * {@link Schema#default_()}/{@link Schema#df()} default are folded in
last (also
+ * {@code setDefault} = first wins). An {@code
@RestInject(name="defaultRequestQueryData") PartList}
+ * bean (matching this operation's method scope) REPLACES the entire
result.
*/
- private PartList findDefaultRequestQueryData() {
+ private final Memoizer<PartList> defaultRequestQueryData = memoizer(()
-> {
var v = Value.of(PartList.create());
getRestOpAnnotationsForProperty(PROPERTY_defaultRequestQueryData).forEach(ai ->
{
for (var s :
ai.getStringArray(PROPERTY_defaultRequestQueryData).orElse(EMPTY_STRING_ARRAY))
v.get().setDefault(basicPart(s));
});
applyParameterQueryData(v.get());
- new BeanCreateMethodFinder<>(PartList.class,
context.getResource(), context.getBeanStore())
+ new BeanCreateMethodFinder<>(PartList.class,
restContext().getResource(), restContext().getBeanStore())
.find(x -> matchesInjectScope(x,
PROPERTY_defaultRequestQueryData))
.run(v::set);
return v.get();
- }
-
- private final Memoizer<HeaderList> defaultResponseHeadersMemo =
memoizer(this::findDefaultResponseHeaders);
+ });
/**
- * Resolves the default response headers for this operation. Starts
with the class-level value
- * (which already incorporates {@code @Rest(defaultResponseHeaders)}),
then walks the
- * {@code @RestOp}/verb chain (parent-to-child); each annotation's
{@code defaultResponseHeaders}
- * entries are applied with {@link HeaderList#setDefault}
(first-in-chain wins per name).
+ * The default response headers for this operation.
*
* <p>
- * An {@code @RestInject(name="defaultResponseHeaders") HeaderList}
bean (matching this operation's
+ * Starts with the class-level value (which already incorporates
+ * {@code @Rest(defaultResponseHeaders)}), then walks the {@code
@RestOp}/verb chain
+ * (parent-to-child); each annotation's {@code defaultResponseHeaders}
entries are applied with
+ * {@link HeaderList#setDefault} (first-in-chain wins per name). An
+ * {@code @RestInject(name="defaultResponseHeaders") HeaderList} bean
(matching this operation's
* method scope) REPLACES the entire result.
*/
- private HeaderList findDefaultResponseHeaders() {
- var v = Value.of(context.getDefaultResponseHeaders().copy());
+ private final Memoizer<HeaderList> defaultResponseHeaders = memoizer(()
-> {
+ var v =
Value.of(restContext().getDefaultResponseHeaders().copy());
getRestOpAnnotationsForProperty(PROPERTY_defaultResponseHeaders).forEach(ai -> {
for (var s :
ai.getStringArray(PROPERTY_defaultResponseHeaders).orElse(EMPTY_STRING_ARRAY))
v.get().setDefault(stringHeader(s));
});
- new BeanCreateMethodFinder<>(HeaderList.class,
context.getResource(), context.getBeanStore())
+ new BeanCreateMethodFinder<>(HeaderList.class,
restContext().getResource(), restContext().getBeanStore())
.find(x -> matchesInjectScope(x,
PROPERTY_defaultResponseHeaders))
.run(v::set);
return v.get();
- }
+ });
/**
* Folds method-parameter {@link Header @Header} annotations (with a
{@link Schema#default_()} /
* {@link Schema#df()} default) into the supplied {@link HeaderList}
using {@link HeaderList#set}
- * (overrides). Used by {@link #findDefaultRequestHeaders()}.
+ * (overrides). Used by the {@link #defaultRequestHeaders} memoizer.
*/
private void applyParameterHeaders(HeaderList list) {
processParameterDefaults((paramAnn, def) -> {
@@ -663,7 +719,7 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
/**
* Folds method-parameter {@link Query @Query} annotations (with a
{@link Schema#default_()} /
* {@link Schema#df()} default) into the supplied {@link PartList}
using {@link PartList#setDefault}
- * (first wins). Used by {@link #findDefaultRequestQueryData()}.
+ * (first wins). Used by the {@link #defaultRequestQueryData} memoizer.
*/
private void applyParameterQueryData(PartList list) {
processParameterDefaults((paramAnn, def) -> {
@@ -680,7 +736,7 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
/**
* Folds method-parameter {@link FormData @FormData} annotations (with
a {@link Schema#default_()} /
* {@link Schema#df()} default) into the supplied {@link PartList}
using {@link PartList#setDefault}
- * (first wins). Used by {@link #findDefaultRequestFormData()}.
+ * (first wins). Used by the {@link #defaultRequestFormData} memoizer.
*/
private void applyParameterFormData(PartList list) {
processParameterDefaults((paramAnn, def) -> {
@@ -726,28 +782,22 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
return (Class<? extends T>[])new Class<?>[] { value };
}
- private final Memoizer<RestConverter[]> convertersMemo =
memoizer(this::findConverters);
-
/**
- * Resolves the response-converter list for this operation by walking
the {@code @Rest(converters)}
- * class chain (parent-to-child) followed by the {@code
@RestOp(converters)} method chain
- * (parent-to-child).
- *
- * <p>
- * Op-level {@code noInherit={"converters"}} cuts off the class-chain
contribution; an op-level
- * {@code noInherit} on a parent annotation in the op chain cuts off
the rest of the op chain too
- * (mirrors the existing {@code allowed*Options} semantics).
+ * The response-converter array for this operation.
*
* <p>
- * An {@code @RestInject RestConverterList} bean (either as a
name-anonymous bean in the bean store or
- * as a {@code @RestInject} method whose {@code methodScope} matches
this operation's method name)
- * REPLACES the entire annotation-derived list — see TODO-16 Decision
#1 (Phase D-1 lock-in).
+ * Walks the {@code @Rest(converters)} class chain (parent-to-child)
followed by the
+ * {@code @RestOp(converters)} method chain (parent-to-child). Op-level
+ * {@code noInherit={"converters"}} cuts off the class-chain
contribution. An
+ * {@code @RestInject RestConverterList} bean (either as a
name-anonymous bean in the bean store
+ * or as a {@code @RestInject} method whose {@code methodScope} matches
this operation's method
+ * name) REPLACES the entire annotation-derived list — see TODO-16
Decision #1 (Phase D-1 lock-in).
*/
- private RestConverter[] findConverters() {
- var bs = context.getBeanStore();
+ private final Memoizer<RestConverter[]> converters = memoizer(() -> {
+ var bs = restContext().getBeanStore();
var v = Value.of(RestConverterList.create(bs));
if (isInherited(PROPERTY_converters))
-
context.getRestAnnotationsForProperty(PROPERTY_converters)
+
restContext().getRestAnnotationsForProperty(PROPERTY_converters)
.forEach(ai ->
v.get().append(ai.inner().converters()));
getRestOpAnnotationsForProperty(PROPERTY_converters)
.forEach(ai -> ai.getClassArray("converters",
RestConverter.class).ifPresent(classes -> {
@@ -755,11 +805,11 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
v.get().append(classArray(c));
}));
bs.getBean(RestConverterList.class).ifPresent(x ->
v.get().impl(x));
- new BeanCreateMethodFinder<>(RestConverterList.class,
context.getResource(), bs)
+ new BeanCreateMethodFinder<>(RestConverterList.class,
restContext().getResource(), bs)
.find(this::matchesInjectScope)
.run(x -> v.get().impl(x));
return v.get().build().asArray();
- }
+ });
/**
* Returns {@code true} if the given method has a {@code @RestInject}
annotation whose
@@ -804,12 +854,10 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
return false;
}
- private final Memoizer<RestGuard[]> guardsMemo =
memoizer(this::findGuards);
-
/**
- * Resolves the request-guard list for this operation. <b>Cross-bucket
memoizer</b> — folds three
- * annotation attributes ({@code guards}, {@code roleGuard}, {@code
rolesDeclared}) into a single
- * effective {@link RestGuardList}.
+ * The request-guard array for this operation. <b>Cross-bucket
memoizer</b> — folds three
+ * annotation attributes ({@code guards}, {@code roleGuard}, {@code
rolesDeclared}) into a
+ * single effective {@link RestGuardList}.
*
* <p>
* Walk order: class {@code @Rest} chain (parent-to-child, gated by
op-level
@@ -826,8 +874,8 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
* An {@code @RestInject RestGuardList} bean (via the bean store or an
{@code @RestInject} method
* with matching {@code methodScope}) REPLACES the entire
annotation-derived list (Decision #1).
*/
- private RestGuard[] findGuards() {
- var bs = context.getBeanStore();
+ private final Memoizer<RestGuard[]> guards = memoizer(() -> {
+ var bs = restContext().getBeanStore();
var v = Value.of(RestGuardList.create(bs));
var rolesDeclaredSet = new java.util.LinkedHashSet<String>();
var roleGuardStrs = new ArrayList<String>();
@@ -844,7 +892,7 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
};
if (isInherited(PROPERTY_guards))
-
context.getRestAnnotationsForProperty(PROPERTY_guards).forEach(walk::accept);
+
restContext().getRestAnnotationsForProperty(PROPERTY_guards).forEach(walk::accept);
getRestOpAnnotationsForProperty(PROPERTY_guards).forEach(walk);
// When no @Rest/@RestOp(rolesDeclared) is set, pass null so
RoleBasedRestGuard
@@ -860,31 +908,27 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
}
bs.getBean(RestGuardList.class).ifPresent(x -> v.get().impl(x));
- new BeanCreateMethodFinder<>(RestGuardList.class,
context.getResource(), bs)
+ new BeanCreateMethodFinder<>(RestGuardList.class,
restContext().getResource(), bs)
.find(this::matchesInjectScope)
.run(x -> v.get().impl(x));
return v.get().build().asArray();
- }
-
- private final Memoizer<RestMatcherList> matchersListMemo =
memoizer(this::findMatchersList);
+ });
/**
- * Resolves the request-matcher list for this operation.
<b>Cross-bucket memoizer</b> — folds
- * {@code matchers} and {@code clientVersion} (both op-level only —
neither attribute exists on
- * {@code @Rest}) into a single effective {@link RestMatcherList}.
+ * The request-matcher list for this operation. <b>Cross-bucket
memoizer</b> — folds
+ * {@code matchers} and {@code clientVersion} (both op-level only) into
a single effective
+ * {@link RestMatcherList}.
*
* <p>
- * Walks the {@code @RestOp} / verb annotation chain (parent-to-child,
gated by op-level
+ * Walks the {@code @RestOp}/verb annotation chain (parent-to-child,
gated by op-level
* {@code noInherit={"matchers"}}). Each annotation contributes its
{@code matchers()} classes
* (appended in chain order). The final non-blank {@code
clientVersion()} (most-derived wins)
* appends a single {@link ClientVersionMatcher} keyed off the
resource's client-version header.
- *
- * <p>
* An {@code @RestInject RestMatcherList} bean (via the bean store or
an {@code @RestInject}
* method with matching {@code methodScope}) REPLACES the entire
annotation-derived list.
*/
- private RestMatcherList findMatchersList() {
- var bs = context.getBeanStore();
+ private final Memoizer<RestMatcherList> matchersList = memoizer(() -> {
+ var bs = restContext().getBeanStore();
var v = Value.of(RestMatcherList.create(bs));
var clientVersion = new String[]{null};
@@ -897,49 +941,38 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
});
if (nn(clientVersion[0]))
- v.get().append(new
ClientVersionMatcher(context.getClientVersionHeader(), MethodInfo.of(method)));
+ v.get().append(new
ClientVersionMatcher(restContext().getClientVersionHeader(),
MethodInfo.of(method())));
bs.getBean(RestMatcherList.class).ifPresent(x ->
v.get().impl(x));
- new BeanCreateMethodFinder<>(RestMatcherList.class,
context.getResource(), bs)
+ new BeanCreateMethodFinder<>(RestMatcherList.class,
restContext().getResource(), bs)
.find(this::matchesInjectScope)
.run(x -> v.get().impl(x));
return v.get().build();
- }
-
- private final Memoizer<RestMatcher[]> optionalMatchersMemo =
memoizer(this::findOptionalMatchers);
+ });
- private RestMatcher[] findOptionalMatchers() {
- return matchersListMemo.get().getOptionalEntries();
- }
+ /** The optional (non-required) matchers extracted from {@link
#matchersList}. */
+ private final Memoizer<RestMatcher[]> optionalMatchers = memoizer(() ->
matchersList.get().getOptionalEntries());
- private final Memoizer<RestMatcher[]> requiredMatchersMemo =
memoizer(this::findRequiredMatchers);
-
- private RestMatcher[] findRequiredMatchers() {
- return matchersListMemo.get().getRequiredEntries();
- }
-
- private final Memoizer<UrlPathMatcher[]> pathMatchersMemo =
memoizer(this::findPathMatchers);
+ /** The required matchers extracted from {@link #matchersList}. */
+ private final Memoizer<RestMatcher[]> requiredMatchers = memoizer(() ->
matchersList.get().getRequiredEntries());
/**
- * Resolves the URL path matchers for this operation by walking the
{@code @RestOp}/verb annotation
- * chain (parent-to-child) and collecting each annotation's {@code
path[]} array plus its
- * {@code value()} (the latter being the conventional shortcut form on
- * {@link RestGet @RestGet}/{@link RestPost @RestPost}/etc., or the
{@code "METHOD path"} pair on
- * {@link RestOp @RestOp}). If no explicit paths are declared, the
operation method name (with the
- * verb prefix stripped where applicable) is auto-detected via {@link
HttpUtils#detectHttpPath}.
+ * The URL path matchers for this operation.
*
* <p>
- * Special case: for an RRPC operation with no explicit path, a
trailing {@code "/*"} is appended so
- * the matcher matches anything below the method's URL — see TODO-16
Decision #17 (the legacy
- * {@code Builder.dotAll()} flag was removed in favor of bake-in here).
- *
- * <p>
- * Op-level {@code noInherit={"path"}} cuts off any further
parent-chain contribution. A
- * {@code @RestInject UrlPathMatcherList} bean (matching this
operation's method scope) REPLACES the
- * entire result.
+ * Walks the {@code @RestOp}/verb annotation chain (parent-to-child)
and collects each
+ * annotation's {@code path[]} array plus its {@code value()} (the
conventional shortcut form on
+ * {@link RestGet @RestGet}/{@link RestPost @RestPost}/etc., or the
{@code "METHOD path"} pair on
+ * {@link RestOp @RestOp}). When no explicit paths are declared, the
operation method name (with
+ * the verb prefix stripped where applicable) is auto-detected via
{@link HttpUtils#detectHttpPath}.
+ * For RRPC operations with no explicit path, a trailing {@code "/*"}
is appended so the matcher
+ * matches anything below the method's URL — see TODO-16 Decision #17.
Op-level
+ * {@code noInherit={"path"}} cuts off any further parent-chain
contribution. A
+ * {@code @RestInject UrlPathMatcherList} bean (matching this
operation's method scope) REPLACES
+ * the entire result.
*/
@SuppressWarnings("java:S3776")
- private UrlPathMatcher[] findPathMatchers() {
+ private final Memoizer<UrlPathMatcher[]> pathMatchers = memoizer(() -> {
var v = Value.of(UrlPathMatcherList.create());
getRestOpAnnotationsForProperty(PROPERTY_path).forEach(ai -> {
for (var p :
ai.getStringArray(PROPERTY_path).orElse(StringUtils.EMPTY_STRING_ARRAY))
@@ -961,7 +994,7 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
});
if (v.get().isEmpty()) {
- var methodInfo2 = MethodInfo.of(method);
+ var methodInfo2 = MethodInfo.of(method());
String httpMethod2 = null;
if (methodInfo2.hasAnnotation(RestGet.class))
httpMethod2 = "get";
@@ -982,7 +1015,7 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
// @formatter:on
}
- var p = HttpUtils.detectHttpPath(method, httpMethod2);
+ var p = HttpUtils.detectHttpPath(method(), httpMethod2);
// RRPC operations match anything below the method's
URL when no explicit path is supplied
// (TODO-16 Decision #17 — replaces the legacy
`Builder.dotAll()` flag).
@@ -992,13 +1025,13 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
v.get().add(UrlPathMatcher.of(p));
}
- new BeanCreateMethodFinder<>(UrlPathMatcherList.class,
context.getResource(), context.getBeanStore())
+ new BeanCreateMethodFinder<>(UrlPathMatcherList.class,
restContext().getResource(), restContext().getBeanStore())
.addBean(UrlPathMatcherList.class, v.get())
.find(this::matchesInjectScope)
.run(v::set);
return v.get().asArray();
- }
+ });
private Stream<String> resolveCdl(String...values) {
if (values == null || values.length == 0)
@@ -1027,8 +1060,8 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
*
* <p>
* Mirrors {@link RestContext#getRestAnnotationsForProperty(String)}
but for the op-level chain. Used by
- * the Phase D-2 op-level memoizers ({@code findConverters}, {@code
findGuards}, {@code findMatchersList},
- * {@code findEncoders}, etc.) when accumulating values from each
{@code @RestOp} / {@code @RestGet} /
+ * op-level memoizers ({@link #converters}, {@link #guards}, {@link
#matchersList},
+ * {@link #encoders}, etc.) when accumulating values from each {@code
@RestOp} / {@code @RestGet} /
* {@code @RestPut} / {@code @RestPost} / {@code @RestDelete} / {@code
@RestPatch} / {@code @RestOptions}
* annotation in the method-override chain.
*
@@ -1085,7 +1118,7 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
* @return The resolved string, or empty if no annotation defines it.
*/
private Optional<String> findOpString(String attr) {
- var vr = context.getVarResolver();
+ var vr = restContext().getVarResolver();
for (var ai : getRestOpAnnotations()) {
var s = ai.getString(attr).orElse("");
if (!s.isEmpty()) {
@@ -1097,81 +1130,12 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
return Optional.empty();
}
- private Charset findDefaultCharset() {
- var v = findOpString(PROPERTY_defaultCharset);
- if (v.isPresent())
- return Charset.forName(v.get());
- if (isInherited(PROPERTY_defaultCharset)) {
- var rv =
context.mergeReplacedStringAttribute(PROPERTY_defaultCharset, null);
- if (rv != null && !rv.isEmpty())
- return Charset.forName(rv);
- }
- return envDefaultRestCharset();
- }
-
- private long findMaxInput() {
- var v = findOpString(PROPERTY_maxInput);
- if (v.isPresent())
- return parseLongWithSuffix(v.get());
- if (isInherited(PROPERTY_maxInput)) {
- var rv =
context.mergeReplacedStringAttribute(PROPERTY_maxInput, null);
- if (rv != null && !rv.isEmpty())
- return parseLongWithSuffix(rv);
- }
- return envDefaultRestMaxInput();
- }
-
- private DebugEnablement findDebugEnablement() {
- var v = findOpString(PROPERTY_debug);
- if (v.isPresent())
- return
DebugEnablement.create(context.getBeanStore()).enable(Enablement.fromString(v.get()),
"*").build();
- if (isInherited(PROPERTY_debug))
- return context.getDebugEnablement();
- return DebugEnablement.create(context.getBeanStore()).build();
- }
-
- /**
- * Resolves the supported request content types for this operation.
- *
- * <p>
- * Walks the op-level {@code @RestOp}-group annotations for {@code
consumes} attributes and (when
- * {@code noInherit} does not block it) the class-level {@code
@Rest(consumes)} hierarchy. Each
- * value is SVL-resolved. If no explicit values are declared, falls
back to the supported content
- * types of the operation's {@link ParserSet}.
- *
- * @return An unmodifiable list of media types, never {@code null}.
- */
- private List<MediaType> findSupportedContentTypes() {
- var result = collectAnnotationMediaTypes(PROPERTY_consumes);
- if (result.isEmpty())
- return u(getParsers().getSupportedMediaTypes());
- return u(result);
- }
-
- /**
- * Resolves the supported response accept types for this operation.
- *
- * <p>
- * Walks the op-level {@code @RestOp}-group annotations for {@code
produces} attributes and (when
- * {@code noInherit} does not block it) the class-level {@code
@Rest(produces)} hierarchy. Each
- * value is SVL-resolved. If no explicit values are declared, falls
back to the supported media
- * types of the operation's {@link SerializerSet}.
- *
- * @return An unmodifiable list of media types, never {@code null}.
- */
- private List<MediaType> findSupportedAcceptTypes() {
- var result = collectAnnotationMediaTypes(PROPERTY_produces);
- if (result.isEmpty())
- return u(getSerializers().getSupportedMediaTypes());
- return u(result);
- }
-
private List<MediaType> collectAnnotationMediaTypes(String attr) {
var result = new ArrayList<MediaType>();
- var vr = context.getVarResolver();
+ var vr = restContext().getVarResolver();
// Class-level @Rest(consumes|produces) first (when inheritance
is allowed), then op-level overrides append.
if (isInherited(attr)) {
- for (var ai : context.getRestAnnotations())
+ for (var ai : restContext().getRestAnnotations())
appendResolvedMediaTypes(ai, attr, vr, result);
}
for (var ai : getRestOpAnnotations())
@@ -1192,34 +1156,6 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
}
}
- /**
- * Resolves the HTTP method for this operation from {@code
@RestOp}-group annotations.
- *
- * <p>
- * Walks the op-level annotations in child-to-parent order:
- * <ul>
- * <li>{@code @RestGet}/{@code @RestPut}/{@code @RestPost}/{@code
@RestDelete}/{@code @RestPatch}/{@code @RestOptions}
- * imply their fixed verb.
- * <li>{@code @RestOp(method)} is SVL-resolved; if blank, {@code
@RestOp(value)} is parsed for a leading verb token.
- * </ul>
- * <p>
- * If no annotation supplies a value, the verb is inferred from the
Java method name via
- * {@link HttpUtils#detectHttpMethod(Method, boolean, String)}. The
literal {@code "METHOD"} is
- * normalized to the wildcard {@code "*"}, and the result is
upper-cased.
- *
- * @return The resolved HTTP method, never {@code null}.
- */
- @SuppressWarnings("java:S3776")
- private String findHttpMethod() {
- var vr = context.getVarResolver();
- for (var ai : getRestOpAnnotations()) {
- var v = httpMethodFromAnnotation(ai.inner(), vr);
- if (v != null && !v.isEmpty())
- return normalizeHttpMethod(v);
- }
- return normalizeHttpMethod(HttpUtils.detectHttpMethod(method,
true, "GET"));
- }
-
@SuppressWarnings("java:S3776")
private static String httpMethodFromAnnotation(Annotation a,
VarResolver vr) {
if (a instanceof RestGet)
@@ -1339,7 +1275,7 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
// The 6 formerly-eager scalar fields are now memoized;
no eagerness needed here.
// Pre-warm httpMethod so it is in the memoizer cache
for immediate use by compareTo/match.
- httpMethodMemo.get();
+ httpMethod.get();
var pm = getPathMatchers();
bs.add(UrlPathMatcher[].class, pm);
@@ -1463,7 +1399,7 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
"java:S112" // throws Exception intentional -
callback/lifecycle method
})
public RestOpSession.Builder createSession(RestSession session) throws
Exception {
- return RestOpSession.create(this,
session).logger(getCallLogger()).debug(debugEnablementMemo.get().isDebug(this,
session.getRequest()));
+ return RestOpSession.create(this,
session).logger(getCallLogger()).debug(debugEnablement.get().isDebug(this,
session.getRequest()));
}
@Override /* Overridden from Object */
@@ -1476,63 +1412,63 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
*
* @return The bean context associated with this context.
*/
- public BeanContext getBeanContext() { return beanContextMemo.get(); }
+ public BeanContext getBeanContext() { return beanContext.get(); }
/**
* Returns the default charset.
*
* @return The default charset. Never <jk>null</jk>.
*/
- public Charset getDefaultCharset() { return defaultCharsetMemo.get(); }
+ public Charset getDefaultCharset() { return defaultCharset.get(); }
/**
* Returns the default request attributes.
*
* @return The default request attributes. Never <jk>null</jk>.
*/
- public NamedAttributeMap getDefaultRequestAttributes() { return
defaultRequestAttributesMemo.get(); }
+ public NamedAttributeMap getDefaultRequestAttributes() { return
defaultRequestAttributes.get(); }
/**
* Returns the default form data parameters.
*
* @return The default form data parameters. Never <jk>null</jk>.
*/
- public PartList getDefaultRequestFormData() { return
defaultRequestFormDataMemo.get(); }
+ public PartList getDefaultRequestFormData() { return
defaultRequestFormData.get(); }
/**
* Returns the default request headers.
*
* @return The default request headers. Never <jk>null</jk>.
*/
- public HeaderList getDefaultRequestHeaders() { return
defaultRequestHeadersMemo.get(); }
+ public HeaderList getDefaultRequestHeaders() { return
defaultRequestHeaders.get(); }
/**
* Returns the default request query parameters.
*
* @return The default request query parameters. Never <jk>null</jk>.
*/
- public PartList getDefaultRequestQueryData() { return
defaultRequestQueryDataMemo.get(); }
+ public PartList getDefaultRequestQueryData() { return
defaultRequestQueryData.get(); }
/**
* Returns the default response headers.
*
* @return The default response headers. Never <jk>null</jk>.
*/
- public HeaderList getDefaultResponseHeaders() { return
defaultResponseHeadersMemo.get(); }
+ public HeaderList getDefaultResponseHeaders() { return
defaultResponseHeaders.get(); }
/**
* Returns the compression encoders to use for this method.
*
* @return The compression encoders to use for this method.
*/
- public EncoderSet getEncoders() { return encodersMemo.get(); }
+ public EncoderSet getEncoders() { return encoders.get(); }
/**
* Returns the HTTP method name (e.g. <js>"GET"</js>).
*
* @return The HTTP method name.
*/
- public String getHttpMethod() { return httpMethodMemo.get(); }
+ public String getHttpMethod() { return httpMethod.get(); }
/**
* Returns the underlying Java method that this context belongs to.
@@ -1546,35 +1482,35 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
*
* @return The JSON-Schema generator applicable to this Java method.
*/
- public JsonSchemaGenerator getJsonSchemaGenerator() { return
jsonSchemaGeneratorMemo.get(); }
+ public JsonSchemaGenerator getJsonSchemaGenerator() { return
jsonSchemaGenerator.get(); }
/**
* Returns the max number of bytes to process in the input content.
*
* @return The max number of bytes to process in the input content.
*/
- public long getMaxInput() { return maxInputMemo.get(); }
+ public long getMaxInput() { return maxInput.get(); }
/**
* Returns the parsers to use for this method.
*
* @return The parsers to use for this method.
*/
- public ParserSet getParsers() { return parsersMemo.get(); }
+ public ParserSet getParsers() { return parsers.get(); }
/**
* Bean property getter: <property>partParser</property>.
*
* @return The value of the <property>partParser</property> property on
this bean, or <jk>null</jk> if it is not set.
*/
- public HttpPartParser getPartParser() { return partParserMemo.get(); }
+ public HttpPartParser getPartParser() { return partParser.get(); }
/**
* Bean property getter: <property>partSerializer</property>.
*
* @return The value of the <property>partSerializer</property>
property on this bean, or <jk>null</jk> if it is not set.
*/
- public HttpPartSerializer getPartSerializer() { return
partSerializerMemo.get(); }
+ public HttpPartSerializer getPartSerializer() { return
partSerializer.get(); }
/**
* Returns the path pattern for this method.
@@ -1589,7 +1525,7 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
* @return The URL path matchers for this operation.
* <br>Never <jk>null</jk>.
*/
- public UrlPathMatcher[] getPathMatchers() { return
pathMatchersMemo.get(); }
+ public UrlPathMatcher[] getPathMatchers() { return pathMatchers.get(); }
/**
* Returns the optional matchers for this operation.
@@ -1597,7 +1533,7 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
* @return The optional matchers for this operation.
* <br>Never <jk>null</jk>.
*/
- public RestMatcher[] getOptionalMatchers() { return
optionalMatchersMemo.get(); }
+ public RestMatcher[] getOptionalMatchers() { return
optionalMatchers.get(); }
/**
* Returns the required matchers for this operation.
@@ -1605,7 +1541,7 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
* @return The required matchers for this operation.
* <br>Never <jk>null</jk>.
*/
- public RestMatcher[] getRequiredMatchers() { return
requiredMatchersMemo.get(); }
+ public RestMatcher[] getRequiredMatchers() { return
requiredMatchers.get(); }
/**
* Returns the call logger for this operation.
@@ -1613,7 +1549,7 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
* @return The call logger for this operation.
* <br>Never <jk>null</jk>.
*/
- public CallLogger getCallLogger() { return callLoggerMemo.get(); }
+ public CallLogger getCallLogger() { return callLogger.get(); }
/**
* Returns metadata about the specified response object if it's
annotated with {@link Response @Response}.
@@ -1677,21 +1613,21 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
*
* @return The serializers to use for this method.
*/
- public SerializerSet getSerializers() { return serializersMemo.get(); }
+ public SerializerSet getSerializers() { return serializers.get(); }
/**
* Returns a list of supported accept types.
*
* @return An unmodifiable list.
*/
- public List<MediaType> getSupportedAcceptTypes() { return
supportedAcceptTypesMemo.get(); }
+ public List<MediaType> getSupportedAcceptTypes() { return
supportedAcceptTypes.get(); }
/**
* Returns the list of supported content types.
*
* @return An unmodifiable list.
*/
- public List<MediaType> getSupportedContentTypes() { return
supportedContentTypesMemo.get(); }
+ public List<MediaType> getSupportedContentTypes() { return
supportedContentTypes.get(); }
@Override /* Overridden from Object */
public int hashCode() {
@@ -1770,9 +1706,9 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
.a(PROP_httpMethod, getHttpMethod());
}
- RestConverter[] getConverters() { return convertersMemo.get(); }
+ RestConverter[] getConverters() { return converters.get(); }
- RestGuard[] getGuards() { return guardsMemo.get(); }
+ RestGuard[] getGuards() { return guards.get(); }
RestOpInvoker getMethodInvoker() { return methodInvoker; }
diff --git a/todo/TODO.md b/todo/TODO.md
index 341a739c78..019315deac 100644
--- a/todo/TODO.md
+++ b/todo/TODO.md
@@ -40,5 +40,3 @@
- [TODO-23] New feature support in org.apache.juneau.commons.inject — roadmap
for a simplified inject API (not a Spring replacement). See
`todo/TODO-23-commons-inject-framework-roadmap.md`.
- [TODO-24] JSR-330 alignment (no `jakarta.inject-api` dependency) + selective
Spring-lite features for `commons.inject`. See
`todo/TODO-24-jsr330-and-spring-lite-support.md`.
-
-- [TODO-30] Eliminate `RestOpContext.Builder` persistent reference: move
annotation-apply results (7 sub-builders + `AnnotationWorkList`) to private
final fields on `RestOpContext` so the `Builder` is GC-able after construction,
and convert 6 eagerly-resolved scalar fields to memoizers. See
`todo/TODO-30-eliminate-restopcontext-builder.md`.