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 b832eb2ea7 refactor: finalize framework-internal @Value adoption and
tests
b832eb2ea7 is described below
commit b832eb2ea7fbb4f4680a5f5e64880aa129ceb13b
Author: James Bognar <[email protected]>
AuthorDate: Wed May 27 07:07:15 2026 -0400
refactor: finalize framework-internal @Value adoption and tests
---
.../apache/juneau/junit/bct/BctConfiguration.java | 70 ++++++
.../microservice/jetty/JettyServerComponent.java | 21 +-
.../apache/juneau/microservice/Microservice.java | 35 ++-
.../juneau/rest/auth/jwt/JwtTokenValidator.java | 28 ++-
.../java/org/apache/juneau/rest/RestContext.java | 104 +++++++--
.../java/org/apache/juneau/rest/RestOpContext.java | 33 +--
.../rest/convention/BasicVersionResource.java | 38 +++-
.../org/apache/juneau/rest/logger/CallLogger.java | 131 ++++-------
.../bct/BctConfiguration_ValueAdoption_Test.java | 79 +++++++
.../Microservice_ValueAdoption_Test.java | 62 ++++++
.../rest/RestContext_ValueAdoption_Test.java | 241 +++++++++++++++++++++
.../jwt/JwtTokenValidator_ValueAdoption_Test.java | 58 +++++
.../BasicVersionResource_ValueAdoption_Test.java | 76 +++++++
.../rest/logger/CallLogger_ValueAdoption_Test.java | 160 ++++++++++++++
14 files changed, 1011 insertions(+), 125 deletions(-)
diff --git
a/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/BctConfiguration.java
b/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/BctConfiguration.java
index e2ceae840e..33c35c226a 100644
---
a/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/BctConfiguration.java
+++
b/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/BctConfiguration.java
@@ -20,6 +20,7 @@ import static
org.apache.juneau.commons.utils.AssertionUtils.*;
import static org.apache.juneau.commons.utils.Utils.*;
import org.apache.juneau.commons.function.*;
+import org.apache.juneau.commons.inject.*;
import org.apache.juneau.commons.settings.*;
/**
@@ -342,4 +343,73 @@ public class BctConfiguration {
static BeanConverter getConverter() {
return
opt(BctConfiguration.CONVERTER_OVERRIDE.get()).orElseGet(CONVERTER_SUPPLIER.get());
}
+
+ /**
+ * Returns the {@link Value @Value}-resolved defaults for {@link
#BCT_SORT_MAPS} and
+ * {@link #BCT_SORT_COLLECTIONS}, sourced from the active
+ * {@link org.apache.juneau.commons.settings.Settings Settings} chain
(system properties
+ * → environment variables → registered property sources).
Each invocation creates
+ * a fresh, fully-injected instance.
+ *
+ * <p>
+ * This is the canonical seam for "BCT default sort flags"
reads — production code that
+ * makes a per-call decision based on the resolved defaults should call
+ * {@code BctConfiguration.defaults().isSortMaps()} / {@code
.isSortCollections()} instead of
+ * hand-rolling its own {@code System.getProperty(...)} read.
+ *
+ * @return A new {@link Defaults} instance, populated via {@link
BeanInstantiator}.
+ */
+ public static Defaults defaults() {
+ return BeanInstantiator.of(Defaults.class,
BasicBeanStore.INSTANCE).run();
+ }
+
+ /**
+ * Holder for {@link Value @Value}-resolved BCT default sort flags.
+ *
+ * <p>
+ * Constructed by {@link #defaults()} via {@link BeanInstantiator};
both fields are populated
+ * by {@link Value @Value} annotations whose SVL expressions inline the
canonical
+ * {@link #BCT_SORT_MAPS} and {@link #BCT_SORT_COLLECTIONS} property
names as string literals
+ * (per the project-wide rule that every {@code @Value} expression is a
fully self-contained
+ * string literal — no concatenation with Java constants). The
constants remain the
+ * canonical reference for external callers ({@link Listifiers}, {@code
BctConfigExtension},
+ * and the {@code BctConfiguration.set/get} helpers).
+ */
+ public static class Defaults {
+
+ /**
+ * The {@code Bct.sortMaps} default; {@code true} when the
{@link #BCT_SORT_MAPS} property is
+ * set to a truthy value, {@code false} otherwise.
+ */
+ @Value("${Bct.sortMaps:false}")
+ boolean sortMaps;
+
+ /**
+ * The {@code Bct.sortCollections} default; {@code true} when
the {@link #BCT_SORT_COLLECTIONS}
+ * property is set to a truthy value, {@code false} otherwise.
+ */
+ @Value("${Bct.sortCollections:false}")
+ boolean sortCollections;
+
+ /** Constructor — instantiated by {@link
BeanInstantiator} via {@link #defaults()}. */
+ protected Defaults() {}
+
+ /**
+ * Returns the resolved {@code Bct.sortMaps} default.
+ *
+ * @return {@code true} when the {@link #BCT_SORT_MAPS}
property is set to a truthy value, {@code false} otherwise.
+ */
+ public boolean isSortMaps() {
+ return sortMaps;
+ }
+
+ /**
+ * Returns the resolved {@code Bct.sortCollections} default.
+ *
+ * @return {@code true} when the {@link #BCT_SORT_COLLECTIONS}
property is set to a truthy value, {@code false} otherwise.
+ */
+ public boolean isSortCollections() {
+ return sortCollections;
+ }
+ }
}
diff --git
a/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyServerComponent.java
b/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyServerComponent.java
index 3ee73234f9..56c950274f 100644
---
a/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyServerComponent.java
+++
b/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyServerComponent.java
@@ -31,6 +31,8 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.*;
import org.apache.juneau.commons.inject.BeanStore;
+import org.apache.juneau.commons.inject.Value;
+import org.apache.juneau.commons.reflect.ClassInfo;
import org.apache.juneau.config.event.*;
import org.apache.juneau.cp.*;
import org.apache.juneau.microservice.*;
@@ -85,6 +87,20 @@ public class JettyServerComponent implements
MicroserviceListener {
private final AtomicReference<Server> server = new AtomicReference<>();
private final AtomicReference<Microservice> microservice = new
AtomicReference<>();
+ /**
+ * Env-driven sentinel for {@code availablePort}; {@link
Optional#empty()} when unset (in which case
+ * {@link #onStart(Microservice)} publishes the bound port back as the
{@code availablePort} system property).
+ */
+ @Value("${availablePort}")
+ Optional<String> availablePortEnv;
+
+ /**
+ * Env-driven sentinel for {@code juneau.serverPort}; {@link
Optional#empty()} when unset (in which case
+ * {@link #onStart(Microservice)} publishes the bound port back as the
{@code juneau.serverPort} system property).
+ */
+ @Value("${juneau.serverPort}")
+ Optional<String> serverPortEnv;
+
private static int[] parseIntArray(String csv) {
if (csv == null || csv.isEmpty())
return new int[0];
@@ -172,6 +188,7 @@ public class JettyServerComponent implements
MicroserviceListener {
try {
microservice.set(ms);
var store = ms.getBeanStore();
+ ClassInfo.of(this).inject(this, store);
var cf = ms.getConfig();
var mf = ms.getManifest();
var vr = ms.getVarResolver();
@@ -184,7 +201,7 @@ public class JettyServerComponent implements
MicroserviceListener {
.orElseGet(() ->
mf.get("Jetty-Port").map(JettyServerComponent::parseIntArray).orElseGet(() ->
ints(8000)));
var availablePort = findOpenPort(ports);
- if (env("availablePort").isEmpty())
+ if (availablePortEnv == null ||
availablePortEnv.isEmpty())
System.setProperty("availablePort",
String.valueOf(availablePort));
// Prefer a @Bean-supplied Server, else build one from
jetty.xml.
@@ -265,7 +282,7 @@ public class JettyServerComponent implements
MicroserviceListener {
addServlet(servlet, pathSpecs);
}
- if (env("juneau.serverPort").isEmpty())
+ if (serverPortEnv == null || serverPortEnv.isEmpty())
System.setProperty("juneau.serverPort",
String.valueOf(availablePort));
server.get().start();
diff --git
a/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/Microservice.java
b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/Microservice.java
index b423351d1c..4dbad4aff1 100755
---
a/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/Microservice.java
+++
b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/Microservice.java
@@ -124,7 +124,7 @@ public class Microservice implements ConfigEventListener {
Scanner consoleReader;
PrintWriter consoleWriter;
MicroserviceListener listener;
- File workingDir =
env("juneau.workingDir").map(File::new).orElse(null);
+ File workingDir;
WritableBeanStore beanStore;
BeanStore overridingBeanStore;
List<Class<?>> configurations = list();
@@ -134,6 +134,21 @@ public class Microservice implements ConfigEventListener {
*/
protected Builder() {}
+ /**
+ * {@link Inject @Inject}-annotated initializer that populates
{@link #workingDir} from the
+ * {@code juneau.workingDir} property (system property →
environment variable →
+ * registered property sources) when the property is set. Has
no effect if {@code workingDir}
+ * was already set programmatically.
+ *
+ * @param workingDirEnv The resolved {@code juneau.workingDir}
value, or {@code null}/empty
+ * when not configured.
+ */
+ @Inject
+ public void
initWorkingDirFromEnv(@Value("${juneau.workingDir}") String workingDirEnv) {
+ if (workingDir == null && workingDirEnv != null &&
!workingDirEnv.isEmpty())
+ workingDir = new File(workingDirEnv);
+ }
+
/**
* Copy constructor.
*
@@ -621,10 +636,26 @@ public class Microservice implements ConfigEventListener {
/**
* Creates a new builder for this object.
*
+ * <p>
+ * Routes builder construction through {@link BeanInstantiator} so that
the
+ * {@code juneau.workingDir} env default is resolved through the active
+ * {@link org.apache.juneau.commons.settings.Settings Settings} chain.
+ *
* @return A new microservice builder.
*/
public static Builder create() {
- return new Builder();
+ return create(BasicBeanStore.INSTANCE);
+ }
+
+ /**
+ * Creates a new builder for this object using the supplied {@link
BeanStore} for
+ * {@link Value @Value}-resolution and dependency injection.
+ *
+ * @param beanStore The bean store to use for dependency injection.
+ * @return A new microservice builder.
+ */
+ public static Builder create(BeanStore beanStore) {
+ return BeanInstantiator.of(Builder.class, beanStore).run();
}
/**
diff --git
a/juneau-rest/juneau-rest-server-jwt/src/main/java/org/apache/juneau/rest/auth/jwt/JwtTokenValidator.java
b/juneau-rest/juneau-rest-server-jwt/src/main/java/org/apache/juneau/rest/auth/jwt/JwtTokenValidator.java
index 738e869230..6f304c09cb 100644
---
a/juneau-rest/juneau-rest-server-jwt/src/main/java/org/apache/juneau/rest/auth/jwt/JwtTokenValidator.java
+++
b/juneau-rest/juneau-rest-server-jwt/src/main/java/org/apache/juneau/rest/auth/jwt/JwtTokenValidator.java
@@ -24,6 +24,7 @@ import java.text.*;
import java.time.*;
import java.util.*;
+import org.apache.juneau.commons.inject.*;
import org.apache.juneau.rest.auth.*;
import com.nimbusds.jose.*;
@@ -99,10 +100,26 @@ public class JwtTokenValidator implements TokenValidator {
/**
* Static creator.
*
+ * <p>
+ * Routes builder construction through {@link BeanInstantiator} so the
+ * {@link Builder#jwksCacheTtl jwksCacheTtl} default is resolved via
{@link Value @Value} from the
+ * active {@link org.apache.juneau.commons.settings.Settings Settings}
chain
+ * ({@code juneau.jwt.jwksCacheTtl} property → {@code PT5M}).
+ *
* @return A new builder.
*/
public static Builder create() {
- return new Builder();
+ return create(BasicBeanStore.INSTANCE);
+ }
+
+ /**
+ * Static creator with explicit bean store.
+ *
+ * @param beanStore The bean store to use for dependency injection.
+ * @return A new builder.
+ */
+ public static Builder create(BeanStore beanStore) {
+ return BeanInstantiator.of(Builder.class, beanStore).run();
}
/**
@@ -116,7 +133,14 @@ public class JwtTokenValidator implements TokenValidator {
private String audience;
private Set<JWSAlgorithm> algorithms = new
LinkedHashSet<>(Arrays.asList(JWSAlgorithm.RS256, JWSAlgorithm.ES256));
private Duration clockSkew = Duration.ofSeconds(60);
- private Duration jwksCacheTtl = Duration.ofMinutes(5);
+
+ /**
+ * JWKS cache TTL; populated via {@link Value @Value} from
+ * {@code juneau.jwt.jwksCacheTtl} (ISO-8601 duration, default
{@code PT5M} = 5 minutes).
+ */
+ @Value("${juneau.jwt.jwksCacheTtl:PT5M}")
+ Duration jwksCacheTtl;
+
private Clock clock = Clock.systemUTC();
/** Constructor. */
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 6105d8a180..c5c0ee7dff 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
@@ -871,6 +871,75 @@ public class RestContext extends Context {
return u(toList(s));
});
+
//---------------------------------------------------------------------------------------------
+ // @Value-injected env-driven defaults
+ //
+ // Populated by ClassInfo.of(this).inject(this, beanStore) during
construction; consumed by the
+ // memoizer lambdas below (which run lazily, i.e. after injection has
completed). Each field
+ // is the env-driven default that flows into
mergeReplacedStringAttribute / mergeReplacedBooleanAttribute
+ // as the initial value before the @Rest annotation chain is walked.
+
//---------------------------------------------------------------------------------------------
+
+ /** Env-driven default for {@code @Rest(debugDefault)}; consumed by the
{@link #debugEnablement} memoizer. */
+ @org.apache.juneau.commons.inject.Value("${RestContext.debugDefault:}")
+ private String defaultDebugDefault;
+
+ /** Env-driven default for the call-logger debug level fallback in the
{@link #debugConfig} memoizer. */
+
@org.apache.juneau.commons.inject.Value("${juneau.restLogger.level:INFO}")
+ private String defaultDebugLevel;
+
+ /** Env-driven default for {@code @Rest(allowedHeaderParams)}. */
+
@org.apache.juneau.commons.inject.Value("${RestContext.allowedHeaderParams:Accept,Content-Type}")
+ private String defaultAllowedHeaderParams;
+
+ /** Env-driven default for {@code @Rest(allowedMethodHeaders)}. */
+
@org.apache.juneau.commons.inject.Value("${RestContext.allowedMethodHeaders:}")
+ private String defaultAllowedMethodHeaders;
+
+ /** Env-driven default for {@code @Rest(allowedMethodParams)}. */
+
@org.apache.juneau.commons.inject.Value("${RestContext.allowedMethodParams:HEAD,OPTIONS}")
+ private String defaultAllowedMethodParams;
+
+ /** Env-driven default for {@code @Rest(disableContentParam)}. */
+
@org.apache.juneau.commons.inject.Value("${RestContext.disableContentParam:false}")
+ private boolean defaultDisableContentParam;
+
+ /** Env-driven default for {@code @Rest(renderResponseStackTraces)}. */
+
@org.apache.juneau.commons.inject.Value("${RestContext.renderResponseStackTraces:false}")
+ private boolean defaultRenderResponseStackTraces;
+
+ /** Env-driven default for {@code @Rest(problemDetails)}. */
+
@org.apache.juneau.commons.inject.Value("${RestContext.problemDetails:false}")
+ private boolean defaultProblemDetails;
+
+ /** Env-driven default for {@code @Rest(virtualThreads)}. */
+
@org.apache.juneau.commons.inject.Value("${RestContext.virtualThreads:false}")
+ private boolean defaultVirtualThreads;
+
+ /** Env-driven default for {@code @Rest(eagerInit)}. */
+
@org.apache.juneau.commons.inject.Value("${RestContext.eagerInit:false}")
+ private boolean defaultEagerInit;
+
+ /** Env-driven default for {@code @Rest(clientVersionHeader)}. */
+
@org.apache.juneau.commons.inject.Value("${RestContext.clientVersionHeader:Client-Version}")
+ private String defaultClientVersionHeader;
+
+ /** Env-driven default for {@code @Rest(uriRelativity)}; resolves to
empty string when unset (treated as "no default"). */
+ @org.apache.juneau.commons.inject.Value("${RestContext.uriRelativity:}")
+ private String defaultUriRelativity;
+
+ /** Env-driven default for {@code @Rest(uriAuthority)}; {@link
Optional#empty()} when unset (preserves null-vs-empty distinction). */
+ @org.apache.juneau.commons.inject.Value("${RestContext.uriAuthority}")
+ private Optional<String> defaultUriAuthority;
+
+ /** Env-driven default for {@code @Rest(uriContext)}; {@link
Optional#empty()} when unset (preserves null-vs-empty distinction). */
+ @org.apache.juneau.commons.inject.Value("${RestContext.uriContext}")
+ private Optional<String> defaultUriContext;
+
+ /** Env-driven default for {@code @Rest(uriResolution)}; resolves to
empty string when unset (treated as "no default"). */
+ @org.apache.juneau.commons.inject.Value("${RestContext.uriResolution:}")
+ private String defaultUriResolution;
+
/**
* The {@link DebugEnablement} for this resource.
*
@@ -888,7 +957,7 @@ public class RestContext extends Context {
// 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.
var bs = beanStore();
- String debugDefaultStr =
mergeReplacedStringAttribute(PROPERTY_debugDefault,
env("RestContext.debugDefault").orElse(null));
+ String debugDefaultStr =
mergeReplacedStringAttribute(PROPERTY_debugDefault, defaultDebugDefault);
Enablement resolvedDebugDefault = null;
if (nn(debugDefaultStr) && !debugDefaultStr.isBlank())
resolvedDebugDefault =
Enablement.fromString(debugDefaultStr);
@@ -923,7 +992,7 @@ public class RestContext extends Context {
.reduce((first, second) -> second)
.orElse("");
var format = formatType == null ? new BasicTextFormat() :
BeanInstantiator.of(DebugFormat.class, bs).type(formatType).run();
- var level = StringUtils.isNotBlank(levelStr) ?
Level.parse(levelStr) : Level.parse(env(CallLogger.SP_level, "INFO"));
+ var level = StringUtils.isNotBlank(levelStr) ?
Level.parse(levelStr) : Level.parse(defaultDebugLevel);
var mode2 = mode;
return new DebugConfig(bs) {
@Override
@@ -1939,6 +2008,11 @@ public class RestContext extends Context {
.addBean(AnnotationWorkList.class,
annotationWork);
// @formatter:on
+ // Inject @Value-annotated env-driven defaults onto
this RestContext instance so that the
+ // memoizer lambdas below (which run lazily on first
.get() call) read fully resolved values.
+ // Must run BEFORE isEagerInit() since the eagerInit
memoizer itself reads defaultEagerInit.
+ ClassInfo.of(this).inject(this, beanStore);
+
if (isEagerInit()) {
// Force-fire the framework-bean memoizers in
dependency-friendly order so their @Rest()
// annotation walks (e.g.
`@Rest(partParser=…)`, `@Rest(partSerializer=…)`, `@Rest(encoders=…)`,
@@ -2128,42 +2202,42 @@ public class RestContext extends Context {
* default {@code "Accept,Content-Type"}.
*/
private final Memoizer<Set<String>> allowedHeaderParams = memoizer(() ->
-
Collections.unmodifiableSet(newCaseInsensitiveSet(mergeReplacedStringAttribute(PROPERTY_allowedHeaderParams,
env("RestContext.allowedHeaderParams", "Accept,Content-Type")))));
+
Collections.unmodifiableSet(newCaseInsensitiveSet(mergeReplacedStringAttribute(PROPERTY_allowedHeaderParams,
defaultAllowedHeaderParams))));
/**
* 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("")))));
+
Collections.unmodifiableSet(newCaseInsensitiveSet(mergeReplacedStringAttribute(PROPERTY_allowedMethodHeaders,
defaultAllowedMethodHeaders))));
/**
* 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")))));
+
Collections.unmodifiableSet(newCaseInsensitiveSet(mergeReplacedStringAttribute(PROPERTY_allowedMethodParams,
defaultAllowedMethodParams))));
/**
* 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)));
+ !mergeReplacedBooleanAttribute(PROPERTY_disableContentParam,
defaultDisableContentParam));
/**
* 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)));
+
mergeReplacedBooleanAttribute(PROPERTY_renderResponseStackTraces,
defaultRenderResponseStackTraces));
/**
* Whether the resource emits RFC 7807 {@code application/problem+json}
responses; resolved from
* {@code @Rest(problemDetails)}.
*/
private final Memoizer<Boolean> problemDetails = memoizer(() ->
- mergeReplacedBooleanAttribute(PROPERTY_problemDetails,
env("RestContext.problemDetails", false)));
+ mergeReplacedBooleanAttribute(PROPERTY_problemDetails,
defaultProblemDetails));
/**
* Whether the resource opts into per-request virtual-thread dispatch
on Java 21+; resolved from
@@ -2174,7 +2248,7 @@ public class RestContext extends Context {
* than Java 21 the flag is logged once and ignored — see {@link
#virtualThreadExecutor}.
*/
private final Memoizer<Boolean> virtualThreadsEnabled = memoizer(() ->
- mergeReplacedBooleanAttribute(PROPERTY_virtualThreads,
env("RestContext.virtualThreads", false)));
+ mergeReplacedBooleanAttribute(PROPERTY_virtualThreads,
defaultVirtualThreads));
/**
* Configurable async-response timeout (milliseconds) applied by {@code
AsyncResponseProcessor} to
@@ -2233,14 +2307,14 @@ public class RestContext extends Context {
* resolved from {@code @Rest(eagerInit)}.
*/
private final Memoizer<Boolean> eagerInit = memoizer(() ->
- mergeReplacedBooleanAttribute(PROPERTY_eagerInit,
env("RestContext.eagerInit", false)));
+ mergeReplacedBooleanAttribute(PROPERTY_eagerInit,
defaultEagerInit));
/**
* 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")));
+ mergeReplacedStringAttribute(PROPERTY_clientVersionHeader,
defaultClientVersionHeader));
/**
* The {@link UriRelativity} strategy for URI resolution in this
resource.
@@ -2251,7 +2325,7 @@ public class RestContext extends Context {
*/
private final Memoizer<UriRelativity> uriRelativity = memoizer(() -> {
var v = new AtomicReference<>(
- parseEnumConstant(UriRelativity.class,
resolve(emptyIfNull(env("RestContext.uriRelativity").get())))
+ parseEnumConstant(UriRelativity.class,
resolve(emptyIfNull(defaultUriRelativity)))
.orElse(UriRelativity.RESOURCE)
);
restAnnotationsForPropertySortedByRank(PROPERTY_uriRelativity).forEach(ai ->
ai.getString(PROPERTY_uriRelativity).filter(StringUtils::isNotBlank).ifPresent(s
->
@@ -2268,7 +2342,7 @@ public class RestContext extends Context {
* 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));
+ String local =
mergeReplacedStringAttribute(PROPERTY_uriAuthority,
defaultUriAuthority.orElse(null));
if (nn(local))
return local;
var pc = parentContext();
@@ -2283,7 +2357,7 @@ public class RestContext extends Context {
* 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));
+ String local =
mergeReplacedStringAttribute(PROPERTY_uriContext,
defaultUriContext.orElse(null));
if (nn(local))
return local;
var pc = parentContext();
@@ -2299,7 +2373,7 @@ public class RestContext extends Context {
*/
private final Memoizer<UriResolution> uriResolution = memoizer(() -> {
var v = new AtomicReference<>(
- parseEnumConstant(UriResolution.class,
resolve(emptyIfNull(env("RestContext.uriResolution").get())))
+ parseEnumConstant(UriResolution.class,
resolve(emptyIfNull(defaultUriResolution)))
.orElse(UriResolution.ROOT_RELATIVE)
);
restAnnotationsForPropertySortedByRank(PROPERTY_uriResolution).forEach(ai ->
ai.getString(PROPERTY_uriResolution).filter(StringUtils::isNotBlank).ifPresent(s
->
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 96e59eba17..d33808e7b0 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
@@ -19,7 +19,6 @@ package org.apache.juneau.rest;
import org.apache.juneau.commons.http.MediaType;
import static org.apache.juneau.commons.reflect.AnnotationTraversal.*;
import static org.apache.juneau.commons.utils.CollectionUtils.*;
-import static org.apache.juneau.commons.utils.IoUtils.UTF8;
import static org.apache.juneau.commons.utils.StringUtils.*;
import static org.apache.juneau.commons.utils.ThrowableUtils.*;
import static org.apache.juneau.commons.utils.Utils.*;
@@ -97,17 +96,21 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
private static final AnnotationProvider AP =
AnnotationProvider.INSTANCE;
- private static Charset envDefaultRestCharset() {
- return
env("RestContext.defaultCharset").map(Charset::forName).orElse(UTF8);
- }
-
- private static long envDefaultRestMaxInput() {
- return
env("RestContext.maxInput").map(RestOpContext::parseMaxInputEnv).orElse(100_000_000L);
- }
+ /**
+ * Env-driven default charset for this operation, populated by {@link
ClassInfo#inject(Object, BeanStore)}
+ * before the {@link #defaultCharset} memoizer fires; resolved from
{@code RestContext.defaultCharset} or
+ * {@code "UTF-8"}.
+ */
+
@org.apache.juneau.commons.inject.Value("${RestContext.defaultCharset:UTF-8}")
+ private String defaultCharsetName;
- private static long parseMaxInputEnv(String value) {
- return parseLongWithSuffix(value);
- }
+ /**
+ * Env-driven default max-input string for this operation, populated by
{@link ClassInfo#inject(Object, BeanStore)}
+ * before the {@link #maxInput} memoizer fires; resolved from {@code
RestContext.maxInput} or
+ * {@code "100000000"}.
+ */
+
@org.apache.juneau.commons.inject.Value("${RestContext.maxInput:100000000}")
+ private String defaultMaxInputString;
/**
* Internal construction-time state holder.
@@ -288,7 +291,7 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
if (rv != null && !rv.isEmpty())
return Charset.forName(rv);
}
- return envDefaultRestCharset();
+ return Charset.forName(defaultCharsetName);
});
/**
@@ -617,7 +620,7 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
if (rv != null && !rv.isEmpty())
return parseLongWithSuffix(rv);
}
- return envDefaultRestMaxInput();
+ return parseLongWithSuffix(defaultMaxInputString);
});
/**
@@ -1223,6 +1226,10 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
bs.add(HttpPartSerializer.class, getPartSerializer());
bs.add(SerializerSet.class, getSerializers());
+ // Inject @Value-annotated env-driven defaults onto
this RestOpContext instance so that the
+ // memoizer lambdas (defaultCharset, maxInput) read
fully resolved values when first invoked.
+ ClassInfo.of(this).inject(this, bs);
+
// The 6 formerly-eager scalar fields are now memoized;
no eagerness needed here.
// Pre-warm httpMethod so it is in the memoizer cache
for immediate use by compareTo/match.
httpMethod.get();
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicVersionResource.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicVersionResource.java
index b1d1362ebc..599ef5ac1d 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicVersionResource.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicVersionResource.java
@@ -16,12 +16,15 @@
*/
package org.apache.juneau.rest.convention;
+import static org.apache.juneau.commons.utils.Utils.*;
+
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.function.*;
import java.util.jar.*;
+import org.apache.juneau.commons.inject.*;
import org.apache.juneau.json.*;
import org.apache.juneau.rest.*;
import org.apache.juneau.rest.annotation.*;
@@ -140,12 +143,25 @@ public class BasicVersionResource {
public static final String UNKNOWN = "(unknown)";
/**
- * Creates a new builder.
+ * Creates a new builder, with {@link Value @Value}-annotated fields
populated through
+ * {@link BeanInstantiator} from the active
+ * {@link org.apache.juneau.commons.settings.Settings Settings} chain.
*
* @return A new builder.
*/
public static Builder create() {
- return new Builder();
+ return create(BasicBeanStore.INSTANCE);
+ }
+
+ /**
+ * Creates a new builder using the supplied {@link BeanStore} for
{@link Value @Value}-resolution
+ * and dependency injection.
+ *
+ * @param beanStore The bean store to use for dependency injection.
+ * @return A new builder.
+ */
+ public static Builder create(BeanStore beanStore) {
+ return BeanInstantiator.of(Builder.class, beanStore).run();
}
private final Map<String,String> info;
@@ -220,7 +236,21 @@ public class BasicVersionResource {
private final Map<String,String> entries = new
LinkedHashMap<>();
private boolean explicit;
- /** Constructor — package access for {@link
BasicVersionResource#create()}. */
+ /**
+ * Env-driven default for the {@code javaVersion} entry;
resolved from the {@code java.version}
+ * system property (defaulting to the literal {@code
(unknown)}, matching
+ * {@link BasicVersionResource#UNKNOWN}). Populated by {@link
BeanInstantiator} via
+ * {@link Value @Value} field injection.
+ *
+ * <p>
+ * The default is inlined as a string literal rather than
referencing {@code UNKNOWN} via
+ * concatenation, per the project-wide rule that every {@code
@Value} expression is a
+ * fully self-contained string literal.
+ */
+ @Value("${java.version:(unknown)}")
+ String javaVersionDefault;
+
+ /** Constructor — protected access for {@link
BasicVersionResource#create()}. */
protected Builder() {}
/**
@@ -356,7 +386,7 @@ public class BasicVersionResource {
* @return This object.
*/
public Builder fromJavaVersion() {
- entries.putIfAbsent("javaVersion",
System.getProperty("java.version", UNKNOWN));
+ entries.putIfAbsent("javaVersion",
opt(javaVersionDefault).orElse(UNKNOWN));
explicit = true;
return this;
}
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logger/CallLogger.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logger/CallLogger.java
index 06c4c5c369..e6633899bb 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logger/CallLogger.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logger/CallLogger.java
@@ -119,10 +119,19 @@ public class CallLogger {
ThrownStore thrownStore;
List<CallLoggerRule> normalRules = list();
List<CallLoggerRule> debugRules = list();
+
+ @Value("${juneau.restLogger.enabled:ALWAYS}")
Enablement enabled;
- Predicate<HttpServletRequest> enabledTest;
+
+ Predicate<HttpServletRequest> enabledTest = x -> false;
+
+ @Value("${juneau.restLogger.requestDetail:STATUS_LINE}")
CallLoggingDetail requestDetail;
+
+ @Value("${juneau.restLogger.responseDetail:STATUS_LINE}")
CallLoggingDetail responseDetail;
+
+ @Value("${juneau.restLogger.level:OFF}")
Level level;
/**
@@ -131,12 +140,19 @@ public class CallLogger {
* @param beanStore The bean store to use for creating beans.
*/
protected Builder(BeanStore beanStore) {
- logger = Logger.getLogger(env(SP_logger, "global"));
- enabled = env(SP_enabled, ALWAYS);
- enabledTest = x -> false;
- requestDetail = env(SP_requestDetail, STATUS_LINE);
- responseDetail = env(SP_responseDetail, STATUS_LINE);
- level = env(SP_level).map(Level::parse).orElse(OFF);
+ }
+
+ /**
+ * Init method invoked by {@link BeanInstantiator} after
construction; resolves the default
+ * logger from the {@code juneau.restLogger.logger} property
(defaulting to {@code "global"})
+ * and installs it as this builder's logger.
+ *
+ * @param loggerName The resolved logger name. Defaulted to
{@code "global"} when unset.
+ */
+ @Inject
+ public void
initLoggerFromName(@Value("${juneau.restLogger.logger:global}") String
loggerName) {
+ if (logger == null)
+ logger = Logger.getLogger(loggerName);
}
/**
@@ -180,8 +196,8 @@ public class CallLogger {
* <p>
* If not specified, the setting is determined via the
following:
* <ul>
- * <li><js>{@link CallLogger#SP_enabled
"juneau.restLogger.enabled"} system property.
- * <li><js>{@link CallLogger#SP_enabled
"JUNEAU_RESTLOGGER_ENABLED"} environment variable.
+ * <li><js>"juneau.restLogger.enabled"</js> system
property.
+ * <li><js>"JUNEAU_RESTLOGGER_ENABLED"</js> environment
variable.
* <li><js>"ALWAYS"</js>.
* </ul>
*
@@ -231,8 +247,8 @@ public class CallLogger {
* <p>
* If not specified, the setting is determined via the
following:
* <ul>
- * <li><js>{@link CallLogger#SP_level
"juneau.restLogger.level"} system property.
- * <li><js>{@link CallLogger#SP_level
"JUNEAU_RESTLOGGER_level"} environment variable.
+ * <li><js>"juneau.restLogger.level"</js> system property.
+ * <li><js>"JUNEAU_RESTLOGGER_LEVEL"</js> environment
variable.
* <li><js>"OFF"</js>.
* </ul>
*
@@ -251,8 +267,8 @@ public class CallLogger {
* <p>
* If not specified, the logger name is determined in the
following order:
* <ol>
- * <li><js>{@link CallLogger#SP_logger
"juneau.restLogger.logger"} system property.
- * <li><js>{@link CallLogger#SP_logger
"JUNEAU_RESTLOGGER_LOGGER"} environment variable.
+ * <li><js>"juneau.restLogger.logger"</js> system property.
+ * <li><js>"JUNEAU_RESTLOGGER_LOGGER"</js> environment
variable.
* <li><js>"global"</js>.
* </ol>
*
@@ -277,8 +293,8 @@ public class CallLogger {
* <p>
* If not specified, the logger name is determined in the
following order:
* <ol>
- * <li><js>{@link CallLogger#SP_logger
"juneau.restLogger.logger"} system property.
- * <li><js>{@link CallLogger#SP_logger
"JUNEAU_RESTLOGGER_LOGGER"} environment variable.
+ * <li><js>"juneau.restLogger.logger"</js> system property.
+ * <li><js>"JUNEAU_RESTLOGGER_LOGGER"</js> environment
variable.
* <li><js>"global"</js>.
* </ol>
*
@@ -329,8 +345,8 @@ public class CallLogger {
* <p>
* If not specified, the setting is determined via the
following:
* <ul>
- * <li><js>{@link CallLogger#SP_requestDetail
"juneau.restLogger.requestDetail"} system property.
- * <li><js>{@link CallLogger#SP_requestDetail
"JUNEAU_RESTLOGGER_requestDetail"} environment variable.
+ * <li><js>"juneau.restLogger.requestDetail"</js> system
property.
+ * <li><js>"JUNEAU_RESTLOGGER_REQUESTDETAIL"</js>
environment variable.
* <li><js>"STATUS_LINE"</js>.
* </ul>
*
@@ -358,8 +374,8 @@ public class CallLogger {
* <p>
* If not specified, the setting is determined via the
following:
* <ul>
- * <li><js>{@link CallLogger#SP_responseDetail
"juneau.restLogger.responseDetail"} system property.
- * <li><js>{@link CallLogger#SP_responseDetail
"JUNEAU_RESTLOGGER_responseDetail"} environment variable.
+ * <li><js>"juneau.restLogger.responseDetail"</js> system
property.
+ * <li><js>"JUNEAU_RESTLOGGER_RESPONSEDETAIL"</js>
environment variable.
* <li><js>"STATUS_LINE"</js>.
* </ul>
*
@@ -429,80 +445,21 @@ public class CallLogger {
private static final CallLoggerRule DEFAULT_RULE =
CallLoggerRule.create(BasicBeanStore.INSTANCE).build();
/**
- * System property name for the default logger name to use for {@link
CallLogger} objects.
- * <p>
- * Can also use a <c>JUNEAU_RESTLOGGER_LOGGER</c> environment variable.
- * <p>
- * If not specified, the default is <js>"global"</js>.
- */
- public static final String SP_logger = "juneau.restLogger.logger";
-
- /**
- * System property name for the default enablement setting for {@link
CallLogger} objects.
- * <p>
- * Can also use a <c>JUNEAU_RESTLOGGER_ENABLED</c> environment variable.
- * <p>
- * The possible values are:
- * <ul>
- * <li>{@link Enablement#ALWAYS "ALWAYS"} (default) - Logging is
enabled.
- * <li>{@link Enablement#NEVER "NEVER"} - Logging is disabled.
- * <li>{@link Enablement#CONDITIONAL "CONDITIONALLY"} - Logging is
enabled if it passes the {@link Builder#enabledTest(Predicate)} test.
- * </ul>
- */
- public static final String SP_enabled = "juneau.restLogger.enabled";
-
- /**
- * System property name for the default request detail setting for
{@link CallLogger} objects.
- * <p>
- * Can also use a <c>JUNEAU_RESTLOGGER_REQUESTDETAIL</c> environment
variable.
- *
- * <ul class='values'>
- * <li>{@link CallLoggingDetail#STATUS_LINE "STATUS_LINE"}
(default) - Log only the status line.
- * <li>{@link CallLoggingDetail#HEADER "HEADER"} - Log the status
line and headers.
- * <li>{@link CallLoggingDetail#ENTITY "ENTITY"} - Log the status
line and headers and content if available.
- * </ul>
- */
- public static final String SP_requestDetail =
"juneau.restLogger.requestDetail";
-
- /**
- * System property name for the default response detail setting for
{@link CallLogger} objects.
- * <p>
- * Can also use a <c>JUNEAU_RESTLOGGER_RESPONSEDETAIL</c> environment
variable.
+ * Static creator.
*
- * <ul class='values'>
- * <li>{@link CallLoggingDetail#STATUS_LINE "STATUS_LINE"}
(default) - Log only the status line.
- * <li>{@link CallLoggingDetail#HEADER "HEADER"} - Log the status
line and headers.
- * <li>{@link CallLoggingDetail#ENTITY "ENTITY"} - Log the status
line and headers and content if available.
- * </ul>
- */
- public static final String SP_responseDetail =
"juneau.restLogger.responseDetail";
-
- /**
- * System property name for the logging level setting for {@link
CallLogger} objects.
* <p>
- * Can also use a <c>JUNEAU_RESTLOGGER_LEVEL</c> environment variable.
- *
- * <ul class='values'>
- * <li>{@link Level#OFF "OFF"} (default)
- * <li>{@link Level#SEVERE "SEVERE"}
- * <li>{@link Level#WARNING "WARNING"}
- * <li>{@link Level#INFO "INFO"}
- * <li>{@link Level#CONFIG "CONFIG"}
- * <li>{@link Level#FINE "FINE"}
- * <li>{@link Level#FINER "FINER"}
- * <li>{@link Level#FINEST "FINEST"}
- * </ul>
- */
- public static final String SP_level = "juneau.restLogger.level";
-
- /**
- * Static creator.
+ * Routes builder construction through {@link BeanInstantiator} so that
{@link Value @Value}-annotated
+ * fields and the {@code @Inject}-annotated logger initializer pick up
+ * {@code juneau.restLogger.logger}, {@code juneau.restLogger.enabled},
{@code juneau.restLogger.requestDetail},
+ * {@code juneau.restLogger.responseDetail}, and {@code
juneau.restLogger.level} from the active
+ * {@link org.apache.juneau.commons.settings.Settings} chain (system
properties → environment
+ * variables → registered property sources).
*
* @param beanStore The bean store to use for creating beans.
* @return A new builder for this object.
*/
public static Builder create(BeanStore beanStore) {
- return new Builder(beanStore);
+ return BeanInstantiator.of(Builder.class, beanStore).run();
}
private final Logger logger;
@@ -762,7 +719,7 @@ public class CallLogger {
* @return A new builder object.
*/
protected Builder init(BeanStore beanStore) {
- return new
Builder(beanStore).logger(beanStore.getBean(Logger.class).orElse(null)).thrownStore(beanStore.getBean(ThrownStore.class).orElse(null));
+ return
create(beanStore).logger(beanStore.getBean(Logger.class).orElse(null)).thrownStore(beanStore.getBean(ThrownStore.class).orElse(null));
}
/**
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/junit/bct/BctConfiguration_ValueAdoption_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/junit/bct/BctConfiguration_ValueAdoption_Test.java
new file mode 100644
index 0000000000..4b9673ebf7
--- /dev/null
+++
b/juneau-utest/src/test/java/org/apache/juneau/junit/bct/BctConfiguration_ValueAdoption_Test.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.junit.bct;
+
+import static org.apache.juneau.junit.bct.BctConfiguration.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.apache.juneau.commons.settings.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * TODO-92 acceptance tests for {@code @Value}-driven defaults on {@link
BctConfiguration.Defaults}.
+ *
+ * <p>
+ * 3-test triad per migrated field per OQA #4 — system property set, unset
(default), and {@code Settings.setGlobal}.
+ */
+class BctConfiguration_ValueAdoption_Test {
+
+ @AfterEach
+ void cleanup() {
+ var s = Settings.get();
+ s.unsetGlobal(BCT_SORT_MAPS);
+ s.unsetGlobal(BCT_SORT_COLLECTIONS);
+ System.clearProperty(BCT_SORT_MAPS);
+ System.clearProperty(BCT_SORT_COLLECTIONS);
+ }
+
+ // -------------------- sortMaps --------------------
+
+ @Test
+ void a01_sortMaps_set() {
+ System.setProperty(BCT_SORT_MAPS, "true");
+ assertTrue(BctConfiguration.defaults().isSortMaps());
+ }
+
+ @Test
+ void a02_sortMaps_unset() {
+ assertFalse(BctConfiguration.defaults().isSortMaps());
+ }
+
+ @Test
+ void a03_sortMaps_setGlobal() {
+ Settings.get().setGlobal(BCT_SORT_MAPS, "true");
+ assertTrue(BctConfiguration.defaults().isSortMaps());
+ }
+
+ // -------------------- sortCollections --------------------
+
+ @Test
+ void b01_sortCollections_set() {
+ System.setProperty(BCT_SORT_COLLECTIONS, "true");
+ assertTrue(BctConfiguration.defaults().isSortCollections());
+ }
+
+ @Test
+ void b02_sortCollections_unset() {
+ assertFalse(BctConfiguration.defaults().isSortCollections());
+ }
+
+ @Test
+ void b03_sortCollections_setGlobal() {
+ Settings.get().setGlobal(BCT_SORT_COLLECTIONS, "true");
+ assertTrue(BctConfiguration.defaults().isSortCollections());
+ }
+}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/microservice/Microservice_ValueAdoption_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/microservice/Microservice_ValueAdoption_Test.java
new file mode 100644
index 0000000000..bc986ca13c
--- /dev/null
+++
b/juneau-utest/src/test/java/org/apache/juneau/microservice/Microservice_ValueAdoption_Test.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.microservice;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.io.*;
+
+import org.apache.juneau.commons.settings.*;
+import org.apache.juneau.testing.annotations.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * TODO-92 acceptance tests for the {@code @Value}-driven {@code
juneau.workingDir} default on
+ * {@link Microservice.Builder}.
+ *
+ * <p>
+ * 3-test triad per migrated field per OQA #4 — system property set, unset
(default = null), and
+ * {@code Settings.setGlobal} override.
+ */
+@JettyMicroserviceTest
+class Microservice_ValueAdoption_Test {
+
+ private static final String SP = "juneau.workingDir";
+
+ @AfterEach
+ void cleanup() {
+ Settings.get().unsetGlobal(SP);
+ System.clearProperty(SP);
+ }
+
+ @Test
+ void a01_workingDir_set() {
+ System.setProperty(SP, "/tmp/todo92-set");
+ assertEquals(new File("/tmp/todo92-set"),
Microservice.create().workingDir);
+ }
+
+ @Test
+ void a02_workingDir_unset() {
+ assertNull(Microservice.create().workingDir);
+ }
+
+ @Test
+ void a03_workingDir_setGlobal() {
+ Settings.get().setGlobal(SP, "/tmp/todo92-global");
+ assertEquals(new File("/tmp/todo92-global"),
Microservice.create().workingDir);
+ }
+}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/rest/RestContext_ValueAdoption_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/rest/RestContext_ValueAdoption_Test.java
new file mode 100644
index 0000000000..5978b372b5
--- /dev/null
+++
b/juneau-utest/src/test/java/org/apache/juneau/rest/RestContext_ValueAdoption_Test.java
@@ -0,0 +1,241 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.commons.settings.*;
+import org.apache.juneau.rest.annotation.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * TODO-92 acceptance tests for the {@code @Value}-driven env-default fields
on {@link RestContext}.
+ *
+ * <p>
+ * 3-test triad per migrated field per OQA #4 — system property set, unset
(default), and
+ * {@code Settings.setGlobal} override. Validates the {@code @Value} field
receives the resolved
+ * value at injection time. The downstream {@code
mergeReplacedStringAttribute} pipeline applies
+ * its own DefaultConfig-driven precedence on top of these fields and is
exercised by the existing
+ * {@code RestContext}-level tests; this class scopes coverage to the {@code
@Value} seam itself.
+ */
+class RestContext_ValueAdoption_Test extends TestBase {
+
+ @Rest
+ public static class A {}
+
+ private static final List<String> PROPS = List.of(
+ "RestContext.debugDefault",
+ "RestContext.allowedHeaderParams",
+ "RestContext.allowedMethodHeaders",
+ "RestContext.allowedMethodParams",
+ "RestContext.disableContentParam",
+ "RestContext.renderResponseStackTraces",
+ "RestContext.problemDetails",
+ "RestContext.virtualThreads",
+ "RestContext.eagerInit",
+ "RestContext.clientVersionHeader",
+ "RestContext.uriRelativity",
+ "RestContext.uriAuthority",
+ "RestContext.uriContext",
+ "RestContext.uriResolution",
+ "juneau.restLogger.level"
+ );
+
+ @AfterEach
+ void cleanup() {
+ var s = Settings.get();
+ for (var k : PROPS) {
+ s.unsetGlobal(k);
+ System.clearProperty(k);
+ }
+ }
+
+ private RestContext ctx() throws Exception {
+ var resource = new A();
+ return new RestContext(new RestContext.Args(A.class, null,
null, () -> resource, "", null, null, null, false))
+ .postInit().postInitChildFirst();
+ }
+
+ @SuppressWarnings("unchecked")
+ private <T> T fld(RestContext c, String name) throws Exception {
+ Field f = RestContext.class.getDeclaredField(name);
+ f.setAccessible(true);
+ return (T)f.get(c);
+ }
+
+ // -------------------- defaultAllowedHeaderParams (String)
--------------------
+
+ @Test
+ void a01_allowedHeaderParams_set() throws Exception {
+ System.setProperty("RestContext.allowedHeaderParams",
"X-Custom1,X-Custom2");
+ assertEquals("X-Custom1,X-Custom2", fld(ctx(),
"defaultAllowedHeaderParams"));
+ }
+
+ @Test
+ void a02_allowedHeaderParams_unset() throws Exception {
+ assertEquals("Accept,Content-Type", fld(ctx(),
"defaultAllowedHeaderParams"));
+ }
+
+ @Test
+ void a03_allowedHeaderParams_setGlobal() throws Exception {
+ Settings.get().setGlobal("RestContext.allowedHeaderParams",
"X-Global");
+ assertEquals("X-Global", fld(ctx(),
"defaultAllowedHeaderParams"));
+ }
+
+ // -------------------- defaultAllowedMethodParams (String)
--------------------
+
+ @Test
+ void b01_allowedMethodParams_set() throws Exception {
+ System.setProperty("RestContext.allowedMethodParams",
"GET,POST");
+ assertEquals("GET,POST", fld(ctx(),
"defaultAllowedMethodParams"));
+ }
+
+ @Test
+ void b02_allowedMethodParams_unset() throws Exception {
+ assertEquals("HEAD,OPTIONS", fld(ctx(),
"defaultAllowedMethodParams"));
+ }
+
+ @Test
+ void b03_allowedMethodParams_setGlobal() throws Exception {
+ Settings.get().setGlobal("RestContext.allowedMethodParams",
"PUT");
+ assertEquals("PUT", fld(ctx(), "defaultAllowedMethodParams"));
+ }
+
+ // -------------------- defaultDisableContentParam (boolean)
--------------------
+
+ @Test
+ void c01_disableContentParam_set() throws Exception {
+ System.setProperty("RestContext.disableContentParam", "true");
+ assertTrue(this.<Boolean>fld(ctx(),
"defaultDisableContentParam"));
+ }
+
+ @Test
+ void c02_disableContentParam_unset() throws Exception {
+ assertFalse(this.<Boolean>fld(ctx(),
"defaultDisableContentParam"));
+ }
+
+ @Test
+ void c03_disableContentParam_setGlobal() throws Exception {
+ Settings.get().setGlobal("RestContext.disableContentParam",
"true");
+ assertTrue(this.<Boolean>fld(ctx(),
"defaultDisableContentParam"));
+ }
+
+ // -------------------- defaultRenderResponseStackTraces (boolean)
--------------------
+
+ @Test
+ void d01_renderResponseStackTraces_set() throws Exception {
+ System.setProperty("RestContext.renderResponseStackTraces",
"true");
+ assertTrue(this.<Boolean>fld(ctx(),
"defaultRenderResponseStackTraces"));
+ }
+
+ @Test
+ void d02_renderResponseStackTraces_unset() throws Exception {
+ assertFalse(this.<Boolean>fld(ctx(),
"defaultRenderResponseStackTraces"));
+ }
+
+ @Test
+ void d03_renderResponseStackTraces_setGlobal() throws Exception {
+
Settings.get().setGlobal("RestContext.renderResponseStackTraces", "true");
+ assertTrue(this.<Boolean>fld(ctx(),
"defaultRenderResponseStackTraces"));
+ }
+
+ // -------------------- defaultProblemDetails (boolean)
--------------------
+
+ @Test
+ void e01_problemDetails_set() throws Exception {
+ System.setProperty("RestContext.problemDetails", "true");
+ assertTrue(this.<Boolean>fld(ctx(), "defaultProblemDetails"));
+ }
+
+ @Test
+ void e02_problemDetails_unset() throws Exception {
+ assertFalse(this.<Boolean>fld(ctx(), "defaultProblemDetails"));
+ }
+
+ @Test
+ void e03_problemDetails_setGlobal() throws Exception {
+ Settings.get().setGlobal("RestContext.problemDetails", "true");
+ assertTrue(this.<Boolean>fld(ctx(), "defaultProblemDetails"));
+ }
+
+ // -------------------- defaultClientVersionHeader (String)
--------------------
+
+ @Test
+ void f01_clientVersionHeader_set() throws Exception {
+ System.setProperty("RestContext.clientVersionHeader",
"X-API-Version");
+ assertEquals("X-API-Version", fld(ctx(),
"defaultClientVersionHeader"));
+ }
+
+ @Test
+ void f02_clientVersionHeader_unset() throws Exception {
+ assertEquals("Client-Version", fld(ctx(),
"defaultClientVersionHeader"));
+ }
+
+ @Test
+ void f03_clientVersionHeader_setGlobal() throws Exception {
+ Settings.get().setGlobal("RestContext.clientVersionHeader",
"X-Global-Version");
+ assertEquals("X-Global-Version", fld(ctx(),
"defaultClientVersionHeader"));
+ }
+
+ // -------------------- defaultUriAuthority (Optional<String>)
--------------------
+
+ @Test
+ void g01_uriAuthority_set() throws Exception {
+ System.setProperty("RestContext.uriAuthority",
"https://example.org");
+ Optional<String> v = fld(ctx(), "defaultUriAuthority");
+ assertEquals(Optional.of("https://example.org"), v);
+ }
+
+ @Test
+ void g02_uriAuthority_unset() throws Exception {
+ Optional<String> v = fld(ctx(), "defaultUriAuthority");
+ assertTrue(v.isEmpty());
+ }
+
+ @Test
+ void g03_uriAuthority_setGlobal() throws Exception {
+ Settings.get().setGlobal("RestContext.uriAuthority",
"https://global.example.org");
+ Optional<String> v = fld(ctx(), "defaultUriAuthority");
+ assertEquals(Optional.of("https://global.example.org"), v);
+ }
+
+ // -------------------- defaultUriContext (Optional<String>)
--------------------
+
+ @Test
+ void h01_uriContext_set() throws Exception {
+ System.setProperty("RestContext.uriContext", "/api");
+ Optional<String> v = fld(ctx(), "defaultUriContext");
+ assertEquals(Optional.of("/api"), v);
+ }
+
+ @Test
+ void h02_uriContext_unset() throws Exception {
+ Optional<String> v = fld(ctx(), "defaultUriContext");
+ assertTrue(v.isEmpty());
+ }
+
+ @Test
+ void h03_uriContext_setGlobal() throws Exception {
+ Settings.get().setGlobal("RestContext.uriContext", "/global");
+ Optional<String> v = fld(ctx(), "defaultUriContext");
+ assertEquals(Optional.of("/global"), v);
+ }
+}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/rest/auth/jwt/JwtTokenValidator_ValueAdoption_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/rest/auth/jwt/JwtTokenValidator_ValueAdoption_Test.java
new file mode 100644
index 0000000000..6940add46e
--- /dev/null
+++
b/juneau-utest/src/test/java/org/apache/juneau/rest/auth/jwt/JwtTokenValidator_ValueAdoption_Test.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.auth.jwt;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.time.*;
+
+import org.apache.juneau.commons.settings.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * TODO-92 acceptance tests for {@code @Value}-driven defaults on {@link
JwtTokenValidator.Builder}.
+ *
+ * <p>
+ * 3-test triad per migrated field per OQA #4 — system property set, unset
(default), and {@code Settings.setGlobal}.
+ */
+class JwtTokenValidator_ValueAdoption_Test {
+
+ private static final String SP = "juneau.jwt.jwksCacheTtl";
+
+ @AfterEach
+ void cleanup() {
+ Settings.get().unsetGlobal(SP);
+ System.clearProperty(SP);
+ }
+
+ @Test
+ void a01_jwksCacheTtl_set() {
+ System.setProperty(SP, "PT10M");
+ assertEquals(Duration.ofMinutes(10),
JwtTokenValidator.create().jwksCacheTtl);
+ }
+
+ @Test
+ void a02_jwksCacheTtl_unset() {
+ assertEquals(Duration.ofMinutes(5),
JwtTokenValidator.create().jwksCacheTtl);
+ }
+
+ @Test
+ void a03_jwksCacheTtl_setGlobal() {
+ Settings.get().setGlobal(SP, "PT15M");
+ assertEquals(Duration.ofMinutes(15),
JwtTokenValidator.create().jwksCacheTtl);
+ }
+}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicVersionResource_ValueAdoption_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicVersionResource_ValueAdoption_Test.java
new file mode 100644
index 0000000000..dcb0f0c049
--- /dev/null
+++
b/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicVersionResource_ValueAdoption_Test.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.convention;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.apache.juneau.commons.settings.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * TODO-92 acceptance tests for the {@code @Value}-driven {@code java.version}
default on
+ * {@link BasicVersionResource.Builder}.
+ *
+ * <p>
+ * 3-test triad per migrated field per OQA #4 — system property set, unset
(default = JVM-resolved),
+ * and {@code Settings.setGlobal} override.
+ *
+ * <p>
+ * Note: the unset case for this builder always yields a non-blank value
because {@code java.version}
+ * is set by the JVM itself; the assertion simply verifies that the
JVM-supplied value is what flows
+ * through {@code @Value} resolution.
+ */
+class BasicVersionResource_ValueAdoption_Test {
+
+ private static final String SP = "java.version";
+
+ private String saved;
+
+ @BeforeEach
+ void capture() {
+ saved = System.getProperty(SP);
+ }
+
+ @AfterEach
+ void cleanup() {
+ Settings.get().unsetGlobal(SP);
+ if (saved != null)
+ System.setProperty(SP, saved);
+ else
+ System.clearProperty(SP);
+ }
+
+ @Test
+ void a01_javaVersion_set() {
+ System.setProperty(SP, "todo92-set");
+ var v =
BasicVersionResource.create().fromJavaVersion().build().getInfoMap();
+ assertEquals("todo92-set", v.get("javaVersion"));
+ }
+
+ @Test
+ void a02_javaVersion_unset() {
+ var v =
BasicVersionResource.create().fromJavaVersion().build().getInfoMap();
+ assertEquals(saved, v.get("javaVersion"));
+ }
+
+ @Test
+ void a03_javaVersion_setGlobal() {
+ Settings.get().setGlobal(SP, "todo92-global");
+ var v =
BasicVersionResource.create().fromJavaVersion().build().getInfoMap();
+ assertEquals("todo92-global", v.get("javaVersion"));
+ }
+}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/rest/logger/CallLogger_ValueAdoption_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/rest/logger/CallLogger_ValueAdoption_Test.java
new file mode 100644
index 0000000000..da994341de
--- /dev/null
+++
b/juneau-utest/src/test/java/org/apache/juneau/rest/logger/CallLogger_ValueAdoption_Test.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.logger;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.List;
+import java.util.logging.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.TestBase;
+import org.apache.juneau.commons.inject.*;
+import org.apache.juneau.commons.settings.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * TODO-92 acceptance tests for {@code @Value}-driven defaults on {@link
CallLogger.Builder}.
+ *
+ * <p>
+ * 3-test triad per migrated field per OQA #4 — system property set, unset
(default), and {@code Settings.setGlobal}.
+ */
+class CallLogger_ValueAdoption_Test extends TestBase {
+
+ private static final String SP_LOGGER = "juneau.restLogger.logger";
+ private static final String SP_ENABLED = "juneau.restLogger.enabled";
+ private static final String SP_REQUEST_DETAIL =
"juneau.restLogger.requestDetail";
+ private static final String SP_RESPONSE_DETAIL =
"juneau.restLogger.responseDetail";
+ private static final String SP_LEVEL = "juneau.restLogger.level";
+
+ private static final List<String> ALL_PROPS = List.of(
+ SP_LOGGER, SP_ENABLED, SP_REQUEST_DETAIL, SP_RESPONSE_DETAIL,
SP_LEVEL);
+
+ @AfterEach
+ void cleanup() {
+ var s = Settings.get();
+ for (var k : ALL_PROPS) {
+ s.unsetGlobal(k);
+ System.clearProperty(k);
+ }
+ }
+
+ private CallLogger.Builder build() {
+ return CallLogger.create(new BasicBeanStore(null));
+ }
+
+ // -------------------- enabled --------------------
+
+ @Test
+ void a01_enabled_set() {
+ System.setProperty(SP_ENABLED, "NEVER");
+ assertEquals(Enablement.NEVER, build().enabled);
+ }
+
+ @Test
+ void a02_enabled_unset() {
+ assertEquals(Enablement.ALWAYS, build().enabled);
+ }
+
+ @Test
+ void a03_enabled_setGlobal() {
+ Settings.get().setGlobal(SP_ENABLED, "CONDITIONAL");
+ assertEquals(Enablement.CONDITIONAL, build().enabled);
+ }
+
+ // -------------------- requestDetail --------------------
+
+ @Test
+ void b01_requestDetail_set() {
+ System.setProperty(SP_REQUEST_DETAIL, "HEADER");
+ assertEquals(CallLoggingDetail.HEADER, build().requestDetail);
+ }
+
+ @Test
+ void b02_requestDetail_unset() {
+ assertEquals(CallLoggingDetail.STATUS_LINE,
build().requestDetail);
+ }
+
+ @Test
+ void b03_requestDetail_setGlobal() {
+ Settings.get().setGlobal(SP_REQUEST_DETAIL, "ENTITY");
+ assertEquals(CallLoggingDetail.ENTITY, build().requestDetail);
+ }
+
+ // -------------------- responseDetail --------------------
+
+ @Test
+ void c01_responseDetail_set() {
+ System.setProperty(SP_RESPONSE_DETAIL, "HEADER");
+ assertEquals(CallLoggingDetail.HEADER, build().responseDetail);
+ }
+
+ @Test
+ void c02_responseDetail_unset() {
+ assertEquals(CallLoggingDetail.STATUS_LINE,
build().responseDetail);
+ }
+
+ @Test
+ void c03_responseDetail_setGlobal() {
+ Settings.get().setGlobal(SP_RESPONSE_DETAIL, "ENTITY");
+ assertEquals(CallLoggingDetail.ENTITY, build().responseDetail);
+ }
+
+ // -------------------- level --------------------
+
+ @Test
+ void d01_level_set() {
+ System.setProperty(SP_LEVEL, "WARNING");
+ assertEquals(Level.WARNING, build().level);
+ }
+
+ @Test
+ void d02_level_unset() {
+ assertEquals(Level.OFF, build().level);
+ }
+
+ @Test
+ void d03_level_setGlobal() {
+ Settings.get().setGlobal(SP_LEVEL, "INFO");
+ assertEquals(Level.INFO, build().level);
+ }
+
+ // -------------------- logger (name -> Logger via @Inject method)
--------------------
+
+ @Test
+ void e01_logger_set() {
+ System.setProperty(SP_LOGGER, "TODO92.set");
+ var logger = build().logger;
+ assertNotNull(logger);
+ assertEquals("TODO92.set", logger.getName());
+ }
+
+ @Test
+ void e02_logger_unset() {
+ var logger = build().logger;
+ assertNotNull(logger);
+ assertEquals("global", logger.getName());
+ }
+
+ @Test
+ void e03_logger_setGlobal() {
+ Settings.get().setGlobal(SP_LOGGER, "TODO92.global");
+ var logger = build().logger;
+ assertNotNull(logger);
+ assertEquals("TODO92.global", logger.getName());
+ }
+}