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 282b88f127 refactor: align/memoize rest context fields and resolve 
Sonar issues
282b88f127 is described below

commit 282b88f1272e74df3dbcda8addc4b2bbd71a2c9d
Author: James Bognar <[email protected]>
AuthorDate: Mon May 4 16:16:57 2026 -0400

    refactor: align/memoize rest context fields and resolve Sonar issues
---
 .../java/org/apache/juneau/rest/RestContext.java   | 850 +++++++++++----------
 .../org/apache/juneau/rest/RestContextInit.java    |   3 +-
 .../java/org/apache/juneau/rest/RestOpContext.java | 700 ++++++++---------
 .../org/apache/juneau/rest/RestOperations.java     |   1 -
 4 files changed, 818 insertions(+), 736 deletions(-)

diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
index 30deb4a74a..1b9c08cc0d 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
@@ -164,7 +164,7 @@ public class RestContext extends Context {
         * Builder class.
         *
         * <p>
-        * Demoted to package-private in TODO-16 Phase D-4a (2026-04-19). User 
code should construct a
+        * Demoted to package-private in the April 2026 refactor (2026-04-19). 
User code should construct a
         * {@link RestContext} via the public {@link 
#RestContext(RestContextInit)} constructor; this Builder
         * exists only as internal bootstrap state for the framework and is 
slated for inlining in a later phase.
         */
@@ -280,7 +280,7 @@ public class RestContext extends Context {
                 * Package-private constructor.
                 *
                 * <p>
-                * Demoted from {@code protected} to package-private in TODO-16 
Phase D-4a (2026-04-19). Only
+                * Demoted from {@code protected} to package-private in the 
April 2026 refactor (2026-04-19). Only
                 * {@link RestContext#toBuilder(RestContextInit)} instantiates 
this type now.
                 *
                 * @param resourceClass
@@ -709,7 +709,7 @@ public class RestContext extends Context {
                        // @formatter:off
                        // Note: pre-9.5 this also called 
.addBean(Builder.class, this) so user code could request the
                        // in-flight RestContext.Builder via @RestInit method 
parameters or @RestInject method-finder
-                       // resolution. That injection protocol was deleted in 
TODO-16 Phase C-3 (zero non-test callers
+                       // resolution. That injection protocol was deleted in 
the April 2026 refactor (zero non-test callers
                        // across the entire codebase) — the Builder is now a 
private staging detail of RestContext
                        // construction, not a user-visible bean.
                        beanStore = createBeanStore(resource)
@@ -1185,8 +1185,8 @@ public class RestContext extends Context {
                        // @formatter:off
                        // Note: pre-9.5 this filter also excluded 
`y.hasParameter(RestOpContext.Builder.class)` because the
                        // per-op `@RestInit(RestOpContext.Builder)` flow 
handled those separately. That flow was deleted in
-                       // TODO-16 Phase C-3 Route B (zero real-world callers), 
so the exclusion is gone too. Likewise, the
-                       // class-level `@RestInit(RestContext.Builder)` 
injection protocol was deleted in TODO-16 Phase C-3
+                       // April 2026 refactor Route B (zero real-world 
callers), so the exclusion is gone too. Likewise, the
+                       // class-level `@RestInit(RestContext.Builder)` 
injection protocol was deleted in the same refactor
                        // (zero non-test callers anywhere) — 
`RestContext.Builder` is no longer added to the bean store, so
                        // any straggling `@RestInit` method that still 
declares either Builder type as a parameter will now
                        // surface a "missing prerequisites" error here, which 
is the desired loud-failure signal.
@@ -1547,7 +1547,8 @@ public class RestContext extends Context {
                        beanStore.getBean(HttpPartParser.class).ifPresent(x -> 
v.get().impl(x));
 
                        // Replace with this bean.
-                       opt(HttpPartParser.class.isInstance(resource.get()) ? 
HttpPartParser.class.cast(resource.get()) : null).ifPresent(x -> 
v.get().impl(x));
+                       var r = resource.get();
+                       opt(r instanceof HttpPartParser x ? x : 
null).ifPresent(x -> v.get().impl(x));
 
                        // Specify the bean type if its set as a default.
                        beanStore.getBeanType(HttpPartParser.class).ifPresent(x 
-> v.get().type(x));
@@ -1583,7 +1584,8 @@ public class RestContext extends Context {
                        beanStore.getBean(HttpPartSerializer.class).ifPresent(x 
-> v.get().impl(x));
 
                        // Replace with this bean.
-                       opt(HttpPartSerializer.class.isInstance(resource.get()) 
? HttpPartSerializer.class.cast(resource.get()) : null).ifPresent(x -> 
v.get().impl(x));
+                       var r = resource.get();
+                       opt(r instanceof HttpPartSerializer x ? x : 
null).ifPresent(x -> v.get().impl(x));
 
                        // Specify the bean type if its set as a default.
                        
beanStore.getBeanType(HttpPartSerializer.class).ifPresent(x -> v.get().type(x));
@@ -1863,7 +1865,7 @@ public class RestContext extends Context {
 
                        var rci = ClassInfo.of(resource.get());
 
-                       // TODO-16 Phase C-3 (Route B, 2026-04-19): The per-op 
`@RestInit(RestOpContext.Builder)` protocol
+                       // April 2026 refactor (Route B, 2026-04-19): The 
per-op `@RestInit(RestOpContext.Builder)` protocol
                        // was deleted. Audit confirmed zero real-world callers 
(only stale javadoc/docs examples). Users who
                        // previously injected `RestOpContext.Builder` per op 
now configure operations declaratively via
                        // `@RestOp(...)` attributes, `@RestInject`-named 
beans, or the class-level `@RestInit(RestContext.Builder)`
@@ -2046,7 +2048,7 @@ public class RestContext extends Context {
         *
         * <p>
         * Wired into the annotation-processing machinery via {@link 
org.apache.juneau.annotation.ContextApply @ContextApply}
-        * on {@link Rest}. Moved into {@code RestContext} during TODO-16 Phase 
D-4a (2026-04-19) so that
+        * on {@link Rest}. Moved into {@code RestContext} during the April 
2026 refactor (2026-04-19) so that
         * {@link Builder} can be demoted to package-private without breaking 
the cross-package reference
         * that used to live in {@code RestAnnotation.RestContextApply}.
         */
@@ -2112,6 +2114,15 @@ public class RestContext extends Context {
        protected final String path;
        protected final ThreadLocal<RestSession> localSession = new 
ThreadLocal<>();
        protected final UrlPathMatcher pathMatcher;
+       private final Supplier<?> resource;
+
+       // Private accessors used by memoizer lambdas to satisfy Java's 
definite-assignment rules for blank final fields.
+       private BasicBeanStore beanStore() { return beanStore; }
+       private Builder builder() { return builder; }
+       private Supplier<?> resource() { return resource; }
+       private Class<?> resourceClass() { return resourceClass; }
+       private RestContext parentContext() { return parentContext; }
+       private RestOperations restOperations() { return restOperations; }
 
        private static final class LifecycleInvokerPair {
                final MethodList methods;
@@ -2123,97 +2134,37 @@ public class RestContext extends Context {
                }
        }
 
-       private final Memoizer<LifecycleInvokerPair> destroyInvokerPair = 
memoizer(this::findDestroyLifecycle);
-       private final Memoizer<LifecycleInvokerPair> endCallInvokerPair = 
memoizer(this::findEndCallLifecycle);
-       private final Memoizer<LifecycleInvokerPair> postInitInvokerPair = 
memoizer(this::findPostInitLifecycle);
-       private final Memoizer<LifecycleInvokerPair> 
postInitChildFirstInvokerPair = memoizer(this::findPostInitChildFirstLifecycle);
-       private final Memoizer<LifecycleInvokerPair> startCallInvokerPair = 
memoizer(this::findStartCallLifecycle);
-       private final Memoizer<MethodList> preCallMethodsMemo = 
memoizer(this::findPreCallMethodsList);
-       private final Memoizer<MethodList> postCallMethodsMemo = 
memoizer(this::findPostCallMethodsList);
-
-       private final Memoizer<Logger> loggerMemo = memoizer(this::findLogger);
-
-       private Logger findLogger() {
-               var v = Value.of(Logger.getLogger(cn(resourceClass)));
-               beanStore.getBean(Logger.class).ifPresent(v::set);
-               new BeanCreateMethodFinder<>(Logger.class, resource.get(), 
beanStore).addBean(Logger.class, 
v.get()).find(Builder::isRestInjectMethod).run(v::set);
-               return v.get();
-       }
-
-       // Bootstrap-time resolver — no Messages, no Config bean. Cached on the 
builder so that init()
-       // and findMessages()/findConfig() see the same instance during 
construction.
-       private final Memoizer<VarResolver> bootstrapVarResolverMemo = 
memoizer(this::findBootstrapVarResolver);
-
-       private VarResolver findBootstrapVarResolver() {
-               return builder.bootstrapVarResolver();
-       }
-
-       private final Memoizer<Messages> messagesMemo = 
memoizer(this::findMessages);
-
-       private Messages findMessages() {
-               var b = Messages.create(resourceClass);
-               // Walk @Rest annotations parent-to-child; child wins because 
location() prepends.
-               var anns = new ArrayList<>(getRestAnnotations());
-               Collections.reverse(anns);
-               // Resolve location strings against the simple resolver — full 
resolver isn't available yet
-               // (it depends on getMessages()).
-               var vrs = getBootstrapVarResolver().createSession();
-               anns.forEach(ai -> 
ai.getString(PROPERTY_messages).filter(StringUtils::isNotBlank).ifPresent(s -> 
b.location(vrs.resolve(s))));
-               beanStore.getBean(Messages.class).ifPresent(b::impl);
-               new BeanCreateMethodFinder<>(Messages.class, resource.get(), 
beanStore).addBean(Messages.Builder.class, 
b).find(Builder::isRestInjectMethod).run(b::impl);
-               return b.build();
-       }
-
-       private final Memoizer<ThrownStore> thrownStoreMemo = 
memoizer(this::findThrownStore);
-
-       private ThrownStore findThrownStore() {
-               var b = ThrownStore.create(beanStore).impl(parentContext == 
null ? null : parentContext.getThrownStore());
-               beanStore.getBeanType(ThrownStore.class).ifPresent(b::type);
-               beanStore.getBean(ThrownStore.class).ifPresent(b::impl);
-               new BeanCreateMethodFinder<>(ThrownStore.class, resource.get(), 
beanStore).addBean(ThrownStore.Builder.class, 
b).find(Builder::isRestInjectMethod).run(b::impl);
-               return b.build();
-       }
-
-       // Depends on getThrownStore() for thrownStoreOnce wiring.
-       private final Memoizer<MethodExecStore> methodExecStoreMemo = 
memoizer(this::findMethodExecStore);
-
-       private MethodExecStore findMethodExecStore() {
-               var b = 
MethodExecStore.create(beanStore).thrownStoreOnce(getThrownStore());
-               beanStore.getBeanType(MethodExecStore.class).ifPresent(b::type);
-               beanStore.getBean(MethodExecStore.class).ifPresent(b::impl);
-               new BeanCreateMethodFinder<>(MethodExecStore.class, 
resource.get(), beanStore).addBean(MethodExecStore.Builder.class, 
b).find(Builder::isRestInjectMethod).run(b::impl);
-               return b.build();
-       }
-
-       // Runtime resolver — wraps the simple resolver and adds Messages + 
Config beans.
-       // Depends on getBootstrapVarResolver() and getMessages(); pulls the 
bootstrap Config from the builder
-       // to avoid an infinite recursion with the runtime Config (which wraps 
the bootstrap Config in a
-       // session backed by *this* resolver).
-       private final Memoizer<VarResolver> varResolverMemo = 
memoizer(this::findVarResolver);
-
-       private VarResolver findVarResolver() {
-               var b = getBootstrapVarResolver().copy()
-                       .bean(Messages.class, getMessages())
-                       .bean(Config.class, builder.config());
-               beanStore.getBean(VarResolver.class).ifPresent(b::impl);
-               new BeanCreateMethodFinder<>(VarResolver.class, resource.get(), 
beanStore).addBean(VarResolver.Builder.class, 
b).find(Builder::isRestInjectMethod).run(b::impl);
-               return b.build();
-       }
-
-       private final Memoizer<Config> configMemo = memoizer(this::findConfig);
-
-       private Config findConfig() {
-               // builder.config() returns the unresolved config (already 
populated in Builder.init() via createConfig());
-               // wrapping with .resolving(varResolver.createSession()) yields 
the resolving variant exposed by getConfig().
-               return 
builder.config().resolving(getVarResolver().createSession());
-       }
+       /**
+        * The {@link BeanContext} for this resource.
+        *
+        * <p>
+        * Built from the builder's sub-builder, which is lazy-created and then 
mutated by the annotation
+        * apply pass during {@code init()} before this memoizer fires — so the 
builder is in its final
+        * configured state by the time {@code build()} is called.
+        */
+       private final Memoizer<BeanContext> beanContext = memoizer(() -> 
builder().beanContext().build());
 
-       private final Memoizer<CallLogger> callLoggerMemo = 
memoizer(this::findCallLogger);
+       /**
+        * The bootstrap-time {@link VarResolver} — no {@link Messages} or 
{@link Config} bean available yet.
+        *
+        * <p>
+        * Cached on the builder so that {@code init()} and the {@link 
#messages} / {@link #config}
+        * memoizers all see the same instance during construction.
+        */
+       private final Memoizer<VarResolver> bootstrapVarResolver = memoizer(() 
-> builder().bootstrapVarResolver());
 
-       private CallLogger findCallLogger() {
-               var creator = BeanCreator.of(CallLogger.class, 
beanStore).type(BasicCallLogger.class);
+       /**
+        * The {@link CallLogger} for this resource.
+        *
+        * <p>
+        * Defaults to {@link BasicCallLogger}. {@code @Rest(callLogger=X)} 
most-derived non-{@code Void} class wins.
+        * A bean-store override or {@code @RestInject} factory method REPLACES 
the result.
+        */
+       private final Memoizer<CallLogger> callLogger = memoizer(() -> {
+               var bs = beanStore();
+               var creator = BeanCreator.of(CallLogger.class, 
bs).type(BasicCallLogger.class);
                // Order matters — annotations override defaults so they need 
to be applied last.
-               
beanStore.getBeanType(CallLogger.class).ifPresent(creator::type);
+               bs.getBeanType(CallLogger.class).ifPresent(creator::type);
                // @Rest(callLogger=X) — most-derived non-Void wins.
                // getRestAnnotationsForProperty(...) yields parent-to-child 
order (rstream reversal); reduce-last
                // keeps the most-derived value, mirroring the prior "apply 
each annotation, child overrides parent" semantics.
@@ -2222,74 +2173,209 @@ public class RestContext extends Context {
                        .filter(c -> c != CallLogger.Void.class)
                        .reduce((first, second) -> second)
                        .ifPresent(creator::type);
-               beanStore.getBean(CallLogger.class).ifPresent(creator::impl);
-               new BeanCreateMethodFinder<>(CallLogger.class, resource.get(), 
beanStore).find(Builder::isRestInjectMethod).run(creator::impl);
+               bs.getBean(CallLogger.class).ifPresent(creator::impl);
+               new BeanCreateMethodFinder<>(CallLogger.class, 
resource().get(), bs).find(Builder::isRestInjectMethod).run(creator::impl);
                return creator.orElse(null);
-       }
+       });
 
-       private final Memoizer<DebugEnablement> debugEnablementMemo = 
memoizer(this::findDebugEnablement);
+       /**
+        * The resolving {@link Config} for this resource.
+        *
+        * <p>
+        * Wraps the builder's unresolved config with a {@link VarResolver} 
session so that SVL variables
+        * in config values are expanded on access.
+        */
+       private final Memoizer<Config> config = memoizer(() -> 
builder().config().resolving(getVarResolver().createSession()));
 
-       private DebugEnablement findDebugEnablement() {
+       /**
+        * The supported request content types ({@code Content-Type} media 
types) for this resource.
+        *
+        * <p>
+        * Walks {@code @Rest(consumes)} annotations parent-to-child and 
returns the union. When no annotation
+        * declares types, falls back to the intersection of all op-level 
parser supported media types.
+        */
+       private final Memoizer<List<MediaType>> consumes = memoizer(() -> {
+               // Walk @Rest(consumes=...) chain (parent-to-child); concat all 
entries in chain order.
+               var fromAnnotations = new ArrayList<MediaType>();
+               getRestAnnotationsForProperty(PROPERTY_consumes)
+                       .forEach(ai -> 
stream(ai.inner().consumes()).map(MediaType::of).forEach(fromAnnotations::add));
+               if (! fromAnnotations.isEmpty())
+                       return u(fromAnnotations);
+               // Fall back to the intersection of opContexts' 
parser-supported media types — matches legacy behavior.
+               var opContexts = restOperations().getOpContexts();
+               Set<MediaType> s = opContexts.isEmpty() ? emptySet() : 
toSet(opContexts.get(0).getParsers().getSupportedMediaTypes());
+               opContexts.forEach(x -> 
s.retainAll(x.getParsers().getSupportedMediaTypes()));
+               return u(toList(s));
+       });
+
+       /**
+        * The {@link DebugEnablement} for this resource.
+        *
+        * <p>
+        * Resolved from {@code @Rest(debugDefault)} (most-derived non-blank 
wins), falling back to
+        * {@code @Rest(debug=true|false)}. The resolved {@link Enablement} is 
published into the bean store
+        * so that {@link BasicDebugEnablement} subclasses can pick it up. 
Defaults to {@link BasicDebugEnablement}.
+        * A bean-store override or {@code @RestInject} factory method REPLACES 
the result.
+        */
+       private final Memoizer<DebugEnablement> debugEnablement = memoizer(() 
-> {
                // @Rest(debugDefault="ALWAYS|NEVER|CONDITIONAL") — 
most-derived non-blank value wins, with parent inheritance
                // gated by @Rest(noInherit={"debugDefault"}). Resolved value 
is published as an Enablement bean so that
-               // BasicDebugEnablement.init() (and any subclass) can pick it 
up via beanStore.getBean(Enablement.class).
+               // BasicDebugEnablement.init() (and any subclass) can pick it 
up via beanStore().getBean(Enablement.class).
                // Annotation value overrides any pre-registered Enablement 
bean (e.g. mock-client default).
                // If neither a debugDefault annotation value NOR a 
pre-registered Enablement bean is present, fall back
-               // to the @Rest(debug=true|false) boolean flag — ALWAYS when 
set, NEVER otherwise. This used to be done
-               // inside BasicDebugEnablement.init() by reading 
RestContext.Builder.isDebug() out of the bean store, but
-               // that protocol is gone (TODO-16 Phase C-3, 2026-04-19): no 
Enablement.class lookup escapes this method
-               // without a value, so subclasses no longer need access to the 
in-flight Builder.
+               // to the @Rest(debug=true|false) boolean flag — ALWAYS when 
set, NEVER otherwise.
+               var bs = beanStore();
                String debugDefaultStr = 
mergeReplacedStringAttribute(PROPERTY_debugDefault, 
env("RestContext.debugDefault").orElse(null));
                Enablement resolvedDebugDefault = null;
                if (nn(debugDefaultStr) && !debugDefaultStr.isBlank())
                        resolvedDebugDefault = 
Enablement.fromString(debugDefaultStr);
                if (nn(resolvedDebugDefault))
-                       beanStore.addBean(Enablement.class, 
resolvedDebugDefault);
-               else if (beanStore.getBean(Enablement.class).isEmpty())
-                       beanStore.addBean(Enablement.class, builder.isDebug() ? 
Enablement.ALWAYS : Enablement.NEVER);
-               var creator = BeanCreator.of(DebugEnablement.class, 
beanStore).type(BasicDebugEnablement.class);
+                       bs.addBean(Enablement.class, resolvedDebugDefault);
+               else if (bs.getBean(Enablement.class).isEmpty())
+                       bs.addBean(Enablement.class, builder().isDebug() ? 
Enablement.ALWAYS : Enablement.NEVER);
+               var creator = BeanCreator.of(DebugEnablement.class, 
bs).type(BasicDebugEnablement.class);
                // Order matters — annotations override defaults so they need 
to be applied last.
-               
beanStore.getBeanType(DebugEnablement.class).ifPresent(creator::type);
-               // @Rest(debugEnablement=X) — most-derived non-Void wins. See 
findCallLogger() for the reduce-last rationale.
+               bs.getBeanType(DebugEnablement.class).ifPresent(creator::type);
+               // @Rest(debugEnablement=X) — most-derived non-Void wins. See 
callLogger for the reduce-last rationale.
                getRestAnnotationsForProperty(PROPERTY_debugEnablement)
                        .map(ai -> ai.inner().debugEnablement())
                        .filter(c -> c != DebugEnablement.Void.class)
                        .reduce((first, second) -> second)
                        .ifPresent(creator::type);
-               
beanStore.getBean(DebugEnablement.class).ifPresent(creator::impl);
-               new BeanCreateMethodFinder<>(DebugEnablement.class, 
resource.get(), beanStore).find(Builder::isRestInjectMethod).run(creator::impl);
+               bs.getBean(DebugEnablement.class).ifPresent(creator::impl);
+               new BeanCreateMethodFinder<>(DebugEnablement.class, 
resource().get(), bs).find(Builder::isRestInjectMethod).run(creator::impl);
                return creator.orElse(null);
-       }
+       });
 
-       private final Memoizer<StaticFiles> staticFilesMemo = 
memoizer(this::findStaticFiles);
+       /**
+        * The default request attributes contributed by {@code 
@Rest(defaultRequestAttributes)} for this resource.
+        */
+       private final Memoizer<NamedAttributeMap> defaultRequestAttributes = 
memoizer(() -> builder().defaultRequestAttributes());
 
-       private StaticFiles findStaticFiles() {
-               var creator = BeanCreator.of(StaticFiles.class, 
beanStore).type(BasicStaticFiles.class);
-               // Order matters — annotations override defaults so they need 
to be applied last.
-               
beanStore.getBeanType(StaticFiles.class).ifPresent(creator::type);
-               // @Rest(staticFiles=X) — most-derived non-Void wins. See 
findCallLogger() for the reduce-last rationale.
-               getRestAnnotationsForProperty(PROPERTY_staticFiles)
-                       .map(ai -> ai.inner().staticFiles())
-                       .filter(c -> c != StaticFiles.Void.class)
-                       .reduce((first, second) -> second)
-                       .ifPresent(creator::type);
-               beanStore.getBean(StaticFiles.class).ifPresent(creator::impl);
-               new BeanCreateMethodFinder<>(StaticFiles.class, resource.get(), 
beanStore).find(Builder::isRestInjectMethod).run(creator::impl);
-               return creator.orElse(null);
-       }
+       /**
+        * The default request headers contributed by {@code 
@Rest(defaultRequestHeaders)} for this resource.
+        */
+       private final Memoizer<HeaderList> defaultRequestHeaders = memoizer(() 
-> builder().defaultRequestHeaders());
+
+       /**
+        * The default response headers contributed by {@code 
@Rest(defaultResponseHeaders)} for this resource.
+        */
+       private final Memoizer<HeaderList> defaultResponseHeaders = memoizer(() 
-> builder().defaultResponseHeaders());
 
-       private final Memoizer<BeanContext> beanContextMemo = 
memoizer(this::findBeanContext);
+       /**
+        * Methods annotated with {@link 
org.apache.juneau.rest.annotation.RestDestroy @RestDestroy} and their invokers.
+        */
+       private final Memoizer<LifecycleInvokerPair> destroyInvokerPair = 
memoizer(() -> buildLifecycleInvokerPair(() -> 
builder().createDestroyMethods(beanStore(), resource())));
 
-       private BeanContext findBeanContext() {
-               // Builder.beanContext() lazy-creates from BeanContext.create() 
seeded with bean-store overrides
-               // (BeanContext.Builder / BeanContext) and is mutated by 
init()'s apply(work) call before the ctor
-               // triggers this memoizer — so by the time we build() the 
builder it's in its final configured state.
-               return builder.beanContext().build();
-       }
+       /**
+        * The {@link EncoderSet} for this resource, built from the builder's 
encoder sub-builder.
+        */
+       private final Memoizer<EncoderSet> encoders = memoizer(() -> 
builder().encoders().build());
+
+       /**
+        * Methods annotated with {@link 
org.apache.juneau.rest.annotation.RestEndCall @RestEndCall} and their invokers.
+        */
+       private final Memoizer<LifecycleInvokerPair> endCallInvokerPair = 
memoizer(() -> buildLifecycleInvokerPair(() -> 
builder().createEndCallMethods(beanStore(), resource())));
+
+       /**
+        * The {@link JsonSchemaGenerator} for this resource, built from the 
builder's JSON-schema sub-builder.
+        */
+       private final Memoizer<JsonSchemaGenerator> jsonSchemaGenerator = 
memoizer(() -> builder().jsonSchemaGenerator().build());
+
+       /**
+        * The {@link Logger} for this resource.
+        *
+        * <p>
+        * Defaults to {@code Logger.getLogger(resourceClass.getName())}. A 
bean-store override or
+        * {@code @RestInject} factory method REPLACES the default.
+        */
+       private final Memoizer<Logger> logger = memoizer(() -> {
+               var v = Value.of(Logger.getLogger(cn(resourceClass())));
+               beanStore().getBean(Logger.class).ifPresent(v::set);
+               new BeanCreateMethodFinder<>(Logger.class, resource().get(), 
beanStore()).addBean(Logger.class, 
v.get()).find(Builder::isRestInjectMethod).run(v::set);
+               return v.get();
+       });
+
+       /**
+        * The {@link Messages} bundle for this resource.
+        *
+        * <p>
+        * Walks {@code @Rest} annotations parent-to-child, resolving each 
{@code messages} location string
+        * against the bootstrap resolver (full resolver not yet available — it 
depends on {@code getMessages()}).
+        * A bean-store override or {@code @RestInject} factory method REPLACES 
the result.
+        */
+       private final Memoizer<Messages> messages = memoizer(() -> {
+               var b = Messages.create(resourceClass());
+               // Walk @Rest annotations parent-to-child; child wins because 
location() prepends.
+               var anns = new ArrayList<>(getRestAnnotations());
+               Collections.reverse(anns);
+               // Resolve location strings against the simple resolver — full 
resolver isn't available yet
+               // (it depends on getMessages()).
+               var vrs = getBootstrapVarResolver().createSession();
+               anns.forEach(ai -> 
ai.getString(PROPERTY_messages).filter(StringUtils::isNotBlank).ifPresent(s -> 
b.location(vrs.resolve(s))));
+               beanStore().getBean(Messages.class).ifPresent(b::impl);
+               new BeanCreateMethodFinder<>(Messages.class, resource().get(), 
beanStore()).addBean(Messages.Builder.class, 
b).find(Builder::isRestInjectMethod).run(b::impl);
+               return b.build();
+       });
+
+       /**
+        * The {@link MethodExecStore} for this resource, wired to the {@link 
ThrownStore}.
+        *
+        * <p>
+        * A bean-store override or {@code @RestInject} factory method REPLACES 
the result.
+        */
+       private final Memoizer<MethodExecStore> methodExecStore = memoizer(() 
-> {
+               var bs = beanStore();
+               var b = 
MethodExecStore.create(bs).thrownStoreOnce(getThrownStore());
+               bs.getBeanType(MethodExecStore.class).ifPresent(b::type);
+               bs.getBean(MethodExecStore.class).ifPresent(b::impl);
+               new BeanCreateMethodFinder<>(MethodExecStore.class, 
resource().get(), bs).addBean(MethodExecStore.Builder.class, 
b).find(Builder::isRestInjectMethod).run(b::impl);
+               return b.build();
+       });
+
+       /**
+        * The {@link ParserSet} for this resource, built from the builder's 
parser sub-builder.
+        */
+       private final Memoizer<ParserSet> parsers = memoizer(() -> 
builder().parsers().build());
+
+       /**
+        * The {@link HttpPartParser} for this resource, created from the 
builder's part-parser creator.
+        */
+       private final Memoizer<HttpPartParser> partParser = memoizer(() -> 
builder().partParser().create());
+
+       /**
+        * The {@link HttpPartSerializer} for this resource, created from the 
builder's part-serializer creator.
+        */
+       private final Memoizer<HttpPartSerializer> partSerializer = memoizer(() 
-> builder().partSerializer().create());
+
+       /**
+        * Methods annotated with {@link 
org.apache.juneau.rest.annotation.RestPostCall @RestPostCall}.
+        */
+       private final Memoizer<MethodList> postCallMethods = memoizer(() -> 
builder().createPostCallMethods(beanStore(), resource()));
+
+       /**
+        * Methods annotated with {@link 
org.apache.juneau.rest.annotation.RestPostInit @RestPostInit}{@code 
(childFirst=true)} and their invokers.
+        */
+       private final Memoizer<LifecycleInvokerPair> 
postInitChildFirstInvokerPair = memoizer(() -> buildLifecycleInvokerPair(() -> 
builder().createPostInitChildFirstMethods(beanStore(), resource())));
+
+       /**
+        * Methods annotated with {@link 
org.apache.juneau.rest.annotation.RestPostInit @RestPostInit} and their 
invokers.
+        */
+       private final Memoizer<LifecycleInvokerPair> postInitInvokerPair = 
memoizer(() -> buildLifecycleInvokerPair(() -> 
builder().createPostInitMethods(beanStore(), resource())));
 
-       private final Memoizer<List<MediaType>> producesMemo = 
memoizer(this::findProduces);
+       /**
+        * Methods annotated with {@link 
org.apache.juneau.rest.annotation.RestPreCall @RestPreCall}.
+        */
+       private final Memoizer<MethodList> preCallMethods = memoizer(() -> 
builder().createPreCallMethods(beanStore(), resource()));
 
-       private List<MediaType> findProduces() {
+       /**
+        * The supported response content types ({@code Accept} media types) 
for this resource.
+        *
+        * <p>
+        * Walks {@code @Rest(produces)} annotations parent-to-child and 
returns the union. When no annotation
+        * declares types, falls back to the intersection of all op-level 
serializer supported media types.
+        */
+       private final Memoizer<List<MediaType>> produces = memoizer(() -> {
                // Walk @Rest(produces=...) chain (parent-to-child); concat all 
entries in chain order.
                var fromAnnotations = new ArrayList<MediaType>();
                getRestAnnotationsForProperty(PROPERTY_produces)
@@ -2297,139 +2383,152 @@ public class RestContext extends Context {
                if (! fromAnnotations.isEmpty())
                        return u(fromAnnotations);
                // Fall back to the intersection of opContexts' 
serializer-supported media types — matches legacy behavior.
-               var opContexts = restOperations.getOpContexts();
+               var opContexts = restOperations().getOpContexts();
                Set<MediaType> s = opContexts.isEmpty() ? emptySet() : 
toSet(opContexts.get(0).getSerializers().getSupportedMediaTypes());
                opContexts.forEach(x -> 
s.retainAll(x.getSerializers().getSupportedMediaTypes()));
                return u(toList(s));
-       }
-
-       private final Memoizer<List<MediaType>> consumesMemo = 
memoizer(this::findConsumes);
+       });
 
-       private List<MediaType> findConsumes() {
-               // Walk @Rest(consumes=...) chain (parent-to-child); concat all 
entries in chain order.
-               var fromAnnotations = new ArrayList<MediaType>();
-               getRestAnnotationsForProperty(PROPERTY_consumes)
-                       .forEach(ai -> 
stream(ai.inner().consumes()).map(MediaType::of).forEach(fromAnnotations::add));
-               if (! fromAnnotations.isEmpty())
-                       return u(fromAnnotations);
-               // Fall back to the intersection of opContexts' 
parser-supported media types — matches legacy behavior.
-               var opContexts = restOperations.getOpContexts();
-               Set<MediaType> s = opContexts.isEmpty() ? emptySet() : 
toSet(opContexts.get(0).getParsers().getSupportedMediaTypes());
-               opContexts.forEach(x -> 
s.retainAll(x.getParsers().getSupportedMediaTypes()));
-               return u(toList(s));
-       }
-
-       private final Memoizer<ResponseProcessor[]> responseProcessorsMemo = 
memoizer(this::findResponseProcessors);
-
-       private ResponseProcessor[] findResponseProcessors() {
+       /**
+        * The ordered array of {@link ResponseProcessor} instances for this 
resource.
+        *
+        * <p>
+        * Walks {@code @Rest(responseProcessors)} annotations parent-to-child 
(append order). A bean-store
+        * override or {@code @RestInject} factory method REPLACES the entire 
list.
+        */
+       private final Memoizer<ResponseProcessor[]> responseProcessors = 
memoizer(() -> {
                // Walk @Rest(responseProcessors=...) chain (parent-to-child 
via getRestAnnotationsForProperty).
                // DefaultConfig contributes the framework defaults at the top 
of the chain; resource-class entries append.
                // ResponseProcessorList.Builder.add(...) uses addAll (append) 
— final order: [DefaultConfig, parent, child].
-               var v = Value.of(ResponseProcessorList.create(beanStore));
+               var bs = beanStore();
+               var v = Value.of(ResponseProcessorList.create(bs));
                getRestAnnotationsForProperty(PROPERTY_responseProcessors)
                        .forEach(ai -> 
v.get().add(ai.inner().responseProcessors()));
                // Bean-store override REPLACES the entire annotation-derived 
list.
-               beanStore.getBean(ResponseProcessorList.class).ifPresent(x -> 
v.get().impl(x));
+               bs.getBean(ResponseProcessorList.class).ifPresent(x -> 
v.get().impl(x));
                // @RestInject method override REPLACES the entire 
annotation-derived list.
-               new BeanCreateMethodFinder<>(ResponseProcessorList.class, 
resource.get(), beanStore)
+               new BeanCreateMethodFinder<>(ResponseProcessorList.class, 
resource().get(), bs)
                        .addBean(ResponseProcessorList.Builder.class, v.get())
                        .find(Builder::isRestInjectMethod)
                        .run(x -> v.get().impl(x));
                return v.get().build().toArray();
-       }
-
-       private final Memoizer<Class<? extends RestOpArg>[]> restOpArgsMemo = 
memoizer(this::findRestOpArgs);
+       });
 
-       private Class<? extends RestOpArg>[] findRestOpArgs() {
+       /**
+        * The ordered array of {@link RestOpArg} argument-resolver types for 
this resource.
+        *
+        * <p>
+        * Walks {@code @Rest(restOpArgs)} annotations parent-to-child (prepend 
order, so child entries
+        * take priority). A bean-store override or {@code @RestInject} factory 
method REPLACES the entire list.
+        */
+       private final Memoizer<Class<? extends RestOpArg>[]> restOpArgs = 
memoizer(() -> {
                // Walk @Rest(restOpArgs=...) chain (parent-to-child via 
getRestAnnotationsForProperty).
                // DefaultConfig contributes the framework arg-resolver list; 
resource-class entries override.
                // RestOpArgList.Builder.add(...) uses prependAll — applying 
per-annotation in chain order yields
                // final order: [child, parent, DefaultConfig], matching the 
legacy apply-pass behavior.
-               var v = Value.of(RestOpArgList.create(beanStore));
+               var bs = beanStore();
+               var v = Value.of(RestOpArgList.create(bs));
                getRestAnnotationsForProperty(PROPERTY_restOpArgs)
                        .forEach(ai -> v.get().add(ai.inner().restOpArgs()));
                // Bean-store override REPLACES the entire annotation-derived 
list.
-               beanStore.getBean(RestOpArgList.class).ifPresent(x -> 
v.get().impl(x));
+               bs.getBean(RestOpArgList.class).ifPresent(x -> v.get().impl(x));
                // @RestInject method override REPLACES the entire 
annotation-derived list.
-               new BeanCreateMethodFinder<>(RestOpArgList.class, 
resource.get(), beanStore)
+               new BeanCreateMethodFinder<>(RestOpArgList.class, 
resource().get(), bs)
                        .addBean(RestOpArgList.Builder.class, v.get())
                        .find(Builder::isRestInjectMethod)
                        .run(x -> v.get().impl(x));
                return v.get().build().asArray();
-       }
-
-       private final Memoizer<SerializerSet> serializersMemo = 
memoizer(this::findSerializers);
-
-       private SerializerSet findSerializers() {
-               return builder.serializers().build();
-       }
+       });
 
-       private final Memoizer<ParserSet> parsersMemo = 
memoizer(this::findParsers);
-
-       private ParserSet findParsers() {
-               return builder.parsers().build();
-       }
-
-       private final Memoizer<HttpPartSerializer> partSerializerMemo = 
memoizer(this::findPartSerializer);
-
-       private HttpPartSerializer findPartSerializer() {
-               return builder.partSerializer().create();
-       }
-
-       private final Memoizer<HttpPartParser> partParserMemo = 
memoizer(this::findPartParser);
-
-       private HttpPartParser findPartParser() {
-               return builder.partParser().create();
-       }
-
-       private final Memoizer<JsonSchemaGenerator> jsonSchemaGeneratorMemo = 
memoizer(this::findJsonSchemaGenerator);
-
-       private JsonSchemaGenerator findJsonSchemaGenerator() {
-               return builder.jsonSchemaGenerator().build();
-       }
-
-       private final Memoizer<EncoderSet> encodersMemo = 
memoizer(this::findEncoders);
-
-       private EncoderSet findEncoders() {
-               return builder.encoders().build();
-       }
-
-       private final Memoizer<HeaderList> defaultRequestHeadersMemo = 
memoizer(this::findDefaultRequestHeaders);
-
-       private HeaderList findDefaultRequestHeaders() {
-               return builder.defaultRequestHeaders();
-       }
-
-       private final Memoizer<HeaderList> defaultResponseHeadersMemo = 
memoizer(this::findDefaultResponseHeaders);
-
-       private HeaderList findDefaultResponseHeaders() {
-               return builder.defaultResponseHeaders();
-       }
-
-       private final Memoizer<NamedAttributeMap> defaultRequestAttributesMemo 
= memoizer(this::findDefaultRequestAttributes);
+       /**
+        * The {@link SerializerSet} for this resource, built from the 
builder's serializer sub-builder.
+        */
+       private final Memoizer<SerializerSet> serializers = memoizer(() -> 
builder().serializers().build());
 
-       private NamedAttributeMap findDefaultRequestAttributes() {
-               return builder.defaultRequestAttributes();
-       }
+       /**
+        * Methods annotated with {@link 
org.apache.juneau.rest.annotation.RestStartCall @RestStartCall} and their 
invokers.
+        */
+       private final Memoizer<LifecycleInvokerPair> startCallInvokerPair = 
memoizer(() -> buildLifecycleInvokerPair(() -> 
builder().createStartCallMethods(beanStore(), resource())));
 
-       private final Memoizer<SwaggerProvider> swaggerProviderMemo = 
memoizer(this::findSwaggerProvider);
+       /**
+        * The {@link StaticFiles} provider for this resource.
+        *
+        * <p>
+        * Defaults to {@link BasicStaticFiles}. {@code @Rest(staticFiles=X)} 
most-derived non-{@code Void} class wins.
+        * A bean-store override or {@code @RestInject} factory method REPLACES 
the result.
+        */
+       private final Memoizer<StaticFiles> staticFiles = memoizer(() -> {
+               var bs = beanStore();
+               var creator = BeanCreator.of(StaticFiles.class, 
bs).type(BasicStaticFiles.class);
+               // Order matters — annotations override defaults so they need 
to be applied last.
+               bs.getBeanType(StaticFiles.class).ifPresent(creator::type);
+               // @Rest(staticFiles=X) — most-derived non-Void wins. See 
callLogger for the reduce-last rationale.
+               getRestAnnotationsForProperty(PROPERTY_staticFiles)
+                       .map(ai -> ai.inner().staticFiles())
+                       .filter(c -> c != StaticFiles.Void.class)
+                       .reduce((first, second) -> second)
+                       .ifPresent(creator::type);
+               bs.getBean(StaticFiles.class).ifPresent(creator::impl);
+               new BeanCreateMethodFinder<>(StaticFiles.class, 
resource().get(), bs).find(Builder::isRestInjectMethod).run(creator::impl);
+               return creator.orElse(null);
+       });
 
-       private SwaggerProvider findSwaggerProvider() {
-               var creator = BeanCreator.of(SwaggerProvider.class, 
beanStore).type(BasicSwaggerProvider.class);
+       /**
+        * The {@link SwaggerProvider} for this resource.
+        *
+        * <p>
+        * Defaults to {@link BasicSwaggerProvider}. {@code 
@Rest(swaggerProvider=X)} most-derived
+        * non-{@code Void} class wins. A bean-store override or {@code 
@RestInject} factory method REPLACES the result.
+        */
+       private final Memoizer<SwaggerProvider> swaggerProvider = memoizer(() 
-> {
+               var bs = beanStore();
+               var creator = BeanCreator.of(SwaggerProvider.class, 
bs).type(BasicSwaggerProvider.class);
                // Order matters — annotations override defaults so they need 
to be applied last.
-               
beanStore.getBeanType(SwaggerProvider.class).ifPresent(creator::type);
-               // @Rest(swaggerProvider=X) — most-derived non-Void wins. See 
findCallLogger() for the reduce-last rationale.
+               bs.getBeanType(SwaggerProvider.class).ifPresent(creator::type);
+               // @Rest(swaggerProvider=X) — most-derived non-Void wins. See 
callLogger for the reduce-last rationale.
                getRestAnnotationsForProperty(PROPERTY_swaggerProvider)
                        .map(ai -> ai.inner().swaggerProvider())
                        .filter(c -> c != SwaggerProvider.Void.class)
                        .reduce((first, second) -> second)
                        .ifPresent(creator::type);
-               
beanStore.getBean(SwaggerProvider.class).ifPresent(creator::impl);
-               new BeanCreateMethodFinder<>(SwaggerProvider.class, 
resource.get(), beanStore).find(Builder::isRestInjectMethod).run(creator::impl);
+               bs.getBean(SwaggerProvider.class).ifPresent(creator::impl);
+               new BeanCreateMethodFinder<>(SwaggerProvider.class, 
resource().get(), bs).find(Builder::isRestInjectMethod).run(creator::impl);
                return creator.orElse(null);
-       }
+       });
 
-       private final Supplier<?> resource;
+       /**
+        * The {@link ThrownStore} for this resource, used to track exception 
statistics.
+        *
+        * <p>
+        * Inherits from the parent context's store when one is present. A 
bean-store override or
+        * {@code @RestInject} factory method REPLACES the result.
+        */
+       private final Memoizer<ThrownStore> thrownStore = memoizer(() -> {
+               var bs = beanStore();
+               var b = ThrownStore.create(bs).impl(parentContext() == null ? 
null : parentContext().getThrownStore());
+               bs.getBeanType(ThrownStore.class).ifPresent(b::type);
+               bs.getBean(ThrownStore.class).ifPresent(b::impl);
+               new BeanCreateMethodFinder<>(ThrownStore.class, 
resource().get(), bs).addBean(ThrownStore.Builder.class, 
b).find(Builder::isRestInjectMethod).run(b::impl);
+               return b.build();
+       });
+
+       /**
+        * The runtime {@link VarResolver} — wraps the bootstrap resolver and 
adds {@link Messages} and {@link Config} beans.
+        *
+        * <p>
+        * The bootstrap {@link Config} is pulled from the builder to avoid a 
circular dependency: the runtime
+        * {@link Config} wraps the bootstrap config in a session backed by 
this resolver.
+        * A bean-store override or {@code @RestInject} factory method REPLACES 
the result.
+        */
+       private final Memoizer<VarResolver> varResolver = memoizer(() -> {
+               var bs = beanStore();
+               var b = getBootstrapVarResolver().copy()
+                       .bean(Messages.class, getMessages())
+                       .bean(Config.class, builder().config());
+               bs.getBean(VarResolver.class).ifPresent(b::impl);
+               new BeanCreateMethodFinder<>(VarResolver.class, 
resource().get(), bs).addBean(VarResolver.Builder.class, 
b).find(Builder::isRestInjectMethod).run(b::impl);
+               return b.build();
+       });
 
        /**
         * Constructor — record-based entry point.
@@ -2462,7 +2561,7 @@ public class RestContext extends Context {
         * Internal constructor.
         *
         * <p>
-        * Privatized in TODO-16 Phase D-4a (2026-04-19). External callers must 
use
+        * Privatized in the April 2026 refactor (2026-04-19). External callers 
must use
         * {@link #RestContext(RestContextInit)} instead; this constructor only 
services the internal
         * {@link #toBuilder(RestContextInit)} helper.
         *
@@ -2532,8 +2631,8 @@ public class RestContext extends Context {
                        restChildren = builder.restChildren(this).build();
                        bs.addBean(SwaggerProvider.class, getSwaggerProvider());
 
-                       // produces/consumes are resolved lazily via the 
producesMemo/consumesMemo memoizers below
-                       // (TODO-16 Phase D-1, 2026-04-19) — they walk the 
@Rest(produces=...) / @Rest(consumes=...)
+                       // produces/consumes are resolved lazily via the 
produces/consumes memoizers below
+                       // (April 2026 refactor, 2026-04-19) — they walk the 
@Rest(produces=...) / @Rest(consumes=...)
                        // chain first, then fall back to deriving the 
intersection of opContexts' supported media types.
 
                } catch (BasicHttpException e) {
@@ -2556,6 +2655,40 @@ public class RestContext extends Context {
        // Memoized allowlist fields
        
//---------------------------------------------------------------------------------------------
 
+       /**
+        * Memoized parser session-option keys from {@code 
@Rest(allowedParserOptions)}, after SVL resolution and comma expansion.
+        *
+        * <p>
+        * When inheritance is not blocked, keys from {@link #parentContext} 
are included first, then this resource's own tokens.
+        * Leading-hyphen tokens (e.g. {@code -foo}) remove earlier positive 
tokens.
+        */
+       private final Memoizer<SortedSet<String>> allowedParserOptions = 
memoizer(() -> {
+               var l = new ArrayList<String>();
+               var p = PROPERTY_allowedParserOptions;
+               var pc = parentContext();
+               if (isInherited(p) && pc != null)
+                       l.addAll(pc.getAllowedParserOptions());
+               getRestAnnotationsForProperty(p).forEach(x -> 
resolveCdl(x.getStringArray(p)).forEach(l::add));
+               return u(treeSetCi(removeNegations(l)));
+       });
+
+       /**
+        * Memoized serializer session-option keys from {@code 
@Rest(allowedSerializerOptions)}, after SVL resolution and comma expansion.
+        *
+        * <p>
+        * When inheritance is not blocked, keys from {@link #parentContext} 
are included first, then this resource's own tokens.
+        * Leading-hyphen tokens remove earlier positive tokens.
+        */
+       private final Memoizer<SortedSet<String>> allowedSerializerOptions = 
memoizer(() -> {
+               var l = new ArrayList<String>();
+               var p = PROPERTY_allowedSerializerOptions;
+               var pc = parentContext();
+               if (isInherited(p) && pc != null)
+                       l.addAll(pc.getAllowedSerializerOptions());
+               getRestAnnotationsForProperty(p).forEach(x -> 
resolveCdl(x.getStringArray(p)).forEach(l::add));
+               return u(treeSetCi(removeNegations(l)));
+       });
+
        /**
         * Memoized value of the {@code noInherit} annotation attribute from 
the nearest {@code @Rest} annotation.
         *
@@ -2581,9 +2714,7 @@ public class RestContext extends Context {
         * {@code DefaultConfig} (transitively or directly) won't get a 
duplicate entry — the list is dedup'd by the
         * {@link Class} the annotation was declared on.
         */
-       private final Memoizer<List<AnnotationInfo<Rest>>> restAnnotations = 
memoizer(this::findRestAnnotations);
-
-       private List<AnnotationInfo<Rest>> findRestAnnotations() {
+       private final Memoizer<List<AnnotationInfo<Rest>>> restAnnotations = 
memoizer(() -> {
                var raw = getAnnotationProvider().find(Rest.class, 
ClassInfo.of(getResourceClass()));
                var hasDefaultConfig = raw.stream().anyMatch(ai -> 
ai.getAnnotatable() instanceof ClassInfo ci && 
DefaultConfig.class.equals(ci.inner()));
                if (hasDefaultConfig)
@@ -2594,43 +2725,7 @@ public class RestContext extends Context {
                var combined = new ArrayList<>(raw);
                combined.addAll(defaultConfigAnnotations);
                return Collections.unmodifiableList(combined);
-       }
-
-       /**
-        * Memoized parser session-option keys from {@code 
@Rest(allowedParserOptions)}, after SVL resolution and comma expansion.
-        *
-        * <p>
-        * When inheritance is not blocked, keys from {@link #parentContext} 
are included first, then this resource's own tokens.
-        * Leading-hyphen tokens (e.g. {@code -foo}) remove earlier positive 
tokens.
-        */
-       private final Memoizer<SortedSet<String>> allowedParserOptions = 
memoizer(this::findAllowedParserOptions);
-
-       private SortedSet<String> findAllowedParserOptions() {
-               var l = new ArrayList<String>();
-               var p = PROPERTY_allowedParserOptions;
-               if (isInherited(p) && parentContext != null)
-                       l.addAll(parentContext.getAllowedParserOptions());
-               getRestAnnotationsForProperty(p).forEach(x -> 
resolveCdl(x.getStringArray(p)).forEach(l::add));
-               return u(treeSetCi(removeNegations(l)));
-       }
-
-       /**
-        * Memoized serializer session-option keys from {@code 
@Rest(allowedSerializerOptions)}, after SVL resolution and comma expansion.
-        *
-        * <p>
-        * When inheritance is not blocked, keys from {@link #parentContext} 
are included first, then this resource's own tokens.
-        * Leading-hyphen tokens remove earlier positive tokens.
-        */
-       private final Memoizer<SortedSet<String>> allowedSerializerOptions = 
memoizer(this::findAllowedSerializerOptions);
-
-       private SortedSet<String> findAllowedSerializerOptions() {
-               var l = new ArrayList<String>();
-               var p = PROPERTY_allowedSerializerOptions;
-               if (isInherited(p) && parentContext != null)
-                       l.addAll(parentContext.getAllowedSerializerOptions());
-               getRestAnnotationsForProperty(p).forEach(x -> 
resolveCdl(x.getStringArray(p)).forEach(l::add));
-               return u(treeSetCi(removeNegations(l)));
-       }
+       });
 
        private Stream<AnnotationInfo<Rest>> 
restAnnotationsForPropertySortedByRank(String propertyName) {
                return 
getRestAnnotationsForProperty(propertyName).sorted(Comparator.comparingInt(AnnotationInfo::getRank));
@@ -2679,45 +2774,27 @@ public class RestContext extends Context {
                return v.get();
        }
 
-       private final Memoizer<Set<String>> allowedHeaderParams = 
memoizer(this::findAllowedHeaderParams);
-
-       private Set<String> findAllowedHeaderParams() {
-               return 
Collections.unmodifiableSet(newCaseInsensitiveSet(mergeReplacedStringAttribute(PROPERTY_allowedHeaderParams,
 env("RestContext.allowedHeaderParams", "Accept,Content-Type"))));
-       }
-
-       private final Memoizer<Set<String>> allowedMethodHeaders = 
memoizer(this::findAllowedMethodHeaders);
+       /** Header names that may be passed via URL query parameter; resolved 
from {@code @Rest(allowedHeaderParams)}, default {@code "Accept,Content-Type"}. 
*/
+       private final Memoizer<Set<String>> allowedHeaderParams      = 
memoizer(() -> 
Collections.unmodifiableSet(newCaseInsensitiveSet(mergeReplacedStringAttribute(PROPERTY_allowedHeaderParams,
 env("RestContext.allowedHeaderParams", "Accept,Content-Type")))));
+       /** HTTP method names that may be specified via a request header; 
resolved from {@code @Rest(allowedMethodHeaders)}, default empty. */
+       private final Memoizer<Set<String>> allowedMethodHeaders     = 
memoizer(() -> 
Collections.unmodifiableSet(newCaseInsensitiveSet(mergeReplacedStringAttribute(PROPERTY_allowedMethodHeaders,
 env("RestContext.allowedMethodHeaders").orElse("")))));
+       /** HTTP method names that may be specified via URL query parameter; 
resolved from {@code @Rest(allowedMethodParams)}, default {@code 
"HEAD,OPTIONS"}. */
+       private final Memoizer<Set<String>> allowedMethodParams      = 
memoizer(() -> 
Collections.unmodifiableSet(newCaseInsensitiveSet(mergeReplacedStringAttribute(PROPERTY_allowedMethodParams,
 env("RestContext.allowedMethodParams", "HEAD,OPTIONS")))));
+       /** Whether a {@code &content=} URL parameter may override the request 
body; inverse of {@code @Rest(disableContentParam)}. */
+       private final Memoizer<Boolean>     allowContentParam        = 
memoizer(() -> !mergeReplacedBooleanAttribute(PROPERTY_disableContentParam, 
env("RestContext.disableContentParam", false)));
+       /** Whether exception stack traces are rendered in error responses; 
resolved from {@code @Rest(renderResponseStackTraces)}. */
+       private final Memoizer<Boolean>     renderResponseStackTraces = 
memoizer(() -> 
mergeReplacedBooleanAttribute(PROPERTY_renderResponseStackTraces, 
env("RestContext.renderResponseStackTraces", false)));
+       /** The request header used for client-version matching; resolved from 
{@code @Rest(clientVersionHeader)}, default {@code "Client-Version"}. */
+       private final Memoizer<String>      clientVersionHeader      = 
memoizer(() -> mergeReplacedStringAttribute(PROPERTY_clientVersionHeader, 
env("RestContext.clientVersionHeader", "Client-Version")));
 
-       private Set<String> findAllowedMethodHeaders() {
-               return 
Collections.unmodifiableSet(newCaseInsensitiveSet(mergeReplacedStringAttribute(PROPERTY_allowedMethodHeaders,
 env("RestContext.allowedMethodHeaders").orElse(""))));
-       }
-
-       private final Memoizer<Set<String>> allowedMethodParams = 
memoizer(this::findAllowedMethodParams);
-
-       private Set<String> findAllowedMethodParams() {
-               return 
Collections.unmodifiableSet(newCaseInsensitiveSet(mergeReplacedStringAttribute(PROPERTY_allowedMethodParams,
 env("RestContext.allowedMethodParams", "HEAD,OPTIONS"))));
-       }
-
-       private final Memoizer<Boolean> allowContentParam = 
memoizer(this::findAllowContentParam);
-
-       private boolean findAllowContentParam() {
-               return ! 
mergeReplacedBooleanAttribute(PROPERTY_disableContentParam, 
env("RestContext.disableContentParam", false));
-       }
-
-       private final Memoizer<Boolean> renderResponseStackTraces = 
memoizer(this::findRenderResponseStackTraces);
-
-       private boolean findRenderResponseStackTraces() {
-               return 
mergeReplacedBooleanAttribute(PROPERTY_renderResponseStackTraces, 
env("RestContext.renderResponseStackTraces", false));
-       }
-
-       private final Memoizer<String> clientVersionHeader = 
memoizer(this::findClientVersionHeader);
-
-       private String findClientVersionHeader() {
-               return 
mergeReplacedStringAttribute(PROPERTY_clientVersionHeader, 
env("RestContext.clientVersionHeader", "Client-Version"));
-       }
-
-       private final Memoizer<UriRelativity> uriRelativity = 
memoizer(this::findUriRelativity);
-
-       private UriRelativity findUriRelativity() {
+       /**
+        * The {@link UriRelativity} strategy for URI resolution in this 
resource.
+        *
+        * <p>
+        * Resolved from {@code @Rest(uriRelativity)} annotations (most-derived 
non-blank wins), with env-var
+        * fallback. Defaults to {@link UriRelativity#RESOURCE}.
+        */
+       private final Memoizer<UriRelativity> uriRelativity = memoizer(() -> {
                var v = new AtomicReference<>(
                        parseEnumConstant(UriRelativity.class, 
resolve(emptyIfNull(env("RestContext.uriRelativity").get())))
                                .orElse(UriRelativity.RESOURCE)
@@ -2726,11 +2803,46 @@ public class RestContext extends Context {
                        parseEnumConstant(UriRelativity.class, 
resolve(s)).ifPresent(v::set)
                ));
                return v.get();
-       }
+       });
 
-       private final Memoizer<UriResolution> uriResolution = 
memoizer(this::findUriResolution);
+       /**
+        * The URI authority (scheme + host + port) override for this resource.
+        *
+        * <p>
+        * Resolved from {@code @Rest(uriAuthority)} annotations and env-var; 
inherited from parent when not
+        * blocked by {@code noInherit}. {@code null} means no override.
+        */
+       private final Memoizer<String> uriAuthority = memoizer(() -> {
+               String local = 
mergeReplacedStringAttribute(PROPERTY_uriAuthority, 
env("RestContext.uriAuthority").orElse(null));
+               if (nn(local))
+                       return local;
+               var pc = parentContext();
+               return isInherited(PROPERTY_uriAuthority) && pc != null ? 
pc.getUriAuthority() : null;
+       });
+
+       /**
+        * The URI context path override for this resource.
+        *
+        * <p>
+        * Resolved from {@code @Rest(uriContext)} annotations and env-var; 
inherited from parent when not
+        * blocked by {@code noInherit}. {@code null} means no override.
+        */
+       private final Memoizer<String> uriContext = memoizer(() -> {
+               String local = 
mergeReplacedStringAttribute(PROPERTY_uriContext, 
env("RestContext.uriContext").orElse(null));
+               if (nn(local))
+                       return local;
+               var pc = parentContext();
+               return isInherited(PROPERTY_uriContext) && pc != null ? 
pc.getUriContext() : null;
+       });
 
-       private UriResolution findUriResolution() {
+       /**
+        * The {@link UriResolution} strategy for URI resolution in this 
resource.
+        *
+        * <p>
+        * Resolved from {@code @Rest(uriResolution)} annotations (most-derived 
non-blank wins), with env-var
+        * fallback. Defaults to {@link UriResolution#ROOT_RELATIVE}.
+        */
+       private final Memoizer<UriResolution> uriResolution = memoizer(() -> {
                var v = new AtomicReference<>(
                        parseEnumConstant(UriResolution.class, 
resolve(emptyIfNull(env("RestContext.uriResolution").get())))
                                .orElse(UriResolution.ROOT_RELATIVE)
@@ -2739,25 +2851,7 @@ public class RestContext extends Context {
                        parseEnumConstant(UriResolution.class, 
resolve(s)).ifPresent(v::set)
                ));
                return v.get();
-       }
-
-       private final Memoizer<String> uriAuthority = 
memoizer(this::findUriAuthority);
-
-       private String findUriAuthority() {
-               String local = 
mergeReplacedStringAttribute(PROPERTY_uriAuthority, 
env("RestContext.uriAuthority").orElse(null));
-               if (nn(local))
-                       return local;
-               return isInherited(PROPERTY_uriAuthority) ? 
parentContext.getUriAuthority() : null;
-       }
-
-       private final Memoizer<String> uriContext = 
memoizer(this::findUriContext);
-
-       private String findUriContext() {
-               String local = 
mergeReplacedStringAttribute(PROPERTY_uriContext, 
env("RestContext.uriContext").orElse(null));
-               if (nn(local))
-                       return local;
-               return isInherited(PROPERTY_uriContext) ? 
parentContext.getUriContext() : null;
-       }
+       });
 
        /**
         * Returns the {@link Rest} annotations on the resource class hierarchy 
for the specified property,
@@ -2899,7 +2993,7 @@ public class RestContext extends Context {
 
                // Must be careful not to bleed thread-locals.
                if (nn(localSession.get()))
-                       LOG.warning("WARNING:  Thread-local call object was not 
cleaned up from previous request.  {}, thread=[{}]", this, 
Thread.currentThread().getId());
+                       LOG.warning("WARNING:  Thread-local call object was not 
cleaned up from previous request.  {}, thread=[{}]", this, 
Thread.currentThread().getName());
 
                RestSession.Builder sb = 
createSession().resource(resource).req(r1).res(r2).logger(getCallLogger());
 
@@ -3039,7 +3133,7 @@ public class RestContext extends Context {
         *
         * @return The bean store associated with this context.
         */
-       public BeanContext getBeanContext() { return beanContextMemo.get(); }
+       public BeanContext getBeanContext() { return beanContext.get(); }
 
        BeanContext.Builder         getBeanContextBuilder()          { return 
builder.beanContext(); }
        EncoderSet.Builder          getEncodersBuilder()             { return 
builder.encoders(); }
@@ -3086,7 +3180,7 @@ public class RestContext extends Context {
         *      The call logger to use for this resource.
         *      <br>Never <jk>null</jk>.
         */
-       public CallLogger getCallLogger() { return callLoggerMemo.get(); }
+       public CallLogger getCallLogger() { return callLogger.get(); }
 
        /**
         * Returns the name of the client version header name used by this 
resource.
@@ -3111,7 +3205,7 @@ public class RestContext extends Context {
         *      The resolving config file associated with this servlet.
         *      <br>Never <jk>null</jk>.
         */
-       public Config getConfig() { return configMemo.get(); }
+       public Config getConfig() { return config.get(); }
 
        /**
         * Returns the explicit list of supported content types for this 
resource.
@@ -3126,14 +3220,14 @@ public class RestContext extends Context {
         *      An unmodifiable list of supported <c>Content-Type</c> header 
values for this resource.
         *      <br>Never <jk>null</jk>.
         */
-       public List<MediaType> getConsumes() { return consumesMemo.get(); }
+       public List<MediaType> getConsumes() { return consumes.get(); }
 
        /**
         * Returns the debug enablement bean for this context.
         *
         * @return The debug enablement bean for this context.
         */
-       public DebugEnablement getDebugEnablement() { return 
debugEnablementMemo.get(); }
+       public DebugEnablement getDebugEnablement() { return 
debugEnablement.get(); }
 
        /**
         * Returns the default request attributes for this resource.
@@ -3146,7 +3240,7 @@ public class RestContext extends Context {
         *      The default request headers for this resource in an 
unmodifiable list.
         *      <br>Never <jk>null</jk>.
         */
-       public NamedAttributeMap getDefaultRequestAttributes() { return 
defaultRequestAttributesMemo.get(); }
+       public NamedAttributeMap getDefaultRequestAttributes() { return 
defaultRequestAttributes.get(); }
 
        /**
         * Returns the default request headers for this resource.
@@ -3159,7 +3253,7 @@ public class RestContext extends Context {
         *      The default request headers for this resource in an 
unmodifiable list.
         *      <br>Never <jk>null</jk>.
         */
-       public HeaderList getDefaultRequestHeaders() { return 
defaultRequestHeadersMemo.get(); }
+       public HeaderList getDefaultRequestHeaders() { return 
defaultRequestHeaders.get(); }
 
        /**
         * Returns the default response headers for this resource.
@@ -3172,14 +3266,14 @@ public class RestContext extends Context {
         *      The default response headers for this resource in an 
unmodifiable list.
         *      <br>Never <jk>null</jk>.
         */
-       public HeaderList getDefaultResponseHeaders() { return 
defaultResponseHeadersMemo.get(); }
+       public HeaderList getDefaultResponseHeaders() { return 
defaultResponseHeaders.get(); }
 
        /**
         * Returns the encoders associated with this context.
         *
         * @return The encoders associated with this context.
         */
-       public EncoderSet getEncoders() { return encodersMemo.get(); }
+       public EncoderSet getEncoders() { return encoders.get(); }
 
        /**
         * Returns the path for this resource as defined by the {@link 
Rest#path() @Rest(path)} annotation or
@@ -3203,7 +3297,7 @@ public class RestContext extends Context {
         *      The HTTP-part serializer associated with this resource.
         *      <br>Never <jk>null</jk>.
         */
-       public JsonSchemaGenerator getJsonSchemaGenerator() { return 
jsonSchemaGeneratorMemo.get(); }
+       public JsonSchemaGenerator getJsonSchemaGenerator() { return 
jsonSchemaGenerator.get(); }
 
        /**
         * Returns the HTTP call for the current request.
@@ -3225,7 +3319,7 @@ public class RestContext extends Context {
         *      The logger for this resource.
         *      <br>Never <jk>null</jk>.
         */
-       public Logger getLogger() { return loggerMemo.get(); }
+       public Logger getLogger() { return logger.get(); }
 
        /**
         * Returns the resource bundle used by this resource.
@@ -3234,21 +3328,21 @@ public class RestContext extends Context {
         *      The resource bundle for this resource.
         *      <br>Never <jk>null</jk>.
         */
-       public Messages getMessages() { return messagesMemo.get(); }
+       public Messages getMessages() { return messages.get(); }
 
        /**
         * Returns the timing statistics on all method executions on this class.
         *
         * @return The timing statistics on all method executions on this class.
         */
-       public MethodExecStore getMethodExecStore() { return 
methodExecStoreMemo.get(); }
+       public MethodExecStore getMethodExecStore() { return 
methodExecStore.get(); }
 
        /**
         * Returns the parsers associated with this context.
         *
         * @return The parsers associated with this context.
         */
-       public ParserSet getParsers() { return parsersMemo.get(); }
+       public ParserSet getParsers() { return parsers.get(); }
 
        /**
         * Returns the HTTP-part parser associated with this resource.
@@ -3257,7 +3351,7 @@ public class RestContext extends Context {
         *      The HTTP-part parser associated with this resource.
         *      <br>Never <jk>null</jk>.
         */
-       public HttpPartParser getPartParser() { return partParserMemo.get(); }
+       public HttpPartParser getPartParser() { return partParser.get(); }
 
        /**
         * Returns the HTTP-part serializer associated with this resource.
@@ -3266,7 +3360,7 @@ public class RestContext extends Context {
         *      The HTTP-part serializer associated with this resource.
         *      <br>Never <jk>null</jk>.
         */
-       public HttpPartSerializer getPartSerializer() { return 
partSerializerMemo.get(); }
+       public HttpPartSerializer getPartSerializer() { return 
partSerializer.get(); }
 
        /**
         * Returns the path for this resource as defined by the {@link 
Rest#path() @Rest(path)} annotation or
@@ -3303,7 +3397,7 @@ public class RestContext extends Context {
         *      An unmodifiable list of supported <c>Accept</c> header values 
for this resource.
         *      <br>Never <jk>null</jk>.
         */
-       public List<MediaType> getProduces() { return producesMemo.get(); }
+       public List<MediaType> getProduces() { return produces.get(); }
 
        /**
         * Returns the resource object.
@@ -3341,7 +3435,7 @@ public class RestContext extends Context {
         *      The response processors for this resource.
         *      <br>Never <jk>null</jk>.
         */
-       public ResponseProcessor[] getResponseProcessors() { return 
responseProcessorsMemo.get(); }
+       public ResponseProcessor[] getResponseProcessors() { return 
responseProcessors.get(); }
 
        /**
         * Returns the {@link RestOpArg} classes registered on this resource.
@@ -3359,7 +3453,7 @@ public class RestContext extends Context {
         *      The REST-op-arg classes for this resource.
         *      <br>Never <jk>null</jk>.
         */
-       public Class<? extends RestOpArg>[] getRestOpArgs() { return 
restOpArgsMemo.get(); }
+       public Class<? extends RestOpArg>[] getRestOpArgs() { return 
restOpArgs.get(); }
 
        /**
         * Returns the child resources associated with this servlet.
@@ -3397,7 +3491,7 @@ public class RestContext extends Context {
         *
         * @return The serializers associated with this context.
         */
-       public SerializerSet getSerializers() { return serializersMemo.get(); }
+       public SerializerSet getSerializers() { return serializers.get(); }
 
        /**
         * Returns the servlet init parameter returned by {@link 
ServletConfig#getInitParameter(String)}.
@@ -3416,7 +3510,7 @@ public class RestContext extends Context {
         *      The static files for this resource.
         *      <br>Never <jk>null</jk>.
         */
-       public StaticFiles getStaticFiles() { return staticFilesMemo.get(); }
+       public StaticFiles getStaticFiles() { return staticFiles.get(); }
 
        /**
         * Gives access to the internal statistics on this context.
@@ -3456,7 +3550,7 @@ public class RestContext extends Context {
         *      The information provider for this resource.
         *      <br>Never <jk>null</jk>.
         */
-       public SwaggerProvider getSwaggerProvider() { return 
swaggerProviderMemo.get(); }
+       public SwaggerProvider getSwaggerProvider() { return 
swaggerProvider.get(); }
 
        /**
         * Returns the stack trace database associated with this context.
@@ -3465,7 +3559,7 @@ public class RestContext extends Context {
         *      The stack trace database for this resource.
         *      <br>Never <jk>null</jk>.
         */
-       public ThrownStore getThrownStore() { return thrownStoreMemo.get(); }
+       public ThrownStore getThrownStore() { return thrownStore.get(); }
 
        /**
         * Returns the authority path of the resource.
@@ -3574,7 +3668,7 @@ public class RestContext extends Context {
         *
         * @return The var resolver in use by this resource.
         */
-       public VarResolver getVarResolver() { return varResolverMemo.get(); }
+       public VarResolver getVarResolver() { return varResolver.get(); }
 
        /**
         * Returns the bootstrap (pre-runtime) variable resolver used during 
context construction.
@@ -3587,7 +3681,7 @@ public class RestContext extends Context {
         *
         * @return The bootstrap var resolver in use by this resource.
         */
-       public VarResolver getBootstrapVarResolver() { return 
bootstrapVarResolverMemo.get(); }
+       public VarResolver getBootstrapVarResolver() { return 
bootstrapVarResolver.get(); }
 
        /**
         * Returns whether it's safe to pass the HTTP content as a 
<js>"content"</js> GET parameter.
@@ -3674,34 +3768,6 @@ public class RestContext extends Context {
                return u(s);
        }
 
-       private LifecycleInvokerPair findDestroyLifecycle() {
-               return buildLifecycleInvokerPair(() -> 
builder.createDestroyMethods(beanStore, resource));
-       }
-
-       private LifecycleInvokerPair findEndCallLifecycle() {
-               return buildLifecycleInvokerPair(() -> 
builder.createEndCallMethods(beanStore, resource));
-       }
-
-       private LifecycleInvokerPair findPostInitLifecycle() {
-               return buildLifecycleInvokerPair(() -> 
builder.createPostInitMethods(beanStore, resource));
-       }
-
-       private LifecycleInvokerPair findPostInitChildFirstLifecycle() {
-               return buildLifecycleInvokerPair(() -> 
builder.createPostInitChildFirstMethods(beanStore, resource));
-       }
-
-       private LifecycleInvokerPair findStartCallLifecycle() {
-               return buildLifecycleInvokerPair(() -> 
builder.createStartCallMethods(beanStore, resource));
-       }
-
-       private MethodList findPreCallMethodsList() {
-               return builder.createPreCallMethods(beanStore, resource);
-       }
-
-       private MethodList findPostCallMethodsList() {
-               return builder.createPostCallMethods(beanStore, resource);
-       }
-
        private LifecycleInvokerPair 
buildLifecycleInvokerPair(Supplier<MethodList> methods) {
                var ml = MethodList.of(methods.get());
                var inv = 
ml.stream().map(this::toMethodInvoker).toArray(MethodInvoker[]::new);
@@ -3832,14 +3898,14 @@ public class RestContext extends Context {
         *
         * @return The list of methods to invoke after the actual REST method 
is called.
         */
-       public MethodList getPostCallMethods() { return 
postCallMethodsMemo.get(); }
+       public MethodList getPostCallMethods() { return postCallMethods.get(); }
 
        /**
         * Returns the list of methods to invoke before the actual REST method 
is called.
         *
         * @return The list of methods to invoke before the actual REST method 
is called.
         */
-       public MethodList getPreCallMethods() { return 
preCallMethodsMemo.get(); }
+       public MethodList getPreCallMethods() { return preCallMethods.get(); }
 
        /**
         * Returns the list of methods to invoke during servlet destruction.
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextInit.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextInit.java
index 00b4c7b51c..cc91ea11a1 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextInit.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextInit.java
@@ -61,7 +61,8 @@ import org.apache.juneau.rest.annotation.Rest;
  *
  * <h5 class='section'>Note on naming:</h5>
  * <p>
- * Decision #27 in {@code TODO-16} originally settled on the {@code XArgs} 
suffix for ctor-arg-bundle records, but
+ * Decision #27 in the RestContext refactor plan originally settled on the 
{@code XArgs} suffix for
+ * ctor-arg-bundle records, but
  * {@code RestContextArgs} is already taken by the {@link 
org.apache.juneau.rest.arg.RestContextArgs} parameter
  * resolver in the {@link org.apache.juneau.rest.arg} package (one of an 
entire family of {@code *Args}
  * {@link org.apache.juneau.rest.annotation.RestOp @RestOp}-method 
arg-resolver classes:
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 0eab48fc90..a6e480bf89 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
@@ -81,7 +81,9 @@ import jakarta.servlet.http.*;
  * </ul>
  */
 @SuppressWarnings({
-       "java:S115"  // Constants use UPPER_snakeCase convention (e.g., 
PROP_defaultRequestFormData)
+       "java:S115",  // Constants use UPPER_snakeCase convention (e.g., 
PROP_defaultRequestFormData)
+       "java:S1200", // High coupling is intentional in this central operation 
context aggregator.
+       "java:S6539"  // RestOpContext intentionally centralizes op wiring and 
annotation resolution.
 })
 public class RestOpContext extends Context implements 
Comparable<RestOpContext> {
 
@@ -155,24 +157,22 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                return 
BeanCreator.of(HttpPartSerializer.class).type(c).orElse(defaultSerializer);
        }
 
-       protected final int hierarchyDepth;
+       private final BasicBeanStore opBeanStore;
        protected final Map<Class<?>,ResponseBeanMeta> responseBeanMetas = new 
ConcurrentHashMap<>();
        protected final Map<Class<?>,ResponsePartMeta> headerPartMetas = new 
ConcurrentHashMap<>();
        protected final Method method;
        protected final MethodInfo mi;
        protected final RestContext context;
-       protected final RestOpInvoker methodInvoker;
-       protected final RestOpInvoker[] postCallMethods;
-       protected final RestOpInvoker[] preCallMethods;
-       protected final ResponseBeanMeta responseMeta;
 
        // The annotation work-list produced during construction.
        private final AnnotationWorkList appliedAnnotations;
 
        private RestContext restContext() { return context; }
        private Method method() { return method; }
+       private MethodInfo methodInfo() { return mi; }
        private AnnotationWorkList appliedAnnotations() { return 
appliedAnnotations; }
        private BasicBeanStore beanStore() { return context.getBeanStore(); }
+       private BasicBeanStore opBeanStore() { return opBeanStore; }
        private Object resource() { return context.getResource(); }
        private VarResolver varResolver() { return context.getVarResolver(); }
 
@@ -180,115 +180,6 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
        // Memoized fields
        
//-----------------------------------------------------------------------------------------------------------------
 
-       /** 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(beanStore()).enable(Enablement.fromString(v.get()), 
"*").build();
-               if (isInherited(PROPERTY_debug))
-                       return restContext().getDebugEnablement();
-               return DebugEnablement.create(beanStore()).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 = varResolver();
-               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"));
-       });
-
-       /**
-        * 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();
-       });
-
-       /** 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 u(treeSetCi(l));
-       });
-
        /** Effective allowed parser session-option keys for this operation. */
        private final Memoizer<SortedSet<String>> allowedParserOptions = 
memoizer(() -> {
                var l = new ArrayList<String>();
@@ -334,136 +225,55 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
        private final Memoizer<CallLogger> callLogger = memoizer(() -> 
restContext().getCallLogger());
 
        /**
-        * The encoder group for this operation.
-        *
-        * <p>
-        * 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 final Memoizer<EncoderSet> encoders = memoizer(() -> {
-               var bs = beanStore();
-               var b = restContext().getEncodersBuilder().copy();
-               getRestOpAnnotationsForProperty(PROPERTY_encoders).forEach(ai 
-> {
-                       var c = ai.getClassArray("encoders", 
Encoder.class).orElse(null);
-                       if (nn(c) && c.length > 0)
-                               b.set(c);
-               });
-               var v = Value.of(b.build());
-               new BeanCreateMethodFinder<>(EncoderSet.class, resource(), bs)
-                       .find(this::matchesInjectScope)
-                       .run(v::set);
-               return v.get();
-       });
-
-       /** The JSON-Schema generator for this operation (op-level annotations 
applied on top of the parent). */
-       private final Memoizer<JsonSchemaGenerator> jsonSchemaGenerator = 
memoizer(() -> {
-               var aa = appliedAnnotations();
-               var parent = restContext().getJsonSchemaGeneratorBuilder();
-               if (!parent.canApply(aa))
-                       return restContext().getJsonSchemaGenerator();
-               Value<JsonSchemaGenerator.Builder> v = Value.of(parent.copy());
-               v.get().apply(aa);
-               var bs = BasicBeanStore.of(beanStore())
-                       .addBean(Method.class, method())
-                       .addBean(JsonSchemaGenerator.Builder.class, v.get());
-               new BeanCreateMethodFinder<>(JsonSchemaGenerator.class, 
resource(), bs)
-                       .find(this::matchesInjectScope)
-                       .run(x -> v.get().impl(x));
-               return v.get().build();
-       });
-
-       /**
-        * The parser group for this operation.
+        * The response-converter array 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.
+        * 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.
         */
-       private final Memoizer<ParserSet> parsers = memoizer(() -> {
-               var aa = appliedAnnotations();
+       private final Memoizer<RestConverter[]> converters = memoizer(() -> {
                var bs = beanStore();
-               var pb = restContext().getParsersBuilder();
-               var b = pb.copy();
-               if (pb.canApply(aa))
-                       b.apply(aa);
-               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, resource(), bs)
-                       .find(this::matchesInjectScope)
-                       .run(result::set);
-               return result.get();
-       });
-
-       /** The HTTP part parser for this operation (op-level creator applied 
on top of the parent). */
-       private final Memoizer<HttpPartParser> partParser = memoizer(() -> {
-               var aa = appliedAnnotations();
-               var parent = restContext().getPartParserCreator();
-               if (!parent.canApply(aa))
-                       return restContext().getPartParser();
-               Value<HttpPartParser.Creator> v = Value.of(parent.copy());
-               v.get().apply(aa);
-               var bs = BasicBeanStore.of(beanStore())
-                       .addBean(Method.class, method())
-                       .addBean(HttpPartParser.Creator.class, v.get());
-               new BeanCreateMethodFinder<>(HttpPartParser.class, resource(), 
bs)
+               var v = Value.of(RestConverterList.create(bs));
+               if (isInherited(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 -> {
+                               for (var c : classes)
+                                       v.get().append(classArray(c));
+                       }));
+               bs.getBean(RestConverterList.class).ifPresent(x -> 
v.get().impl(x));
+               new BeanCreateMethodFinder<>(RestConverterList.class, 
resource(), bs)
                        .find(this::matchesInjectScope)
                        .run(x -> v.get().impl(x));
-               return v.get().create();
+               return v.get().build().asArray();
        });
 
-       /** The HTTP part serializer for this operation (op-level creator 
applied on top of the parent). */
-       private final Memoizer<HttpPartSerializer> partSerializer = memoizer(() 
-> {
-               var aa = appliedAnnotations();
-               var parent = restContext().getPartSerializerCreator();
-               if (!parent.canApply(aa))
-                       return restContext().getPartSerializer();
-               Value<HttpPartSerializer.Creator> v = Value.of(parent.copy());
-               v.get().apply(aa);
-               var bs = BasicBeanStore.of(beanStore())
-                       .addBean(Method.class, method())
-                       .addBean(HttpPartSerializer.Creator.class, v.get());
-               new BeanCreateMethodFinder<>(HttpPartSerializer.class, 
resource(), bs)
-                       .find(this::matchesInjectScope)
-                       .run(x -> v.get().impl(x));
-               return v.get().create();
+       /** The effective {@link DebugEnablement} for this operation. */
+       private final Memoizer<DebugEnablement> debugEnablement = memoizer(() 
-> {
+               var v = findOpString(PROPERTY_debug);
+               if (v.isPresent())
+                       return 
DebugEnablement.create(beanStore()).enable(Enablement.fromString(v.get()), 
"*").build();
+               if (isInherited(PROPERTY_debug))
+                       return restContext().getDebugEnablement();
+               return DebugEnablement.create(beanStore()).build();
        });
 
-       /**
-        * 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 final Memoizer<SerializerSet> serializers = memoizer(() -> {
-               var aa = appliedAnnotations();
-               var bs = beanStore();
-               var sb = restContext().getSerializersBuilder();
-               var b = sb.copy();
-               if (sb.canApply(aa))
-                       b.apply(aa);
-               
getRestOpAnnotationsForProperty(PROPERTY_serializers).forEach(ai -> {
-                       var c = ai.getClassArray("serializers", 
Serializer.class).orElse(null);
-                       if (nn(c) && c.length > 0)
-                               b.set(c);
-               });
-               var result = Value.of(b.build());
-               new BeanCreateMethodFinder<>(SerializerSet.class, resource(), 
bs)
-                       .find(this::matchesInjectScope)
-                       .run(result::set);
-               return result.get();
+       /** 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();
        });
 
        /**
@@ -618,96 +428,31 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
        });
 
        /**
-        * Iterates over each parameter annotation on the operation method, 
computing the parameter's
-        * {@link Schema#default_()}/{@link Schema#df()} string 
(joined-non-blank-first) and dispatching
-        * each annotation+default pair to the supplied callback.
-        */
-       private void 
processParameterDefaults(java.util.function.BiConsumer<Annotation,String> 
callback) {
-               for (var aa : method.getParameterAnnotations()) {
-                       String def = null;
-                       for (var a : aa) {
-                               if (a instanceof Schema s)
-                                       def = 
joinnlFirstNonEmptyArray(s.default_(), s.df());
-                       }
-                       if (def == null)
-                               continue;
-                       for (var a : aa)
-                               callback.accept(a, def);
-               }
-       }
-
-       /**
-        * The response-converter array for this operation.
+        * The encoder group for this operation.
         *
         * <p>
-        * 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.
+        * 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 final Memoizer<RestConverter[]> converters = memoizer(() -> {
+       private final Memoizer<EncoderSet> encoders = memoizer(() -> {
                var bs = beanStore();
-               var v = Value.of(RestConverterList.create(bs));
-               if (isInherited(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 -> {
-                               for (var c : classes)
-                                       v.get().append(classArray(c));
-                       }));
-               bs.getBean(RestConverterList.class).ifPresent(x -> 
v.get().impl(x));
-               new BeanCreateMethodFinder<>(RestConverterList.class, 
resource(), bs)
+               var b = restContext().getEncodersBuilder().copy();
+               getRestOpAnnotationsForProperty(PROPERTY_encoders).forEach(ai 
-> {
+                       var c = ai.getClassArray("encoders", 
Encoder.class).orElse(null);
+                       if (nn(c) && c.length > 0)
+                               b.set(c);
+               });
+               var v = Value.of(b.build());
+               new BeanCreateMethodFinder<>(EncoderSet.class, resource(), bs)
                        .find(this::matchesInjectScope)
-                       .run(x -> v.get().impl(x));
-               return v.get().build().asArray();
+                       .run(v::set);
+               return v.get();
        });
 
-       /**
-        * Returns {@code true} if the given method has a {@code @RestInject} 
annotation whose
-        * {@code methodScope} includes this operation's method name (or {@code 
"*"}).
-        *
-        * <p>
-        * Used by op-level memoizers when scanning the resource class for 
{@code @RestInject}-supplied
-        * composite-bean overrides ({@code RestConverterList}, {@code 
RestGuardList}, etc.). This is the
-        * {@link RestOpContext}-scope peer of {@link 
Builder#matches(MethodInfo)} — kept in sync with that
-        * one.
-        */
-       private boolean matchesInjectScope(MethodInfo annotated) {
-               var a = 
annotated.getAnnotations(RestInject.class).findFirst().map(AnnotationInfo::inner).orElse(null);
-               if (a != null) {
-                       for (var n : a.methodScope()) {
-                               if ("*".equals(n) || method.getName().equals(n))
-                                       return true;
-                       }
-               }
-               return false;
-       }
-
-       /**
-        * Same as {@link #matchesInjectScope(MethodInfo)} but additionally 
requires the
-        * {@link RestInject#name()} attribute to equal {@code beanName}.
-        *
-        * <p>
-        * Used for named composite-bean overrides ({@code 
defaultRequestHeaders}, {@code defaultResponseHeaders},
-        * etc.) where the bean type alone is ambiguous (e.g. {@link 
HeaderList} appears as both request and
-        * response headers).
-        */
-       private boolean matchesInjectScope(MethodInfo annotated, String 
beanName) {
-               var a = 
annotated.getAnnotations(RestInject.class).findFirst().map(AnnotationInfo::inner).orElse(null);
-               if (a != null) {
-                       if (! a.name().equals(beanName))
-                               return false;
-                       for (var n : a.methodScope()) {
-                               if ("*".equals(n) || method.getName().equals(n))
-                                       return true;
-                       }
-               }
-               return false;
-       }
-
        /**
         * The request-guard array for this operation. <b>Cross-bucket 
memoizer</b> — folds three
         * annotation attributes ({@code guards}, {@code roleGuard}, {@code 
rolesDeclared}) into a
@@ -761,11 +506,58 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                        }
                }
 
-               bs.getBean(RestGuardList.class).ifPresent(x -> v.get().impl(x));
-               new BeanCreateMethodFinder<>(RestGuardList.class, resource(), 
bs)
+               bs.getBean(RestGuardList.class).ifPresent(x -> v.get().impl(x));
+               new BeanCreateMethodFinder<>(RestGuardList.class, resource(), 
bs)
+                       .find(this::matchesInjectScope)
+                       .run(x -> v.get().impl(x));
+               return v.get().build().asArray();
+       });
+
+       /** The depth of the declaring class in the inheritance chain. */
+       private final Memoizer<Integer> hierarchyDepth = memoizer(() -> {
+               int hierarchyDepthTemp = 0;
+               var sc = method().getDeclaringClass().getSuperclass();
+               while (nn(sc)) {
+                       hierarchyDepthTemp++;
+                       sc = sc.getSuperclass();
+               }
+               return hierarchyDepthTemp;
+       });
+
+       /**
+        * 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 = varResolver();
+               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"));
+       });
+
+       /** The JSON-Schema generator for this operation (op-level annotations 
applied on top of the parent). */
+       private final Memoizer<JsonSchemaGenerator> jsonSchemaGenerator = 
memoizer(() -> {
+               var aa = appliedAnnotations();
+               var parent = restContext().getJsonSchemaGeneratorBuilder();
+               if (!parent.canApply(aa))
+                       return restContext().getJsonSchemaGenerator();
+               Value<JsonSchemaGenerator.Builder> v = Value.of(parent.copy());
+               v.get().apply(aa);
+               var bs = BasicBeanStore.of(beanStore())
+                       .addBean(Method.class, method())
+                       .addBean(JsonSchemaGenerator.Builder.class, v.get());
+               new BeanCreateMethodFinder<>(JsonSchemaGenerator.class, 
resource(), bs)
                        .find(this::matchesInjectScope)
                        .run(x -> v.get().impl(x));
-               return v.get().build().asArray();
+               return v.get().build();
        });
 
        /**
@@ -804,11 +596,107 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                return v.get().build();
        });
 
+       /** The invoker for the operation method itself. */
+       private final Memoizer<RestOpInvoker> methodInvoker = memoizer(() ->
+               new RestOpInvoker(method(), 
restContext().findRestOperationArgs(method(), opBeanStore()), 
restContext().getMethodExecStats(method()))
+       );
+
+       /** 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();
+       });
+
+       /** 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 u(treeSetCi(l));
+       });
+
        /** The optional (non-required) matchers extracted from {@link 
#matchersList}. */
        private final Memoizer<RestMatcher[]> optionalMatchers = memoizer(() -> 
matchersList.get().getOptionalEntries());
 
-       /** The required matchers extracted from {@link #matchersList}. */
-       private final Memoizer<RestMatcher[]> requiredMatchers = memoizer(() -> 
matchersList.get().getRequiredEntries());
+       /** The invokers for context-level post-call lifecycle methods. */
+       private final Memoizer<RestOpInvoker[]> postCallMethods = memoizer(() ->
+               restContext().getPostCallMethods().stream().map(x -> new 
RestOpInvoker(x, restContext().findRestOperationArgs(x, opBeanStore()), 
restContext().getMethodExecStats(x))).toArray(RestOpInvoker[]::new)
+       );
+
+       /** The invokers for context-level pre-call lifecycle methods. */
+       private final Memoizer<RestOpInvoker[]> preCallMethods = memoizer(() ->
+               restContext().getPreCallMethods().stream().map(x -> new 
RestOpInvoker(x, restContext().findRestOperationArgs(x, opBeanStore()), 
restContext().getMethodExecStats(x))).toArray(RestOpInvoker[]::new)
+       );
+
+       /**
+        * 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 final Memoizer<ParserSet> parsers = memoizer(() -> {
+               var aa = appliedAnnotations();
+               var bs = beanStore();
+               var pb = restContext().getParsersBuilder();
+               var b = pb.copy();
+               if (pb.canApply(aa))
+                       b.apply(aa);
+               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, resource(), bs)
+                       .find(this::matchesInjectScope)
+                       .run(result::set);
+               return result.get();
+       });
+
+       /** The HTTP part parser for this operation (op-level creator applied 
on top of the parent). */
+       private final Memoizer<HttpPartParser> partParser = memoizer(() -> {
+               var aa = appliedAnnotations();
+               var parent = restContext().getPartParserCreator();
+               if (!parent.canApply(aa))
+                       return restContext().getPartParser();
+               Value<HttpPartParser.Creator> v = Value.of(parent.copy());
+               v.get().apply(aa);
+               var bs = BasicBeanStore.of(beanStore())
+                       .addBean(Method.class, method())
+                       .addBean(HttpPartParser.Creator.class, v.get());
+               new BeanCreateMethodFinder<>(HttpPartParser.class, resource(), 
bs)
+                       .find(this::matchesInjectScope)
+                       .run(x -> v.get().impl(x));
+               return v.get().create();
+       });
+
+       /** The HTTP part serializer for this operation (op-level creator 
applied on top of the parent). */
+       private final Memoizer<HttpPartSerializer> partSerializer = memoizer(() 
-> {
+               var aa = appliedAnnotations();
+               var parent = restContext().getPartSerializerCreator();
+               if (!parent.canApply(aa))
+                       return restContext().getPartSerializer();
+               Value<HttpPartSerializer.Creator> v = Value.of(parent.copy());
+               v.get().apply(aa);
+               var bs = BasicBeanStore.of(beanStore())
+                       .addBean(Method.class, method())
+                       .addBean(HttpPartSerializer.Creator.class, v.get());
+               new BeanCreateMethodFinder<>(HttpPartSerializer.class, 
resource(), bs)
+                       .find(this::matchesInjectScope)
+                       .run(x -> v.get().impl(x));
+               return v.get().create();
+       });
 
        /**
         * The URL path matchers for this operation.
@@ -886,6 +774,147 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                return v.get().asArray();
        });
 
+       /** The required matchers extracted from {@link #matchersList}. */
+       private final Memoizer<RestMatcher[]> requiredMatchers = memoizer(() -> 
matchersList.get().getRequiredEntries());
+
+       /** The computed response metadata for this operation method. */
+       private final Memoizer<ResponseBeanMeta> responseMeta = memoizer(() -> 
ResponseBeanMeta.create(methodInfo(), appliedAnnotations()));
+
+       /**
+        * 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();
+       });
+
+       /**
+        * 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 final Memoizer<SerializerSet> serializers = memoizer(() -> {
+               var aa = appliedAnnotations();
+               var bs = beanStore();
+               var sb = restContext().getSerializersBuilder();
+               var b = sb.copy();
+               if (sb.canApply(aa))
+                       b.apply(aa);
+               
getRestOpAnnotationsForProperty(PROPERTY_serializers).forEach(ai -> {
+                       var c = ai.getClassArray("serializers", 
Serializer.class).orElse(null);
+                       if (nn(c) && c.length > 0)
+                               b.set(c);
+               });
+               var result = Value.of(b.build());
+               new BeanCreateMethodFinder<>(SerializerSet.class, resource(), 
bs)
+                       .find(this::matchesInjectScope)
+                       .run(result::set);
+               return result.get();
+       });
+
+       /**
+        * 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);
+       });
+
+       /**
+        * Iterates over each parameter annotation on the operation method, 
computing the parameter's
+        * {@link Schema#default_()}/{@link Schema#df()} string 
(joined-non-blank-first) and dispatching
+        * each annotation+default pair to the supplied callback.
+        */
+       private void 
processParameterDefaults(java.util.function.BiConsumer<Annotation,String> 
callback) {
+               for (var aa : method.getParameterAnnotations()) {
+                       String def = null;
+                       for (var a : aa) {
+                               if (a instanceof Schema s)
+                                       def = 
joinnlFirstNonEmptyArray(s.default_(), s.df());
+                       }
+                       if (def == null)
+                               continue;
+                       for (var a : aa)
+                               callback.accept(a, def);
+               }
+       }
+
+       /**
+        * Returns {@code true} if the given method has a {@code @RestInject} 
annotation whose
+        * {@code methodScope} includes this operation's method name (or {@code 
"*"}).
+        *
+        * <p>
+        * Used by op-level memoizers when scanning the resource class for 
{@code @RestInject}-supplied
+        * composite-bean overrides ({@code RestConverterList}, {@code 
RestGuardList}, etc.). This is the
+        * {@link RestOpContext}-scope peer of {@link 
Builder#matches(MethodInfo)} — kept in sync with that
+        * one.
+        */
+       private boolean matchesInjectScope(MethodInfo annotated) {
+               var a = 
annotated.getAnnotations(RestInject.class).findFirst().map(AnnotationInfo::inner).orElse(null);
+               if (a != null) {
+                       for (var n : a.methodScope()) {
+                               if ("*".equals(n) || method.getName().equals(n))
+                                       return true;
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * Same as {@link #matchesInjectScope(MethodInfo)} but additionally 
requires the
+        * {@link RestInject#name()} attribute to equal {@code beanName}.
+        *
+        * <p>
+        * Used for named composite-bean overrides ({@code 
defaultRequestHeaders}, {@code defaultResponseHeaders},
+        * etc.) where the bean type alone is ambiguous (e.g. {@link 
HeaderList} appears as both request and
+        * response headers).
+        */
+       private boolean matchesInjectScope(MethodInfo annotated, String 
beanName) {
+               var a = 
annotated.getAnnotations(RestInject.class).findFirst().map(AnnotationInfo::inner).orElse(null);
+               if (a != null) {
+                       if (! a.name().equals(beanName))
+                               return false;
+                       for (var n : a.methodScope()) {
+                               if ("*".equals(n) || method.getName().equals(n))
+                                       return true;
+                       }
+               }
+               return false;
+       }
+
        private Stream<String> resolveCdl(String...values) {
                if (values == null || values.length == 0)
                        return Stream.empty();
@@ -1080,7 +1109,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                        mi = MethodInfo.of(method).accessible();
 
                        // @formatter:off
-                       var bs = 
BasicBeanStore.of(context.getBootstrapBeanStore())
+                       var bs = opBeanStore = 
BasicBeanStore.of(context.getBootstrapBeanStore())
                                .addBean(RestOpContext.class, this)
                                .addBean(Method.class, method)
                                .addBean(AnnotationWorkList.class, 
appliedAnnotations);
@@ -1105,19 +1134,6 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                        bs.add(UrlPathMatcher[].class, pm);
                        bs.addBean(UrlPathMatcher.class, pm.length > 0 ? pm[0] 
: null);
 
-                       int hierarchyDepthTemp = 0;
-                       var sc = method.getDeclaringClass().getSuperclass();
-                       while (nn(sc)) {
-                               hierarchyDepthTemp++;
-                               sc = sc.getSuperclass();
-                       }
-                       hierarchyDepth = hierarchyDepthTemp;
-
-                       responseMeta = ResponseBeanMeta.create(mi, 
appliedAnnotations);
-
-                       preCallMethods = 
context.getPreCallMethods().stream().map(x -> new RestOpInvoker(x, 
context.findRestOperationArgs(x, bs), 
context.getMethodExecStats(x))).toArray(RestOpInvoker[]::new);
-                       postCallMethods = 
context.getPostCallMethods().stream().map(x -> new RestOpInvoker(x, 
context.findRestOperationArgs(x, bs), 
context.getMethodExecStats(x))).toArray(RestOpInvoker[]::new);
-                       methodInvoker = new RestOpInvoker(method, 
context.findRestOperationArgs(method, bs), context.getMethodExecStats(method));
                } catch (Exception e) {
                        throw new ServletException(e);
                }
@@ -1139,7 +1155,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                                return c;
                }
 
-               c = cmp(o.hierarchyDepth, hierarchyDepth);
+               c = cmp(o.hierarchyDepth.get(), hierarchyDepth.get());
                if (c != 0)
                        return c;
 
@@ -1430,7 +1446,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
         *
         * @return The response bean meta or <jk>null</jk> if it's not a {@link 
Response}-annotated bean.
         */
-       public ResponseBeanMeta getResponseMeta() { return responseMeta; }
+       public ResponseBeanMeta getResponseMeta() { return responseMeta.get(); }
 
        /**
         * Returns the serializers to use for this method.
@@ -1534,11 +1550,11 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
 
        RestGuard[] getGuards() { return guards.get(); }
 
-       RestOpInvoker getMethodInvoker() { return methodInvoker; }
+       RestOpInvoker getMethodInvoker() { return methodInvoker.get(); }
 
-       RestOpInvoker[] getPostCallMethods() { return postCallMethods; }
+       RestOpInvoker[] getPostCallMethods() { return postCallMethods.get(); }
 
-       RestOpInvoker[] getPreCallMethods() { return preCallMethods; }
+       RestOpInvoker[] getPreCallMethods() { return preCallMethods.get(); }
        
        private static String joinnlFirstNonEmptyArray(String[]...s) {
                for (var ss : s)
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOperations.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOperations.java
index d2b9bfc46e..54961a8eca 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOperations.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOperations.java
@@ -28,7 +28,6 @@ import org.apache.juneau.rest.annotation.*;
 
 /**
  * Encapsulates the set of {@link RestOp}-annotated methods within a single 
{@link Rest}-annotated object.
- *
  */
 public class RestOperations {
 

Reply via email to