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 9b0210e800 Refactor RestContext memoized initialization for headers,
encoders, children, and operations
9b0210e800 is described below
commit 9b0210e80019d2d2863842fc38b8a47c19e81845
Author: James Bognar <[email protected]>
AuthorDate: Tue May 5 08:12:04 2026 -0400
Refactor RestContext memoized initialization for headers, encoders,
children, and operations
---
.../java/org/apache/juneau/rest/RestContext.java | 720 ++++++---------------
1 file changed, 208 insertions(+), 512 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 76f8e7f6a5..1f470f8f1e 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
@@ -45,7 +45,6 @@ import java.util.function.*;
import java.util.logging.*;
import java.util.stream.*;
-import org.apache.http.Header;
import org.apache.juneau.*;
import org.apache.juneau.bean.swagger.Swagger;
import org.apache.juneau.commons.collections.FluentMap;
@@ -130,6 +129,7 @@ import jakarta.servlet.http.*;
* </ul>
*/
@SuppressWarnings({
+ "java:S112", // RuntimeException used in memoizer lambdas to re-wrap
checked exceptions (ServletException/Exception) that Supplier<T> cannot declare
"java:S115", // Constants use UPPER_snakeCase convention (e.g.,
PROP_allowContentParam)
"java:S1200", // Class has many dependencies; acceptable for this core
context class
"java:S6539", // Monster class; RestContext is intentionally a central
hub for REST framework configuration
@@ -155,11 +155,6 @@ public class RestContext extends Context {
private static final String PROP_staticFiles = "staticFiles";
private static final String PROP_swaggerProvider = "swaggerProvider";
- // Argument name constants for assertArgNotNull
- private static final String ARG_values = "values";
- private static final String ARG_resource = "resource";
- private static final String ARG_restContext = "restContext";
-
/**
* Bootstrap arguments for {@link RestContext}.
*
@@ -208,7 +203,7 @@ public class RestContext extends Context {
*/
public Args {
assertArgNotNull("resourceClass", resourceClass);
- assertArgNotNull(ARG_resource, resource);
+ assertArgNotNull("resource", resource);
if (path == null)
path = "";
if (beanStoreConfigurer == null)
@@ -270,9 +265,6 @@ public class RestContext extends Context {
);
private static final Set<String> DELAYED_INJECTION_NAMES = set(
- PROP_defaultRequestAttributes,
- PROP_defaultRequestHeaders,
- PROP_defaultResponseHeaders,
PROP_bootstrapVarResolver,
"destroyMethods",
"endCallMethods",
@@ -301,33 +293,25 @@ public class RestContext extends Context {
return isRestInjectMethod(mi, null);
}
- private static boolean isRestInjectMethod(MethodInfo mi, String name) {
- return mi.getAnnotations(RestInject.class)
- .map(AnnotationInfo::inner)
- .anyMatch(x -> nn(x) && x.methodScope().length == 0 &&
(n(name) || eq(x.name(), name)));
- }
+ private static boolean isRestInjectMethod(MethodInfo mi, String
name) {
+ return mi.getAnnotations(RestInject.class)
+ .map(AnnotationInfo::inner)
+ .anyMatch(x -> nn(x) && x.methodScope().length
== 0 && (n(name) || eq(x.name(), name)));
+ }
private BeanContext.Builder beanContext;
private BasicBeanStore beanStore;
private BasicBeanStore bootstrapBeanStore;
private final Class<?> resourceClass;
private Config config;
- private EncoderSet.Builder encoders;
- private HeaderList defaultRequestHeaders;
- private HeaderList defaultResponseHeaders;
private HttpPartParser.Creator partParser;
private HttpPartSerializer.Creator partSerializer;
private JsonSchemaGenerator.Builder jsonSchemaGenerator;
private List<Object> children = list();
- private NamedAttributeMap defaultRequestAttributes;
- private ParserSet.Builder parsers;
private final RestContext parentContext;
- private RestChildren.Builder restChildren;
private RestOpArgList.Builder restOpArgs;
- private RestOperations.Builder restOperations;
private ResponseProcessorList.Builder responseProcessors;
private ResourceSupplier resource;
- private SerializerSet.Builder serializers;
private final ServletConfig inner;
private String path = null;
private VarResolver bootstrapVarResolver;
@@ -363,9 +347,6 @@ public class RestContext extends Context {
throw new NoSuchMethodError("Not implemented.");
}
-
-
-
/**
* Returns the bean context sub-builder.
*
@@ -494,7 +475,7 @@ public class RestContext extends Context {
* @return This object.
*/
public Builder children(Object...values) {
- assertArgNoNulls(ARG_values, values);
+ assertArgNoNulls("values", values);
addAll(children, values);
return this;
}
@@ -536,194 +517,6 @@ public class RestContext extends Context {
return config;
}
- /**
- * Returns the default request attributes sub-builder.
- *
- * @return The default request attributes sub-builder.
- */
- public NamedAttributeMap defaultRequestAttributes() {
- if (defaultRequestAttributes == null)
- defaultRequestAttributes =
createDefaultRequestAttributes(beanStore(), resource());
- return defaultRequestAttributes;
- }
-
- /**
- * Default request attributes.
- *
- * <p>
- * Specifies default values for request attributes if they're
not already set on the request.
- *
- * Affects values returned by the following methods:
- * <ul>
- * <li class='jm'>{@link RestRequest#getAttribute(String)}.
- * <li class='jm'>{@link RestRequest#getAttributes()}.
- * </ul>
- *
- * <h5 class='section'>Example:</h5>
- * <p class='bjava'>
- * <jc>// Defined via annotation (config-file substitution
supported).</jc>
- *
<ja>@Rest</ja>(defaultRequestAttributes={<js>"Foo=bar"</js>, <js>"Baz:
$C{REST/myAttributeValue}"</js>})
- * <jk>public class</jk> MyResource {
- *
- * <jc>// Override at the method level.</jc>
- *
<ja>@RestGet</ja>(defaultRequestAttributes={<js>"Foo: bar"</js>})
- * <jk>public</jk> Object myMethod() {...}
- * }
- * </p>
- *
- * <h5 class='section'>Notes:</h5><ul>
- * <li class='note'>Use {@link
BasicNamedAttribute#of(String, Supplier)} to provide a dynamically changeable
attribute value.
- * </ul>
- *
- * @param values The attributes.
- * <br>Cannot contain <jk>null</jk> values.
- * @return This object.
- */
- public Builder
defaultRequestAttributes(NamedAttribute...values) {
- assertArgNoNulls(ARG_values, values);
- defaultRequestAttributes().add(values);
- return this;
- }
-
- /**
- * Returns the default request headers.
- *
- * @return The default request headers.
- */
- public HeaderList defaultRequestHeaders() {
- if (defaultRequestHeaders == null)
- defaultRequestHeaders =
createDefaultRequestHeaders(beanStore(), resource());
- return defaultRequestHeaders;
- }
-
- /**
- * Default request headers.
- *
- * <p>
- * Specifies default values for request headers if they're not
passed in through the request.
- *
- * <h5 class='section'>Notes:</h5><ul>
- * <li class='note'>
- * Affects values returned by {@link
RestRequest#getHeader(String)} when the header is not present on the request.
- * <li class='note'>
- * The most useful reason for this annotation is
to provide a default <c>Accept</c> header when one is not
- * specified so that a particular default {@link
Serializer} is picked.
- * </ul>
- *
- * <h5 class='section'>Example:</h5>
- * <p class='bjava'>
- * <jc>// Defined via annotation (config-file substitution
supported).</jc>
- * <ja>@Rest</ja>(defaultRequestHeaders={<js>"Accept:
application/json"</js>, <js>"My-Header=$C{REST/myHeaderValue}"</js>})
- * <jk>public class</jk> MyResource {
- *
- * <jc>// Override at the method level.</jc>
- *
<ja>@RestGet</ja>(defaultRequestHeaders={<js>"Accept: text/xml"</js>})
- * <jk>public</jk> Object myMethod() {...}
- * }
- * </p>
- *
- * <h5 class='section'>See Also:</h5><ul>
- * <li class='ja'>{@link Rest#defaultRequestHeaders}
- * <li class='ja'>{@link RestOp#defaultRequestHeaders}
- * <li class='ja'>{@link RestGet#defaultRequestHeaders}
- * <li class='ja'>{@link RestPut#defaultRequestHeaders}
- * <li class='ja'>{@link RestPost#defaultRequestHeaders}
- * <li class='ja'>{@link RestDelete#defaultRequestHeaders}
- * </ul>
- *
- * @param values The headers to add.
- * <br>Cannot contain <jk>null</jk> values.
- * @return This object.
- */
- public Builder defaultRequestHeaders(Header...values) {
- assertArgNoNulls(ARG_values, values);
- defaultRequestHeaders().setDefault(values);
- return this;
- }
-
- /**
- * Returns the default response headers.
- *
- * @return The default response headers.
- */
- public HeaderList defaultResponseHeaders() {
- if (defaultResponseHeaders == null)
- defaultResponseHeaders =
createDefaultResponseHeaders(beanStore(), resource());
- return defaultResponseHeaders;
- }
-
- /**
- * Default response headers.
- *
- * <p>
- * Specifies default values for response headers if they're not
set after the Java REST method is called.
- *
- * <h5 class='section'>Notes:</h5><ul>
- * <li class='note'>
- * This is equivalent to calling {@link
RestResponse#setHeader(String, String)} programmatically in each of
- * the Java methods.
- * <li class='note'>
- * The header value will not be set if the header
value has already been specified (hence the 'default' in the name).
- * </ul>
- *
- * <h5 class='section'>Example:</h5>
- * <p class='bjava'>
- * <jc>// Defined via annotation (config-file substitution
supported).</jc>
- *
<ja>@Rest</ja>(defaultResponseHeaders={<js>"Content-Type:
$C{REST/defaultContentType,text/plain}"</js>,<js>"My-Header:
$C{REST/myHeaderValue}"</js>})
- * <jk>public class</jk> MyResource { ... }
- * </p>
- *
- * <h5 class='section'>See Also:</h5><ul>
- * <li class='ja'>{@link Rest#defaultResponseHeaders}
- * <li class='ja'>{@link RestOp#defaultResponseHeaders}
- * <li class='ja'>{@link RestGet#defaultResponseHeaders}
- * <li class='ja'>{@link RestPut#defaultResponseHeaders}
- * <li class='ja'>{@link RestPost#defaultResponseHeaders}
- * <li class='ja'>{@link RestDelete#defaultResponseHeaders}
- * </ul>
- *
- * @param values The headers to add.
- * <br>Cannot contain <jk>null</jk> values.
- * @return This object.
- */
- public Builder defaultResponseHeaders(Header...values) {
- assertArgNoNulls(ARG_values, values);
- defaultResponseHeaders().setDefault(values);
- return this;
- }
-
- /**
- * Returns the encoder group sub-builder.
- *
- * <p>
- * Encoders are used to decode HTTP requests and encode HTTP
responses based on {@code Content-Encoding} and {@code Accept-Encoding}
- * headers.
- *
- * <p>
- * The default encoder set has support for identity incoding
only.
- * It can be overridden via any of the following:
- * <ul class='spaced-list'>
- * <li>Injected via bean store.
- * <li>Class annotation: {@link Rest#encoders()
@Rest(encoders)}
- * <li>{@link RestInject @RestInject}-annotated method:
- * <p class='bjava'>
- * <ja>@RestInject</ja> <jk>public</jk> [<jk>static</jk>]
EncoderSet myMethod(<i><args></i>) {...}
- * </p>
- * Args can be any injected bean including
EncoderSet.Builder, the default builder.
- * </ul>
- *
- * <h5 class='section'>See Also:</h5><ul>
- * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/RestServerEncoders">Encoders</a>
- * </ul>
- *
- * @return The builder for the {@link EncoderSet} object in the
REST context.
- */
- public EncoderSet.Builder encoders() {
- if (encoders == null)
- encoders = createEncoders(beanStore(),
resource());
- return encoders;
- }
-
@Override /* Overridden from ServletConfig */
public String getInitParameter(String name) {
return inner == null ? null :
inner.getInitParameter(name);
@@ -748,7 +541,7 @@ public class RestContext extends Context {
private Builder init(Supplier<?> resource) throws
ServletException {
- this.resource = new ResourceSupplier(resourceClass,
assertArgNotNull(ARG_resource, resource));
+ this.resource = new ResourceSupplier(resourceClass,
assertArgNotNull("resource", resource));
var r = this.resource;
var rc = resourceClass;
@@ -859,37 +652,6 @@ public class RestContext extends Context {
return jsonSchemaGenerator;
}
- /**
- * Returns the parser group sub-builder.
- *
- * <p>
- * Parsers are used to HTTP request bodies into POJOs based on
the {@code Content-Type} header.
- *
- * <p>
- * The default parser set is empty.
- * It can be overridden via any of the following:
- * <ul class='spaced-list'>
- * <li>Injected via bean store.
- * <li>Class annotation: {@link Rest#parsers()
@Rest(parsers)}
- * <li>{@link RestInject @RestInject}-annotated method:
- * <p class='bjava'>
- * <ja>@RestInject</ja> <jk>public</jk> [<jk>static</jk>]
ParserSet myMethod(<i><args></i>) {...}
- * </p>
- * Args can be any injected bean including
ParserSet.Builder, the default builder.
- * </ul>
- *
- * <h5 class='section'>See Also:</h5><ul>
- * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/Marshalling">Marshalling</a>
- * </ul>
- *
- * @return The parser group sub-builder.
- */
- public ParserSet.Builder parsers() {
- if (parsers == null)
- parsers = createParsers(beanStore(), resource
!= null ? resource.get() : null);
- return parsers;
- }
-
/**
* Returns the part parser sub-builder.
*
@@ -1111,20 +873,6 @@ public class RestContext extends Context {
return responseProcessors;
}
- /**
- * Returns the REST children list.
- *
- * @param restContext The rest context.
- * <br>Can be <jk>null</jk> if the bean is a top-level
resource.
- * @return The REST children list.
- * @throws Exception If a problem occurred instantiating one of
the child rest contexts.
- */
- public RestChildren.Builder restChildren(RestContext
restContext) throws Exception {
- if (restChildren == null)
- restChildren = createRestChildren(beanStore(),
resource(), restContext);
- return restChildren;
- }
-
/**
* Returns the REST operation args sub-builder.
*
@@ -1136,20 +884,6 @@ public class RestContext extends Context {
return restOpArgs;
}
- /**
- * Returns the REST operations list.
- *
- * @param restContext The rest context.
- * <br>Cannot be <jk>null</jk>.
- * @return The REST operations list.
- * @throws ServletException If a problem occurred instantiating
one of the child rest contexts.
- */
- public RestOperations.Builder restOperations(RestContext
restContext) throws ServletException {
- if (restOperations == null)
- restOperations =
createRestOperations(beanStore(), resource(), assertArgNotNull(ARG_restContext,
restContext));
- return restOperations;
- }
-
/**
* Returns the bootstrap bean store.
*
@@ -1163,37 +897,6 @@ public class RestContext extends Context {
return bootstrapBeanStore;
}
- /**
- * Returns the serializer group sub-builder.
- *
- * <p>
- * Serializers are used to convert POJOs to HTTP response
bodies based on the {@code Accept} header.
- *
- * <p>
- * The default serializer set is empty.
- * It can be overridden via any of the following:
- * <ul class='spaced-list'>
- * <li>Injected via bean store.
- * <li>Class annotation: {@link Rest#serializers()
@Rest(serializers)}
- * <li>{@link RestInject @RestInject}-annotated method:
- * <p class='bjava'>
- * <ja>@RestInject</ja> <jk>public</jk> [<jk>static</jk>]
SerializerSet myMethod(<i><args></i>) {...}
- * </p>
- * Args can be any injected bean including
SerializerSet.Builder, the default builder.
- * </ul>
- *
- * <h5 class='section'>See Also:</h5><ul>
- * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/Marshalling">Marshalling</a>
- * </ul>
- *
- * @return The serializer group sub-builder.
- */
- public SerializerSet.Builder serializers() {
- if (serializers == null)
- serializers = createSerializers(beanStore(),
resource != null ? resource.get() : null);
- return serializers;
- }
-
/**
* Returns the bootstrap (pre-runtime) variable resolver for
this REST context.
*
@@ -1364,74 +1067,6 @@ public class RestContext extends Context {
return v.get();
}
- /**
- * Instantiates the default request attributes sub-builder.
- *
- * @param beanStore
- * The factory used for creating beans and retrieving
injected beans.
- * @param resource
- * The REST servlet/bean instance that this context is
defined against.
- * @return A new default request attributes sub-builder.
- */
- protected NamedAttributeMap
createDefaultRequestAttributes(BasicBeanStore beanStore, Supplier<?> resource) {
-
- // Default value.
- var v = Value.of(NamedAttributeMap.create());
-
- beanStore.getBean(NamedAttributeMap.class,
PROP_defaultRequestAttributes).ifPresent(v::set);
-
- // Replace with bean from:
@RestInject(name="defaultRequestAttributes") public [static] NamedAttributeMap
xxx(<args>)
- new BeanCreateMethodFinder<>(NamedAttributeMap.class,
resource.get(), beanStore).addBean(NamedAttributeMap.class, v.get()).find(x ->
isRestInjectMethod(x, PROP_defaultRequestAttributes)).run(v::set);
-
- return v.get();
- }
-
- /**
- * Instantiates the default request headers sub-builder.
- *
- * @param beanStore
- * The factory used for creating beans and retrieving
injected beans.
- * @param resource
- * The REST servlet/bean instance that this context is
defined against.
- * @return A new default request headers sub-builder.
- */
- protected HeaderList createDefaultRequestHeaders(BasicBeanStore
beanStore, Supplier<?> resource) {
-
- // Default value.
- var v = Value.of(HeaderList.create());
-
- // Replace with bean from bean store.
- beanStore.getBean(HeaderList.class,
PROP_defaultRequestHeaders).ifPresent(v::set);
-
- // Replace with bean from:
@RestInject(name="defaultRequestHeaders") public [static] HeaderList xxx(<args>)
- new BeanCreateMethodFinder<>(HeaderList.class,
resource.get(), beanStore).addBean(HeaderList.class, v.get()).find(x ->
isRestInjectMethod(x, PROP_defaultRequestHeaders)).run(v::set);
-
- return v.get();
- }
-
- /**
- * Instantiates the default response headers sub-builder.
- *
- * @param beanStore
- * The factory used for creating beans and retrieving
injected beans.
- * @param resource
- * The REST servlet/bean instance that this context is
defined against.
- * @return A new default response headers sub-builder.
- */
- protected HeaderList
createDefaultResponseHeaders(BasicBeanStore beanStore, Supplier<?> resource) {
-
- // Default value.
- var v = Value.of(HeaderList.create());
-
- // Replace with bean from bean store.
- beanStore.getBean(HeaderList.class,
PROP_defaultResponseHeaders).ifPresent(v::set);
-
- // Replace with bean from:
@RestInject(name="defaultResponseHeaders") public [static] HeaderList
xxx(<args>)
- new BeanCreateMethodFinder<>(HeaderList.class,
resource.get(), beanStore).addBean(HeaderList.class, v.get()).find(x ->
isRestInjectMethod(x, PROP_defaultResponseHeaders)).run(v::set);
-
- return v.get();
- }
-
/**
* Instantiates the destroy method list.
*
@@ -1452,38 +1087,6 @@ public class RestContext extends Context {
return v.get();
}
- /**
- * Instantiates the encoder group sub-builder.
- *
- * <h5 class='section'>See Also:</h5><ul>
- * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/RestServerEncoders">Encoders</a>
- * <li class='jm'>{@link #encoders()}
- * </ul>
- *
- * @param resource
- * The REST servlet/bean instance that this context is
defined against.
- * @param beanStore
- * The factory used for creating beans and retrieving
injected beans.
- * <br>Created during context bootstrap.
- * @return A new encoder group sub-builder.
- */
- protected EncoderSet.Builder createEncoders(BasicBeanStore
beanStore, Supplier<?> resource) {
-
- // Default value.
- Value<EncoderSet.Builder> v =
Value.of(EncoderSet.create(beanStore).add(IdentityEncoder.INSTANCE));
-
- // Specify the implementation class if its set as a
default.
- beanStore.getBeanType(EncoderSet.class).ifPresent(x ->
v.get().type(x));
-
- // Replace with bean from bean store.
- beanStore.getBean(EncoderSet.class).ifPresent(x ->
v.get().impl(x));
-
- // Replace with bean from: @RestInject public [static]
EncoderSet xxx(<args>)
- new BeanCreateMethodFinder<>(EncoderSet.class,
resource.get(), beanStore).addBean(EncoderSet.Builder.class,
v.get()).find(Builder::isRestInjectMethod).run(x -> v.get().impl(x));
-
- return v.get();
- }
-
/**
* Instantiates the end call method list.
*
@@ -1535,39 +1138,6 @@ public class RestContext extends Context {
}
- /**
- * Instantiates the parser group sub-builder.
- *
- * <h5 class='section'>See Also:</h5><ul>
- * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/Marshalling">Marshalling</a>
- * </ul>
- *
- * @param beanStore
- * The factory used for creating beans and retrieving
injected beans.
- * <br>Created during context bootstrap.
- * @param resourceInstance
- * The REST servlet/bean instance that this context is
defined against.
- * <br>Can be <jk>null</jk> when <jk>init</jk> has not
been called yet.
- * @return A new parser group sub-builder.
- */
- protected ParserSet.Builder createParsers(BasicBeanStore
beanStore, Object resourceInstance) {
-
- // Default value.
- Value<ParserSet.Builder> v =
Value.of(ParserSet.create(beanStore));
-
- // Specify the implementation class if its set as a
default.
- beanStore.getBeanType(ParserSet.class).ifPresent(x ->
v.get().type(x));
-
- // Replace with bean from bean store.
- beanStore.getBean(ParserSet.class).ifPresent(x ->
v.get().impl(x));
-
- // Replace with bean from: @RestInject public [static]
ParserSet xxx(<args>)
- if (resourceInstance != null)
- new BeanCreateMethodFinder<>(ParserSet.class,
resourceInstance, beanStore).addBean(ParserSet.Builder.class,
v.get()).find(Builder::isRestInjectMethod).run(x -> v.get().impl(x));
-
- return v.get();
- }
-
/**
* Instantiates the part parser sub-builder.
*
@@ -1964,39 +1534,6 @@ public class RestContext extends Context {
return v.get();
}
- /**
- * Instantiates the serializer group sub-builder.
- *
- * <h5 class='section'>See Also:</h5><ul>
- * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/Marshalling">Marshalling</a>
- * </ul>
- *
- * @param beanStore
- * The factory used for creating beans and retrieving
injected beans.
- * <br>Created during context bootstrap.
- * @param resourceInstance
- * The REST servlet/bean instance that this context is
defined against.
- * <br>Can be <jk>null</jk> when <jk>init</jk> has not
been called yet.
- * @return A new serializer group sub-builder.
- */
- protected SerializerSet.Builder
createSerializers(BasicBeanStore beanStore, Object resourceInstance) {
-
- // Default value.
- Value<SerializerSet.Builder> v =
Value.of(SerializerSet.create(beanStore));
-
- // Specify the implementation class if its set as a
default.
- beanStore.getBeanType(SerializerSet.class).ifPresent(x
-> v.get().type(x));
-
- // Replace with bean from bean store.
- beanStore.getBean(SerializerSet.class).ifPresent(x ->
v.get().impl(x));
-
- // Replace with bean from: @RestInject public [static]
SerializerSet xxx(<args>)
- if (resourceInstance != null)
- new
BeanCreateMethodFinder<>(SerializerSet.class, resourceInstance,
beanStore).addBean(SerializerSet.Builder.class,
v.get()).find(Builder::isRestInjectMethod).run(x -> v.get().impl(x));
-
- return v.get();
- }
-
/**
* Instantiates the start call method list.
*
@@ -2106,17 +1643,7 @@ public class RestContext extends Context {
public void apply(AnnotationInfo<Rest> ai, Builder b) {
Rest a = ai.inner();
- classes(a.serializers()).ifPresent(x ->
b.serializers().add(x));
- classes(a.parsers()).ifPresent(x -> b.parsers().add(x));
- type(a.partSerializer()).ifPresent(x ->
b.partSerializer().type(x));
- type(a.partParser()).ifPresent(x ->
b.partParser().type(x));
-
stream(a.defaultRequestAttributes()).map(BasicNamedAttribute::ofPair).forEach(b::defaultRequestAttributes);
-
stream(a.defaultRequestHeaders()).map(org.apache.juneau.http.HttpHeaders::stringHeader).forEach(b::defaultRequestHeaders);
-
stream(a.defaultResponseHeaders()).map(org.apache.juneau.http.HttpHeaders::stringHeader).forEach(b::defaultResponseHeaders);
-
string(a.defaultAccept()).map(org.apache.juneau.http.HttpHeaders::accept).ifPresent(b::defaultRequestHeaders);
-
string(a.defaultContentType()).map(org.apache.juneau.http.HttpHeaders::contentType).ifPresent(b::defaultRequestHeaders);
- b.children((java.lang.Object[])a.children());
- classes(a.encoders()).ifPresent(x ->
b.encoders().add(x));
+ b.children((java.lang.Object[])a.children());
string(a.path()).ifPresent(b::path);
}
}
@@ -2146,9 +1673,7 @@ public class RestContext extends Context {
protected final Class<?> resourceClass;
protected final ConcurrentHashMap<Locale,Swagger> swaggerCache = new
ConcurrentHashMap<>();
protected final Instant startTime;
- protected final RestChildren restChildren;
protected final RestContext parentContext;
- protected final RestOperations restOperations;
protected final String fullPath;
protected final String path;
protected final ThreadLocal<RestSession> localSession = new
ThreadLocal<>();
@@ -2161,7 +1686,7 @@ public class RestContext extends Context {
private Supplier<?> resource() { return resource; }
private Class<?> resourceClass() { return resourceClass; }
private RestContext parentContext() { return parentContext; }
- private RestOperations restOperations() { return restOperations; }
+ private RestOperations restOperations() { return restOperations.get(); }
private static final class LifecycleInvokerPair {
final MethodList methods;
@@ -2288,18 +1813,70 @@ public class RestContext extends Context {
/**
* The default request attributes contributed by {@code
@Rest(defaultRequestAttributes)} for this resource.
+ *
+ * <p>
+ * Walks {@code @Rest} annotations parent-to-child, resolving each
attribute string and parsing it as a
+ * key=value or key:value pair. A named bean-store override or {@code
@RestInject} factory method REPLACES
+ * the accumulated result.
*/
- private final Memoizer<NamedAttributeMap> defaultRequestAttributes =
memoizer(() -> builder().defaultRequestAttributes());
+ private final Memoizer<NamedAttributeMap> defaultRequestAttributes =
memoizer(() -> {
+ var v = Value.of(NamedAttributeMap.create());
+ var anns = new ArrayList<>(getRestAnnotations());
+ Collections.reverse(anns);
+ anns.forEach(ai ->
Arrays.stream(ai.inner().defaultRequestAttributes())
+ .filter(StringUtils::isNotBlank)
+ .map(this::resolve)
+ .filter(StringUtils::isNotBlank)
+ .map(BasicNamedAttribute::ofPair)
+ .forEach(v.get()::add));
+ beanStore().getBean(NamedAttributeMap.class,
PROP_defaultRequestAttributes).ifPresent(v::set);
+ new BeanCreateMethodFinder<>(NamedAttributeMap.class,
resource().get(), beanStore()).addBean(NamedAttributeMap.class, v.get()).find(x
-> Builder.isRestInjectMethod(x, PROP_defaultRequestAttributes)).run(v::set);
+ return v.get();
+ });
/**
- * The default request headers contributed by {@code
@Rest(defaultRequestHeaders)} for this resource.
+ * The default request headers contributed by {@code
@Rest(defaultRequestHeaders)},
+ * {@code @Rest(defaultAccept)}, and {@code @Rest(defaultContentType)}
for this resource.
+ *
+ * <p>
+ * Walks {@code @Rest} annotations parent-to-child, resolving each
header string. A named bean-store
+ * override or {@code @RestInject} factory method REPLACES the
accumulated result.
*/
- private final Memoizer<HeaderList> defaultRequestHeaders = memoizer(()
-> builder().defaultRequestHeaders());
+ private final Memoizer<HeaderList> defaultRequestHeaders = memoizer(()
-> {
+ var v = Value.of(HeaderList.create());
+ var anns = new ArrayList<>(getRestAnnotations());
+ Collections.reverse(anns);
+ anns.forEach(ai -> {
+ Rest a = ai.inner();
+
Arrays.stream(a.defaultRequestHeaders()).filter(StringUtils::isNotBlank).map(this::resolve).filter(StringUtils::isNotBlank).map(s
-> stringHeader(s)).forEach(v.get()::setDefault);
+ var defaultAccept = resolve(a.defaultAccept());
+ if (isNotBlank(defaultAccept))
+ v.get().setDefault(accept(defaultAccept));
+ var defaultContentType =
resolve(a.defaultContentType());
+ if (isNotBlank(defaultContentType))
+
v.get().setDefault(contentType(defaultContentType));
+ });
+ beanStore().getBean(HeaderList.class,
PROP_defaultRequestHeaders).ifPresent(v::set);
+ new BeanCreateMethodFinder<>(HeaderList.class,
resource().get(), beanStore()).addBean(HeaderList.class, v.get()).find(x ->
Builder.isRestInjectMethod(x, PROP_defaultRequestHeaders)).run(v::set);
+ return v.get();
+ });
/**
* The default response headers contributed by {@code
@Rest(defaultResponseHeaders)} for this resource.
+ *
+ * <p>
+ * Walks {@code @Rest} annotations parent-to-child, resolving each
header string. A named bean-store
+ * override or {@code @RestInject} factory method REPLACES the
accumulated result.
*/
- private final Memoizer<HeaderList> defaultResponseHeaders = memoizer(()
-> builder().defaultResponseHeaders());
+ private final Memoizer<HeaderList> defaultResponseHeaders = memoizer(()
-> {
+ var v = Value.of(HeaderList.create());
+ var anns = new ArrayList<>(getRestAnnotations());
+ Collections.reverse(anns);
+ anns.forEach(ai ->
Arrays.stream(ai.inner().defaultResponseHeaders()).filter(StringUtils::isNotBlank).map(this::resolve).filter(StringUtils::isNotBlank).map(s
-> stringHeader(s)).forEach(v.get()::setDefault));
+ beanStore().getBean(HeaderList.class,
PROP_defaultResponseHeaders).ifPresent(v::set);
+ new BeanCreateMethodFinder<>(HeaderList.class,
resource().get(), beanStore()).addBean(HeaderList.class, v.get()).find(x ->
Builder.isRestInjectMethod(x, PROP_defaultResponseHeaders)).run(v::set);
+ return v.get();
+ });
/**
* Methods annotated with {@link
org.apache.juneau.rest.annotation.RestDestroy @RestDestroy} and their invokers.
@@ -2307,9 +1884,29 @@ public class RestContext extends Context {
private final Memoizer<LifecycleInvokerPair> destroyInvokerPair =
memoizer(() -> buildLifecycleInvokerPair(() ->
builder().createDestroyMethods(beanStore(), resource())));
/**
- * The {@link EncoderSet} for this resource, built from the builder's
encoder sub-builder.
+ * Fully-configured {@link EncoderSet.Builder} for this resource,
populated from the
+ * {@code @Rest(encoders)} annotation chain and any {@code @RestInject}
override.
+ *
+ * <p>
+ * Starts with {@link IdentityEncoder} as the implicit default. A
bean-store type override
+ * or bean-store instance override REPLACES the builder or its impl.
Annotation entries
+ * (parent-to-child) are appended after the default. A {@code
@RestInject} factory method
+ * REPLACES the impl with the returned value.
+ */
+ private final Memoizer<EncoderSet.Builder> encodersBuilder =
memoizer(() -> {
+ var bs = beanStore();
+ var v = Value.of(EncoderSet.create(bs));
+ bs.getBeanType(EncoderSet.class).ifPresent(x ->
v.get().type(x));
+ bs.getBean(EncoderSet.class).ifPresent(x -> v.get().impl(x));
+ getRestAnnotationsForProperty(PROPERTY_encoders).forEach(ai ->
v.get().add(ai.inner().encoders()));
+ new BeanCreateMethodFinder<>(EncoderSet.class,
resource().get(), bs).addBean(EncoderSet.Builder.class,
v.get()).find(Builder::isRestInjectMethod).run(x -> v.get().impl(x));
+ return v.get();
+ });
+
+ /**
+ * The {@link EncoderSet} for this resource, built from the
self-contained encoder builder memoizer.
*/
- private final Memoizer<EncoderSet> encoders = memoizer(() ->
builder().encoders().build());
+ private final Memoizer<EncoderSet> encoders = memoizer(() ->
encodersBuilder.get().build());
/**
* Methods annotated with {@link
org.apache.juneau.rest.annotation.RestEndCall @RestEndCall} and their invokers.
@@ -2373,19 +1970,63 @@ public class RestContext extends Context {
});
/**
- * The {@link ParserSet} for this resource, built from the builder's
parser sub-builder.
+ * Fully-configured {@link ParserSet.Builder} for this resource,
populated from the
+ * {@code @Rest(parsers)} annotation chain and any {@code @RestInject}
override.
+ *
+ * <p>
+ * Starts with an empty set. A bean-store type or instance override
REPLACES the builder or its impl.
+ * Annotation entries (parent-to-child) are appended. A {@code
@RestInject} factory method REPLACES the impl.
*/
- private final Memoizer<ParserSet> parsers = memoizer(() ->
builder().parsers().build());
+ private final Memoizer<ParserSet.Builder> parsersBuilder = memoizer(()
-> {
+ var bs = beanStore();
+ var v = Value.of(ParserSet.create(bs));
+ bs.getBeanType(ParserSet.class).ifPresent(x -> v.get().type(x));
+ bs.getBean(ParserSet.class).ifPresent(x -> v.get().impl(x));
+ getRestAnnotationsForProperty(PROPERTY_parsers).forEach(ai ->
v.get().add(ai.inner().parsers()));
+ new BeanCreateMethodFinder<>(ParserSet.class, resource().get(),
bs).addBean(ParserSet.Builder.class,
v.get()).find(Builder::isRestInjectMethod).run(x -> v.get().impl(x));
+ return v.get();
+ });
/**
- * The {@link HttpPartParser} for this resource, created from the
builder's part-parser creator.
+ * The {@link ParserSet} for this resource, built from the
self-contained parser builder memoizer.
*/
- private final Memoizer<HttpPartParser> partParser = memoizer(() ->
builder().partParser().create());
+ private final Memoizer<ParserSet> parsers = memoizer(() ->
parsersBuilder.get().build());
/**
- * The {@link HttpPartSerializer} for this resource, created from the
builder's part-serializer creator.
- */
- private final Memoizer<HttpPartSerializer> partSerializer = memoizer(()
-> builder().partSerializer().create());
+ * The {@link HttpPartParser} for this resource.
+ *
+ * <p>
+ * Uses the builder's part-parser creator (which has general config
annotations applied by {@code init()}).
+ * Adds the {@code @Rest(partParser)} type override from the most
specific (child-most)
+ * annotation in the hierarchy, replacing the factory default.
+ */
+ private final Memoizer<HttpPartParser> partParser = memoizer(() -> {
+ var creator = builder().partParser();
+ getRestAnnotationsForProperty(PROPERTY_partParser)
+ .map(ai -> ai.inner().partParser())
+ .filter(ClassUtils::isNotVoid)
+ .reduce((a, b) -> b)
+ .ifPresent(creator::type);
+ return creator.create();
+ });
+
+ /**
+ * The {@link HttpPartSerializer} for this resource.
+ *
+ * <p>
+ * Uses the builder's part-serializer creator (which has general config
annotations applied by {@code init()}).
+ * Adds the {@code @Rest(partSerializer)} type override from the most
specific (child-most)
+ * annotation in the hierarchy, replacing the factory default.
+ */
+ private final Memoizer<HttpPartSerializer> partSerializer = memoizer(()
-> {
+ var creator = builder().partSerializer();
+ getRestAnnotationsForProperty(PROPERTY_partSerializer)
+ .map(ai -> ai.inner().partSerializer())
+ .filter(ClassUtils::isNotVoid)
+ .reduce((a, b) -> b)
+ .ifPresent(creator::type);
+ return creator.create();
+ });
/**
* Methods annotated with {@link
org.apache.juneau.rest.annotation.RestPostCall @RestPostCall}.
@@ -2480,9 +2121,27 @@ public class RestContext extends Context {
});
/**
- * The {@link SerializerSet} for this resource, built from the
builder's serializer sub-builder.
+ * Fully-configured {@link SerializerSet.Builder} for this resource,
populated from the
+ * {@code @Rest(serializers)} annotation chain and any {@code
@RestInject} override.
+ *
+ * <p>
+ * Starts with an empty set. A bean-store type or instance override
REPLACES the builder or its impl.
+ * Annotation entries (parent-to-child) are appended. A {@code
@RestInject} factory method REPLACES the impl.
*/
- private final Memoizer<SerializerSet> serializers = memoizer(() ->
builder().serializers().build());
+ private final Memoizer<SerializerSet.Builder> serializersBuilder =
memoizer(() -> {
+ var bs = beanStore();
+ var v = Value.of(SerializerSet.create(bs));
+ bs.getBeanType(SerializerSet.class).ifPresent(x ->
v.get().type(x));
+ bs.getBean(SerializerSet.class).ifPresent(x -> v.get().impl(x));
+ getRestAnnotationsForProperty(PROPERTY_serializers).forEach(ai
-> v.get().add(ai.inner().serializers()));
+ new BeanCreateMethodFinder<>(SerializerSet.class,
resource().get(), bs).addBean(SerializerSet.Builder.class,
v.get()).find(Builder::isRestInjectMethod).run(x -> v.get().impl(x));
+ return v.get();
+ });
+
+ /**
+ * The {@link SerializerSet} for this resource, built from the
self-contained serializer builder memoizer.
+ */
+ private final Memoizer<SerializerSet> serializers = memoizer(() ->
serializersBuilder.get().build());
/**
* Methods annotated with {@link
org.apache.juneau.rest.annotation.RestStartCall @RestStartCall} and their
invokers.
@@ -2569,6 +2228,40 @@ public class RestContext extends Context {
return b.build();
});
+ /**
+ * The {@link RestOperations} for this resource — all {@link
RestOpContext} instances built from
+ * methods annotated with {@link
org.apache.juneau.rest.annotation.RestOp @RestOp} (and related).
+ *
+ * <p>
+ * Eagerly initialized in the constructor (via an explicit {@code
.get()} call inside the try-catch block)
+ * so that any {@link ServletException} thrown by {@link
Builder#createRestOperations} surfaces at
+ * construction time rather than lazily on first request. Subsequent
calls return the cached instance.
+ */
+ private final Memoizer<RestOperations> restOperations = memoizer(() -> {
+ try {
+ return builder().createRestOperations(beanStore(),
resource(), this).build();
+ } catch (ServletException e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ /**
+ * The {@link RestChildren} for this resource — child {@link
RestContext} instances registered via
+ * {@link Rest#children() @Rest(children)}.
+ *
+ * <p>
+ * Eagerly initialized in the constructor (via an explicit {@code
.get()} call inside the try-catch block)
+ * so that any exception thrown by {@link Builder#createRestChildren}
surfaces at construction time
+ * rather than lazily. Subsequent calls return the cached instance.
+ */
+ private final Memoizer<RestChildren> restChildren = memoizer(() -> {
+ try {
+ return builder().createRestChildren(beanStore(),
resource(), this).build();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+
/**
* Constructor.
*
@@ -2649,8 +2342,11 @@ public class RestContext extends Context {
bs.add(HeaderList.class, getDefaultResponseHeaders(),
PROP_defaultResponseHeaders);
bs.add(NamedAttributeMap.class,
getDefaultRequestAttributes(), PROP_defaultRequestAttributes);
bs.addBean(DebugEnablement.class, getDebugEnablement());
- restOperations = builder.restOperations(this).build();
- restChildren = builder.restChildren(this).build();
+ // Force-initialize restOperations and restChildren now
so that any construction failures
+ // (e.g. bad @RestOp method or invalid child class)
surface here inside the try-catch and
+ // propagate as a proper init exception rather than
lazily on first use.
+ getRestOperations();
+ getRestChildren();
bs.addBean(SwaggerProvider.class, getSwaggerProvider());
// produces/consumes are resolved lazily via the
produces/consumes memoizers below
@@ -2991,7 +2687,7 @@ public class RestContext extends Context {
}
}
- restChildren.destroy();
+ getRestChildren().destroy();
}
/**
@@ -3055,7 +2751,7 @@ public class RestContext extends Context {
}
// If this resource has child resources, try to
recursively call them.
- var childMatch = restChildren.findMatch(sb);
+ var childMatch = getRestChildren().findMatch(sb);
if (childMatch.isPresent()) {
var uppm = childMatch.get().getPathMatch();
var rc = childMatch.get().getChildContext();
@@ -3158,12 +2854,12 @@ public class RestContext extends Context {
public BeanContext getBeanContext() { return beanContext.get(); }
BeanContext.Builder getBeanContextBuilder() { return
builder.beanContext(); }
- EncoderSet.Builder getEncodersBuilder() { return
builder.encoders(); }
+ EncoderSet.Builder getEncodersBuilder() { return
encodersBuilder.get(); }
JsonSchemaGenerator.Builder getJsonSchemaGeneratorBuilder() { return
builder.jsonSchemaGenerator(); }
- ParserSet.Builder getParsersBuilder() { return
builder.parsers(); }
+ ParserSet.Builder getParsersBuilder() { return
parsersBuilder.get(); }
HttpPartParser.Creator getPartParserCreator() { return
builder.partParser(); }
HttpPartSerializer.Creator getPartSerializerCreator() { return
builder.partSerializer(); }
- SerializerSet.Builder getSerializersBuilder() { return
builder.serializers(); }
+ SerializerSet.Builder getSerializersBuilder() { return
serializersBuilder.get(); }
/**
* Returns the bean store associated with this context.
@@ -3484,7 +3180,7 @@ public class RestContext extends Context {
* An unmodifiable map of child resources.
* Keys are the {@link Rest#path() @Rest(path)} annotation defined
on the child resource.
*/
- public RestChildren getRestChildren() { return restChildren; }
+ public RestChildren getRestChildren() { return restChildren.get(); }
/**
* Returns the REST Java methods defined in this resource.
@@ -3495,7 +3191,7 @@ public class RestContext extends Context {
* @return
* An unmodifiable map of Java method names to call method objects.
*/
- public RestOperations getRestOperations() { return restOperations; }
+ public RestOperations getRestOperations() { return
restOperations.get(); }
/**
* Returns the bootstrap bean store for this context.
@@ -3748,7 +3444,7 @@ public class RestContext extends Context {
throw new ServletException(unwrap(e));
}
}
- restChildren.postInit();
+ getRestChildren().postInit();
return this;
}
@@ -3761,7 +3457,7 @@ public class RestContext extends Context {
public RestContext postInitChildFirst() throws ServletException {
if (initialized.get())
return this;
- restChildren.postInitChildFirst();
+ getRestChildren().postInitChildFirst();
for (var x : postInitChildFirstInvokerPair.get().invokers) {
try {
x.invoke(beanStore, getResource());