You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2026/05/04 14:32:53 UTC
(juneau) branch master updated: RestOpContext: inline all findXxx() bodies into memoizer lambdas, rename Memo fields, add Javadoc
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 <ja...@jamesbog-ltmjqf2.internal.salesforce.com>
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`.