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 54f170ef8c feat(rest): hard-break debug model to typed @Debug config
and migrate tests/docs
54f170ef8c is described below
commit 54f170ef8c8220e598727608818af17a9e8ec441
Author: James Bognar <[email protected]>
AuthorDate: Mon May 25 09:09:04 2026 -0400
feat(rest): hard-break debug model to typed @Debug config and migrate
tests/docs
---
.../java/org/apache/juneau/rest/RestContext.java | 74 +-
.../java/org/apache/juneau/rest/RestOpContext.java | 20 +-
.../java/org/apache/juneau/rest/RestRequest.java | 68 +-
.../java/org/apache/juneau/rest/RestSession.java | 2 +
.../org/apache/juneau/rest/annotation/Debug.java | 75 ++
.../juneau/rest/annotation/DebugAnnotation.java | 75 ++
.../org/apache/juneau/rest/annotation/Rest.java | 154 +--
.../juneau/rest/annotation/RestAnnotation.java | 61 +-
.../org/apache/juneau/rest/annotation/RestOp.java | 2 +-
.../juneau/rest/annotation/RestOpAnnotation.java | 19 +-
.../apache/juneau/rest/config/DefaultConfig.java | 5 +-
.../juneau/rest/debug/BasicDebugEnablement.java | 4 +-
.../org/apache/juneau/rest/debug/DebugConfig.java | 207 ++++
.../org/apache/juneau/rest/debug/DebugFormat.java | 34 +
.../juneau/rest/debug/DebugFormatContext.java | 39 +
.../org/apache/juneau/rest/debug/DebugResult.java | 30 +
.../org/apache/juneau/rest/debug/DebugRule.java | 178 +++
.../juneau/rest/debug/format/BasicTextFormat.java | 36 +
.../juneau/rest/debug/format/CapturingFormat.java | 45 +
.../juneau/rest/debug/format/JsonFormat.java | 36 +
.../juneau/rest/debug/format/OneLineFormat.java | 33 +
.../org/apache/juneau/rest/logger/CallLogger.java | 12 +
.../rest/RestOpContext_OpLevelOverrides_Test.java | 2 +-
.../rest/annotation/RestAnnotation_Test.java | 20 +-
.../rest/annotation/RestOpAnnotation_Test.java | 6 +-
.../annotation/Rest_BeanCreatorOverrides_Test.java | 28 +-
.../juneau/rest/annotation/Rest_Debug_Test.java | 1155 +-------------------
.../mixin/MixinInheritance_DebugDefault_Test.java | 88 +-
.../MixinInheritance_DebugEnablement_Test.java | 92 +-
.../rest/ops/BasicEchoResource_AsMixin_Test.java | 14 +-
.../BasicEchoResource_JettyMicroservice_Test.java | 4 +-
.../ops/BasicEchoResource_Springboot_Test.java | 4 +-
.../rest/ops/BasicOps_OpenApiHidden_Test.java | 2 +-
.../juneau/rest/ops/BasicOps_ParentChain_Test.java | 4 +-
...ethink.md => FINISHED-20-rest-debug-rethink.md} | 0
todo/TODO-79-value-annotation-config-bridge.md | 114 ++
todo/TODO.md | 4 +-
37 files changed, 1222 insertions(+), 1524 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 b19acbcae3..cdf9911d96 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
@@ -81,6 +81,7 @@ import org.apache.juneau.rest.annotation.*;
import org.apache.juneau.rest.arg.*;
import org.apache.juneau.rest.config.*;
import org.apache.juneau.rest.debug.*;
+import org.apache.juneau.rest.debug.format.*;
import org.apache.juneau.rest.httppart.*;
import org.apache.juneau.rest.logger.*;
import org.apache.juneau.rest.processor.*;
@@ -703,6 +704,7 @@ public class RestContext extends Context {
bs.addDefaultSupplier(StaticFiles.class, staticFiles::get);
bs.addDefaultSupplier(FileFinder.class, staticFiles::get);
bs.addDefaultSupplier(DebugEnablement.class,
debugEnablement::get);
+ bs.addDefaultSupplier(DebugConfig.class, debugConfig::get);
bs.addDefaultSupplier(SwaggerProvider.class,
swaggerProvider::get);
bs.addDefaultSupplier(OpenApiProvider.class,
openApiProvider::get);
bs.addDefaultSupplier(RestOperations.class,
restOperations::get);
@@ -893,16 +895,66 @@ public class RestContext extends Context {
bs.addBean(Enablement.class, isDebug() ?
Enablement.ALWAYS : Enablement.NEVER);
var creator = BeanInstantiator.of(DebugEnablement.class,
bs).type(BasicDebugEnablement.class).noBuilder();
bs.getBeanType(DebugEnablement.class).ifPresent(creator::type);
- // @Rest(debugEnablement=X) — most-derived non-Void wins. See
callLogger for the reduce-last rationale.
- getRestAnnotationsForProperty(PROPERTY_debugEnablement)
- .map(ai -> ai.inner().debugEnablement())
- .filter(c -> c != DebugEnablement.Void.class)
- .reduce((first, second) -> second)
- .ifPresent(creator::type);
bs.createBeanFromMethod(DebugEnablement.class,
resource().get(), RestContext::isBeanMethod).ifPresent(creator::impl);
return creator.asOptional().orElse(null);
});
+ /**
+ * The {@link DebugConfig} for this resource.
+ */
+ private final Memoizer<DebugConfig> debugConfig = memoizer(() -> {
+ var bs = beanStore();
+ var mode = getRestAnnotationsForProperty(PROPERTY_debug)
+ .map(ai -> resolve(ai.inner().debug().value()))
+ .filter(StringUtils::isNotBlank)
+ .reduce((first, second) -> second)
+ .orElse("");
+ var formatType = getRestAnnotationsForProperty(PROPERTY_debug)
+ .map(ai -> ai.inner().debug().format())
+ .filter(c -> c != DebugFormat.Void.class)
+ .reduce((first, second) -> second)
+ .orElse(null);
+ var levelStr = getRestAnnotationsForProperty(PROPERTY_debug)
+ .map(ai -> resolve(ai.inner().debug().level()))
+ .filter(StringUtils::isNotBlank)
+ .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 mode2 = mode;
+ return new DebugConfig(bs) {
+ @Override
+ public DebugResult resolve(RestContext context,
HttpServletRequest req) {
+ var enabled = isTrue(cast(Boolean.class,
req.getAttribute("Debug")));
+ if (!enabled) {
+ if ("always".equalsIgnoreCase(mode2) ||
"true".equalsIgnoreCase(mode2))
+ enabled = true;
+ else if
("conditional".equalsIgnoreCase(mode2))
+ enabled =
"true".equalsIgnoreCase(req.getHeader("Debug"));
+ }
+ var cacheBodies = enabled;
+ return new DebugResult(enabled, format, level,
cacheBodies);
+ }
+
+ @Override
+ public DebugResult resolve(RestOpContext context,
HttpServletRequest req) {
+ var opDebug =
AnnotationProvider.INSTANCE.find(RestOp.class,
ClassInfo.of(context.getJavaMethod())).stream().findFirst();
+ if (opDebug.isPresent()) {
+ var v =
RestContext.this.resolve(opDebug.get().inner().debug().value());
+ if (StringUtils.isNotBlank(v)) {
+ if
("always".equalsIgnoreCase(v) || "true".equalsIgnoreCase(v))
+ return new
DebugResult(true, format, level, true);
+ if ("never".equalsIgnoreCase(v)
|| "false".equalsIgnoreCase(v))
+ return new
DebugResult(false, format, level, false);
+ if
("conditional".equalsIgnoreCase(v))
+ return new
DebugResult("true".equalsIgnoreCase(req.getHeader("Debug")), format, level,
true);
+ }
+ }
+ return resolve(context.getContext(), req);
+ }
+ };
+ });
+
/**
* The default request attributes contributed by {@code
@Rest(defaultRequestAttributes)} for this resource.
*
@@ -2674,6 +2726,13 @@ public class RestContext extends Context {
*/
public DebugEnablement getDebugEnablement() { return
beanStore.getBean(DebugEnablement.class).orElse(null); }
+ /**
+ * Returns the debug configuration bean for this context.
+ *
+ * @return The debug configuration bean for this context.
+ */
+ public DebugConfig getDebugConfig() { return
beanStore.getBean(DebugConfig.class).orElse(null); }
+
/**
* Returns the default request attributes for this resource.
*
@@ -3314,7 +3373,7 @@ public class RestContext extends Context {
}
private boolean isDebug(RestSession call) {
- return getDebugEnablement().isDebug(this, call.getRequest());
+ return getDebugConfig().resolve(this,
call.getRequest()).enabled();
}
/**
@@ -3342,6 +3401,7 @@ public class RestContext extends Context {
getDefaultResponseHeaders();
getDefaultRequestAttributes();
getDebugEnablement();
+ getDebugConfig();
getSwaggerProvider();
getOpenApiProvider();
}
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 c747e5842b..e614c2ee72 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
@@ -275,15 +275,8 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
return (nn(override) ? override : b.build()).asArray();
});
- /** The effective {@link DebugEnablement} for this operation. */
- private final Memoizer<DebugEnablement> debugEnablement = memoizer(()
-> {
- var v = findOpString(PROPERTY_debug);
- if (v.isPresent())
- return
DebugEnablement.create(beanStore()).enable(Enablement.fromString(v.get()),
"*").build();
- if (isInherited(PROPERTY_debug))
- return restContext().getDebugEnablement();
- return DebugEnablement.create(beanStore()).build();
- });
+ /** The effective {@link DebugConfig} for this operation. */
+ private final Memoizer<DebugConfig> debugConfig = memoizer(() ->
restContext().getDebugConfig());
/** The effective default {@link Charset} for this operation, resolved
from op annotations, context, or env. */
private final Memoizer<Charset> defaultCharset = memoizer(() -> {
@@ -1277,7 +1270,7 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
"java:S112" // throws Exception intentional -
callback/lifecycle method
})
public RestOpSession.Builder createSession(RestSession session) throws
Exception {
- return RestOpSession.create(this,
session).logger(getCallLogger()).debug(debugEnablement.get().isDebug(this,
session.getRequest()));
+ return RestOpSession.create(this,
session).logger(getCallLogger()).debug(debugConfig.get().resolve(this,
session.getRequest()).enabled());
}
@Override /* Overridden from Object */
@@ -1446,6 +1439,13 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
*/
public CallLogger getCallLogger() { return callLogger.get(); }
+ /**
+ * Returns the debug config for this operation.
+ *
+ * @return The debug config for this operation.
+ */
+ public DebugConfig getDebugConfig() { return debugConfig.get(); }
+
/**
* Returns metadata about the specified response object if it's
annotated with {@link Response @Response}.
*
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
index d1426cc1ba..82350d3519 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
@@ -63,6 +63,7 @@ import org.apache.juneau.commons.httppart.*;
import org.apache.juneau.httppart.bean.*;
import org.apache.juneau.rest.annotation.*;
import org.apache.juneau.rest.assertions.*;
+import org.apache.juneau.rest.debug.format.*;
import org.apache.juneau.rest.guard.*;
import org.apache.juneau.rest.httppart.*;
import org.apache.juneau.rest.logger.*;
@@ -1582,6 +1583,70 @@ public class RestRequest extends
HttpServletRequestWrapper {
*/
public boolean isDebug() { return
getAttribute("Debug").as(Boolean.class).orElse(false); }
+ /**
+ * Returns a fluent debug scope for this request.
+ *
+ * @return A debug scope.
+ */
+ public DebugScope debug() {
+ return new DebugScope(this);
+ }
+
+ /**
+ * Request-scoped debug controls.
+ */
+ public static class DebugScope {
+ private final RestRequest req;
+
+ DebugScope(RestRequest req) {
+ this.req = req;
+ }
+
+ /**
+ * Enables debug.
+ *
+ * @return This object.
+ * @throws IOException If debug could not be enabled.
+ */
+ public DebugScope enable() throws IOException {
+ req.setDebug(true);
+ return this;
+ }
+
+ /**
+ * Enables debug with a capturing format marker.
+ *
+ * @param formatType The format type.
+ * @return This object.
+ * @throws IOException If debug could not be enabled.
+ */
+ public DebugScope enable(Class<?> formatType) throws
IOException {
+ req.setDebug(true);
+ req.setAttribute("DebugFormatType", formatType == null
? BasicTextFormat.class : formatType);
+ return this;
+ }
+
+ /**
+ * Disables debug.
+ *
+ * @return This object.
+ * @throws IOException If debug could not be disabled.
+ */
+ public DebugScope disable() throws IOException {
+ req.setDebug(false);
+ return this;
+ }
+
+ /**
+ * Returns whether debug is enabled.
+ *
+ * @return Whether debug is enabled.
+ */
+ public boolean isEnabled() {
+ return req.isDebug();
+ }
+ }
+
/**
* Returns <jk>true</jk> if <c>&plainText=true</c> was specified as
a URL parameter.
*
@@ -1815,7 +1880,8 @@ public class RestRequest extends
HttpServletRequestWrapper {
* @throws IOException If content could not be cached.
*/
public RestRequest setDebug() throws IOException {
- return setDebug(true);
+ debug().enable();
+ return this;
}
/**
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestSession.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestSession.java
index f6aebf4e8e..41866f1cf9 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestSession.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestSession.java
@@ -294,6 +294,8 @@ public class RestSession extends ContextSession {
} catch (Exception e) {
exception(e);
}
+ if (nn(logger))
+ req.setAttribute("DebugConfig", opSession != null ?
opSession.getContext().getDebugConfig() : context.getDebugConfig());
if (nn(logger))
logger.log(req, res);
return this;
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Debug.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Debug.java
new file mode 100644
index 0000000000..26555484d0
--- /dev/null
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Debug.java
@@ -0,0 +1,75 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.*;
+
+import java.lang.annotation.*;
+
+import org.apache.juneau.rest.debug.*;
+
+/**
+ * Debug configuration metadata for {@link Rest @Rest} and {@link RestOp
@RestOp}.
+ */
+@Target({TYPE,METHOD})
+@Retention(RUNTIME)
+@Inherited
+public @interface Debug {
+
+ /**
+ * Debug enablement policy.
+ *
+ * <ul class='values'>
+ * <li><js>"always"</js> (or <js>"true"</js>)
+ * <li><js>"never"</js> (or <js>"false"</js>)
+ * <li><js>"conditional"</js>
+ * <li><js>""</js> (inherit)
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String value() default "";
+
+ /**
+ * Optional format class.
+ *
+ * @return The annotation value.
+ */
+ Class<? extends DebugFormat> format() default DebugFormat.Void.class;
+
+ /**
+ * Optional JUL level string parsed by {@link
java.util.logging.Level#parse(String)}.
+ *
+ * @return The annotation value.
+ */
+ String level() default "";
+
+ /**
+ * Optional endpoint override list.
+ *
+ * @return The annotation value.
+ */
+ String on() default "";
+
+ /**
+ * Optional debug configuration override class.
+ *
+ * @return The annotation value.
+ */
+ Class<? extends DebugConfig> config() default DebugConfig.Void.class;
+}
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/DebugAnnotation.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/DebugAnnotation.java
new file mode 100644
index 0000000000..1f5f5b70ad
--- /dev/null
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/DebugAnnotation.java
@@ -0,0 +1,75 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.*;
+
+import org.apache.juneau.commons.annotation.*;
+import org.apache.juneau.rest.debug.*;
+
+/**
+ * Utility classes and methods for {@link Debug}.
+ */
+public final class DebugAnnotation {
+
+ private DebugAnnotation() {}
+
+ public static class Builder extends AnnotationObject.Builder {
+ private String value = "";
+ private Class<? extends DebugFormat> format =
DebugFormat.Void.class;
+ private String level = "";
+ private String on = "";
+ private Class<? extends DebugConfig> config =
DebugConfig.Void.class;
+
+ protected Builder() {
+ super(Debug.class);
+ }
+
+ public Builder value(String value) { this.value = value; return
this; }
+ public Builder format(Class<? extends DebugFormat> value) {
this.format = value; return this; }
+ public Builder level(String value) { this.level = value; return
this; }
+ public Builder on(String value) { this.on = value; return this;
}
+ public Builder config(Class<? extends DebugConfig> value) {
this.config = value; return this; }
+ public Debug build() { return new Object(this); }
+ }
+
+ private static class Object extends AnnotationObject implements Debug {
+ private final String value;
+ private final Class<? extends DebugFormat> format;
+ private final String level;
+ private final String on;
+ private final Class<? extends DebugConfig> config;
+
+ Object(DebugAnnotation.Builder b) {
+ super(b);
+ value = b.value;
+ format = b.format;
+ level = b.level;
+ on = b.on;
+ config = b.config;
+ }
+
+ @Override public String value() { return value; }
+ @Override public Class<? extends DebugFormat> format() { return
format; }
+ @Override public String level() { return level; }
+ @Override public String on() { return on; }
+ @Override public Class<? extends DebugConfig> config() { return
config; }
+ }
+
+ public static Builder create() { return new Builder(); }
+ public static final Debug DEFAULT = create().build();
+}
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java
index 263056c23a..aec2046e0d 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java
@@ -32,7 +32,6 @@ import org.apache.juneau.parser.*;
import org.apache.juneau.rest.*;
import org.apache.juneau.rest.arg.*;
import org.apache.juneau.rest.converter.*;
-import org.apache.juneau.rest.debug.*;
import org.apache.juneau.rest.guard.*;
import org.apache.juneau.rest.logger.*;
import org.apache.juneau.rest.openapi.*;
@@ -464,158 +463,7 @@ public @interface Rest {
*
* @return The annotation value.
*/
- String debug() default "";
-
- /**
- * Debug enablement bean.
- *
- * <p>
- * Specifies a custom {@link
org.apache.juneau.rest.debug.DebugEnablement} implementation class to use for
determining whether debug mode is enabled.
- * This allows for more sophisticated control over when debug features
are enabled beyond simple boolean flags.
- *
- * <h5 class='section'>See Also:</h5><ul>
- * <li class='jm'>{@link
org.apache.juneau.rest.RestContext#getDebugEnablement()}
- * </ul>
- *
- * @return The annotation value.
- */
- Class<? extends DebugEnablement> debugEnablement() default
DebugEnablement.Void.class;
-
- /**
- * Default debug enablement value.
- *
- * <p>
- * Specifies the default {@link org.apache.juneau.Enablement} value
used by {@link DebugEnablement} when no
- * resource-class or operation-method debug setting is in effect.
Accepts the standard enablement names —
- * {@code "ALWAYS"}, {@code "NEVER"}, {@code "CONDITIONAL"}
(case-insensitive). An empty string (the default)
- * means "no override" — {@link DebugEnablement} falls back to {@link
org.apache.juneau.Enablement#ALWAYS} when
- * {@link Rest#debug()} is set, otherwise {@link
org.apache.juneau.Enablement#NEVER}.
- *
- * <p>
- * Replaces the deleted {@code
RestContext.Builder.debugDefault(Enablement)} method.
- *
- * <h5 class='section'>See Also:</h5><ul>
- * <li class='jm'>{@link
org.apache.juneau.rest.RestContext#getDebugEnablement()}
- * </ul>
- *
- * @return The annotation value.
- */
- String debugDefault() default "";
-
- /**
- * Enable debug mode on specified classes/methods.
- *
- * <p>
- * Enables the following:
- * <ul class='spaced-list'>
- * <li>
- * HTTP request/response bodies are cached in memory for
logging purposes on matching classes and methods.
- * <li>
- * HTTP requests/responses are logged to the registered
{@link CallLogger}.
- * </ul>
- *
- * <p>
- * Consists of a comma-delimited list of strings of the following forms:
- * <ul>
- * <li><js>"class-identifier"</js> - Enable debug on the specified
class.
- * <li><js>"class-identifier=[true|false|conditional]"</js> -
Explicitly enable debug on the specified class.
- * <li><js>"method-identifier"</js> - Enable debug on the
specified class.
- * <li><js>"method-identifier=[true|false|conditional]"</js> -
Explicitly enable debug on the specified class.
- * </ul>
- *
- * <p>
- * Class identifiers can be any of the following forms:
- * <ul>
- * <li>Fully qualified:
- * <ul>
- * <li><js>"com.foo.MyClass"</js>
- * </ul>
- * <li>Fully qualified inner class:
- * <ul>
- * <li><js>"com.foo.MyClass$Inner1$Inner2"</js>
- * </ul>
- * <li>Simple:
- * <ul>
- * <li><js>"MyClass"</js>
- * </ul>
- * <li>Simple inner:
- * <ul>
- * <li><js>"MyClass$Inner1$Inner2"</js>
- * <li><js>"Inner1$Inner2"</js>
- * <li><js>"Inner2"</js>
- * </ul>
- * </ul>
- *
- * <p>
- * Method identifiers can be any of the following forms:
- * <ul>
- * <li>Fully qualified with args:
- * <ul>
- *
<li><js>"com.foo.MyClass.myMethod(String,int)"</js>
- *
<li><js>"com.foo.MyClass.myMethod(java.lang.String,int)"</js>
- * <li><js>"com.foo.MyClass.myMethod()"</js>
- * </ul>
- * <li>Fully qualified:
- * <ul>
- * <li><js>"com.foo.MyClass.myMethod"</js>
- * </ul>
- * <li>Simple with args:
- * <ul>
- * <li><js>"MyClass.myMethod(String,int)"</js>
- *
<li><js>"MyClass.myMethod(java.lang.String,int)"</js>
- * <li><js>"MyClass.myMethod()"</js>
- * </ul>
- * <li>Simple:
- * <ul>
- * <li><js>"MyClass.myMethod"</js>
- * </ul>
- * <li>Simple inner class:
- * <ul>
- * <li><js>"MyClass$Inner1$Inner2.myMethod"</js>
- * <li><js>"Inner1$Inner2.myMethod"</js>
- * <li><js>"Inner2.myMethod"</js>
- * </ul>
- * </ul>
- *
- * <h5 class='figure'>Example:</h5>
- * <p class='bjava'>
- * <jc>// Turn on debug per-request on the class and always on the
doX() method</jc>.
- * <ja>@Rest</ja>(
- *
debugOn=<js>"MyResource=conditional,MyResource.doX=true"</js>
- * )
- * <jk>public class</jk> MyResource {
- *
- * <ja>@RestGet</ja>
- * <jk>public void</jk> String getX() {
- * ...
- * }
- * </p>
- *
- * <p>
- * A more-typical scenario is to pull this setting from an external
source such as system property or environment
- * variable:
- *
- * <h5 class='figure'>Example:</h5>
- * <p class='bjava'>
- * <ja>@Rest</ja>(
- * debugOn=<js>"$E{DEBUG_ON_SETTINGS}"</js>
- * )
- * <jk>public class</jk> MyResource {...}
- * </p>
- *
- * <h5 class='section'>Notes:</h5><ul>
- * <li class='note'>
- * Supports <a class="doclink"
href="https://juneau.apache.org/docs/topics/RestServerSvlVariables">SVL
Variables</a>
- * (e.g. <js>"$L{my.localized.variable}"</js>).
- * <li class='note'>
- * These debug settings override the settings define via
{@link Rest#debug()} and {@link RestOp#debug()}.
- * <li class='note'>
- * These debug settings can be overridden at runtime by
directly calling {@link RestRequest#setDebug()}.
- * </ul>
- *
- * @return The annotation value.
- */
- String debugOn() default "";
+ Debug debug() default @Debug;
/**
* Default <c>Accept</c> header.
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestAnnotation.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestAnnotation.java
index d90d2f446f..0ae4bc41b4 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestAnnotation.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestAnnotation.java
@@ -25,7 +25,6 @@ import org.apache.juneau.httppart.*;
import org.apache.juneau.commons.annotation.*;
import org.apache.juneau.rest.arg.*;
import org.apache.juneau.rest.converter.*;
-import org.apache.juneau.rest.debug.*;
import org.apache.juneau.rest.guard.*;
import org.apache.juneau.rest.logger.*;
import org.apache.juneau.rest.openapi.*;
@@ -72,7 +71,7 @@ public class RestAnnotation {
private Class<? extends SwaggerProvider> swaggerProvider =
SwaggerProvider.Void.class;
private Class<? extends OpenApiProvider> openApiProvider =
OpenApiProvider.Void.class;
private Class<? extends RestOpArg>[] restOpArgs = new Class[0];
- private Class<? extends DebugEnablement> debugEnablement =
DebugEnablement.Void.class;
+ private Debug debug = DebugAnnotation.DEFAULT;
private Class<? extends Serializer>[] serializers = new
Class[0];
private Class<?>[] children = {};
private Class<?>[] mixins = {};
@@ -85,9 +84,6 @@ public class RestAnnotation {
private String clientVersionHeader = "";
private String config = "";
private String eagerInit = "";
- private String debug = "";
- private String debugDefault = "";
- private String debugOn = "";
private String defaultAccept = "";
private String defaultCharset = "";
private String defaultContentType = "";
@@ -307,40 +303,18 @@ public class RestAnnotation {
* @return This object.
*/
public Builder debug(String value) {
- debug = value;
+ debug = DebugAnnotation.create().value(value).build();
return this;
}
/**
- * Sets the {@link Rest#debugEnablement()} property on this
annotation.
- *
- * @param value The new value for this property.
- * @return This object.
- */
- public Builder debugEnablement(Class<? extends DebugEnablement>
value) {
- debugEnablement = value;
- return this;
- }
-
- /**
- * Sets the {@link Rest#debugDefault()} property on this
annotation.
- *
- * @param value The new value for this property.
- * @return This object.
- */
- public Builder debugDefault(String value) {
- debugDefault = value;
- return this;
- }
-
- /**
- * Sets the {@link Rest#debugOn()} property on this annotation.
+ * Sets the {@link Rest#debug()} property on this annotation.
*
* @param value The new value for this property.
* @return This object.
*/
- public Builder debugOn(String value) {
- debugOn = value;
+ public Builder debug(Debug value) {
+ debug = value == null ? DebugAnnotation.DEFAULT : value;
return this;
}
@@ -742,7 +716,7 @@ public class RestAnnotation {
private final Class<? extends SwaggerProvider> swaggerProvider;
private final Class<? extends OpenApiProvider> openApiProvider;
private final Class<? extends RestOpArg>[] restOpArgs;
- private final Class<? extends DebugEnablement> debugEnablement;
+ private final Debug debug;
private final Class<? extends Serializer>[] serializers;
private final Class<?>[] children;
private final Class<?>[] mixins;
@@ -755,9 +729,6 @@ public class RestAnnotation {
private final String clientVersionHeader;
private final String config;
private final String eagerInit;
- private final String debug;
- private final String debugDefault;
- private final String debugOn;
private final String defaultAccept;
private final String defaultCharset;
private final String defaultContentType;
@@ -807,9 +778,6 @@ public class RestAnnotation {
consumes = copyOf(b.consumes);
converters = copyOf(b.converters);
debug = b.debug;
- debugDefault = b.debugDefault;
- debugEnablement = b.debugEnablement;
- debugOn = b.debugOn;
defaultAccept = b.defaultAccept;
defaultCharset = b.defaultCharset;
defaultContentType = b.defaultContentType;
@@ -920,25 +888,10 @@ public class RestAnnotation {
}
@Override /* Overridden from Rest */
- public String debug() {
+ public Debug debug() {
return debug;
}
- @Override /* Overridden from Rest */
- public Class<? extends DebugEnablement> debugEnablement() {
- return debugEnablement;
- }
-
- @Override /* Overridden from Rest */
- public String debugDefault() {
- return debugDefault;
- }
-
- @Override /* Overridden from Rest */
- public String debugOn() {
- return debugOn;
- }
-
@Override /* Overridden from Rest */
public String defaultAccept() {
return defaultAccept;
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOp.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOp.java
index 94811c04ba..3aa99f1f88 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOp.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOp.java
@@ -174,7 +174,7 @@ public @interface RestOp {
*
* @return The annotation value.
*/
- String debug() default "";
+ Debug debug() default @Debug;
/**
* Default <c>Accept</c> header.
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOpAnnotation.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOpAnnotation.java
index 0ef1aa0b07..d74f87abd9 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOpAnnotation.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOpAnnotation.java
@@ -65,7 +65,7 @@ public class RestOpAnnotation {
private Class<?>[] parsers = {};
private OpSwagger swagger = OpSwaggerAnnotation.DEFAULT;
private String clientVersion = "";
- private String debug = "";
+ private Debug debug = DebugAnnotation.DEFAULT;
private String defaultAccept = "";
private String defaultCharset = "";
private String defaultContentType = "";
@@ -156,7 +156,18 @@ public class RestOpAnnotation {
* @return This object.
*/
public Builder debug(String value) {
- debug = value;
+ debug = DebugAnnotation.create().value(value).build();
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestOp#debug()} property on this annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder debug(Debug value) {
+ debug = value == null ? DebugAnnotation.DEFAULT : value;
return this;
}
@@ -471,7 +482,7 @@ public class RestOpAnnotation {
private final Class<?>[] parsers;
private final OpSwagger swagger;
private final String clientVersion;
- private final String debug;
+ private final Debug debug;
private final String defaultAccept;
private final String defaultCharset;
private final String defaultContentType;
@@ -545,7 +556,7 @@ public class RestOpAnnotation {
}
@Override /* Overridden from RestOp */
- public String debug() {
+ public Debug debug() {
return debug;
}
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/DefaultConfig.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/DefaultConfig.java
index bbb1331804..7e29c7b07a 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/DefaultConfig.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/DefaultConfig.java
@@ -22,7 +22,6 @@ import org.apache.juneau.oapi.*;
import org.apache.juneau.rest.annotation.*;
import org.apache.juneau.rest.arg.*;
import org.apache.juneau.rest.auth.*;
-import org.apache.juneau.rest.debug.*;
import org.apache.juneau.rest.logger.*;
import org.apache.juneau.rest.openapi.*;
import org.apache.juneau.rest.processor.*;
@@ -101,8 +100,7 @@ import org.apache.juneau.serializer.annotation.*;
allowedMethodHeaders="$S{j.allowedMethodHeaders,$E{J_ALLOWED_METHOD_HEADERS,}}",
allowedMethodParams="$S{j.allowedMethodParams,$E{J_ALLOWED_METHOD_PARAMS,HEAD,OPTIONS}}",
clientVersionHeader="$S{j.clientVersionHeader,$E{J_CLIENT_VERSION_HEADER,Client-Version}}",
- debug="$S{j.debug,$E{J_DEBUG,}}",
- debugOn="$S{j.debugOn,$E{J_DEBUG_ON,}}",
+ debug=@Debug("$S{j.debug,$E{J_DEBUG,}}"),
defaultAccept="$S{j.defaultAccept,$E{J_DEFAULT_ACCEPT,}}",
defaultCharset="$S{j.defaultCharset,$E{J_DEFAULT_CHARSET,UTF-8}}",
defaultContentType="$S{j.defaultContentType,$E{J_DEFAULT_CONTENT_TYPE,}}",
@@ -128,7 +126,6 @@ import org.apache.juneau.serializer.annotation.*;
// Injectable/overridable beans.
callLogger=CallLogger.Void.class, // Defaults to BasicCallLogger.
- debugEnablement=DebugEnablement.Void.class, // Defaults to
BasicDefaultEnablement.
staticFiles=StaticFiles.Void.class, // Defaults to BasicStaticFiles.
swaggerProvider=SwaggerProvider.Void.class, // Defaults to
BasicSwaggerProvider.
openApiProvider=OpenApiProvider.Void.class // Defaults to
BasicOpenApiProvider.
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/BasicDebugEnablement.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/BasicDebugEnablement.java
index 66f8d6530d..1e31f549ab 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/BasicDebugEnablement.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/BasicDebugEnablement.java
@@ -79,7 +79,7 @@ public class BasicDebugEnablement extends DebugEnablement {
// Gather @Rest(debug) settings.
// @formatter:off
rstream(ap.find(Rest.class,
ci)).map(AnnotationInfo::inner).forEach(x -> {
- var x2 = varResolver.resolve(x.debug());
+ var x2 = varResolver.resolve(x.debug().value());
if (! x2.isEmpty())
b.enable(Enablement.fromString(x2),
ci.getNameFull());
});
@@ -103,7 +103,7 @@ public class BasicDebugEnablement extends DebugEnablement {
// Gather @Rest(debugOn) settings.
// @formatter:off
rstream(ap.find(Rest.class,
ci)).map(AnnotationInfo::inner).forEach(x -> {
- var x2 = varResolver.resolve(x.debugOn());
+ var x2 = varResolver.resolve(x.debug().on());
for (var e : splitMap(x2, true).entrySet()) {
var k = e.getKey();
var v = e.getValue();
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugConfig.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugConfig.java
new file mode 100644
index 0000000000..2cb15e0e21
--- /dev/null
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugConfig.java
@@ -0,0 +1,207 @@
+/*
+ * 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.debug;
+
+import static org.apache.juneau.commons.utils.CollectionUtils.*;
+import static org.apache.juneau.commons.utils.Utils.*;
+
+import java.lang.reflect.*;
+import java.util.function.*;
+import java.util.logging.*;
+
+import org.apache.juneau.commons.inject.*;
+import org.apache.juneau.rest.*;
+import org.apache.juneau.rest.debug.format.*;
+
+import jakarta.servlet.http.*;
+
+/**
+ * Debug configuration with class/method targeted rules.
+ */
+public class DebugConfig {
+
+ /** Represents no debug config. */
+ public abstract class Void extends DebugConfig {
+ Void(BeanStore beanStore) {
+ super(beanStore);
+ }
+ }
+
+ /**
+ * Builder class.
+ */
+ public static class Builder {
+
+ private final BeanStore beanStore;
+ private Predicate<HttpServletRequest> conditional = x ->
"true".equalsIgnoreCase(x.getHeader("Debug"));
+ private DebugFormat defaultFormat;
+ private Level defaultLevel = Level.INFO;
+ private boolean defaultCacheBodies = false;
+
+ /**
+ * Constructor.
+ *
+ * @param beanStore The bean store.
+ */
+ protected Builder(BeanStore beanStore) {
+ this.beanStore = beanStore;
+ defaultFormat =
beanStore.getBean(BasicTextFormat.class).orElseGet(() -> new BasicTextFormat());
+ }
+
+ /**
+ * Adds a rule.
+ *
+ * @param target The target key.
+ * @param value The rule spec.
+ * @return This object.
+ */
+ public Builder rule(String target, Consumer<DebugRule.Builder>
value) {
+ var b = DebugRule.create();
+ value.accept(b);
+ // TODO-20 Phase 2: Persist full DebugRule metadata per
target.
+ return this;
+ }
+
+ /**
+ * Sets default format.
+ *
+ * @param value The value.
+ * @return This object.
+ */
+ public Builder defaultFormat(DebugFormat value) {
+ defaultFormat = value;
+ return this;
+ }
+
+ /**
+ * Sets default level.
+ *
+ * @param value The value.
+ * @return This object.
+ */
+ public Builder defaultLevel(Level value) {
+ defaultLevel = value;
+ return this;
+ }
+
+ /**
+ * Sets conditional rule predicate.
+ *
+ * @param value The value.
+ * @return This object.
+ */
+ public Builder conditional(Predicate<HttpServletRequest> value)
{
+ conditional = value;
+ return this;
+ }
+
+ /**
+ * Sets default cache-body flag.
+ *
+ * @param value The value.
+ * @return This object.
+ */
+ public Builder defaultCacheBodies(boolean value) {
+ defaultCacheBodies = value;
+ return this;
+ }
+
+ /**
+ * Builds this object.
+ *
+ * @return A new object.
+ */
+ public DebugConfig build() {
+ return new DebugConfig(this);
+ }
+ }
+
+ /**
+ * Creates a builder.
+ *
+ * @param beanStore The bean store.
+ * @return A new builder.
+ */
+ public static Builder create(BeanStore beanStore) {
+ return new Builder(beanStore);
+ }
+
+ private final BeanStore beanStore;
+ private final Predicate<HttpServletRequest> conditional;
+ private final DebugFormat defaultFormat;
+ private final Level defaultLevel;
+ private final boolean defaultCacheBodies;
+
+ /**
+ * Constructor.
+ *
+ * @param beanStore The bean store.
+ */
+ public DebugConfig(BeanStore beanStore) {
+ this(create(beanStore));
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param builder The builder.
+ */
+ protected DebugConfig(Builder builder) {
+ beanStore = builder.beanStore;
+ conditional = builder.conditional;
+ defaultFormat = builder.defaultFormat;
+ defaultLevel = builder.defaultLevel;
+ defaultCacheBodies = builder.defaultCacheBodies;
+ }
+
+ /**
+ * Resolves debug for resource-level requests.
+ *
+ * @param context The context.
+ * @param req The request.
+ * @return A debug result.
+ */
+ public DebugResult resolve(RestContext context, HttpServletRequest req)
{
+ return resolveInternal(context == null ? null :
context.getResourceClass(), null, req);
+ }
+
+ /**
+ * Resolves debug for operation-level requests.
+ *
+ * @param context The context.
+ * @param req The request.
+ * @return A debug result.
+ */
+ public DebugResult resolve(RestOpContext context, HttpServletRequest
req) {
+ return resolveInternal(context.getContext().getResourceClass(),
context.getJavaMethod(), req);
+ }
+
+ private DebugResult resolveInternal(Class<?> resourceClass, Method
method, HttpServletRequest req) {
+ var enabled = false;
+ if (req != null) {
+ if (isTrue(cast(Boolean.class,
req.getAttribute("Debug"))))
+ enabled = true;
+ else
+ enabled = conditional.test(req);
+ }
+ var cacheBodies = defaultCacheBodies && enabled;
+ return new DebugResult(enabled, defaultFormat, defaultLevel,
cacheBodies);
+ }
+
+ /** Returns bean store. */
+ protected BeanStore beanStore() { return beanStore; }
+}
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugFormat.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugFormat.java
new file mode 100644
index 0000000000..cb379e209b
--- /dev/null
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugFormat.java
@@ -0,0 +1,34 @@
+/*
+ * 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.debug;
+
+/**
+ * Formatter abstraction for debug logging.
+ */
+public interface DebugFormat {
+
+ /** Represents no format. */
+ abstract class Void implements DebugFormat {}
+
+ /**
+ * Formats a debug log message.
+ *
+ * @param context The context.
+ * @return The formatted message.
+ */
+ String format(DebugFormatContext context);
+}
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugFormatContext.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugFormatContext.java
new file mode 100644
index 0000000000..be7dae16b0
--- /dev/null
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugFormatContext.java
@@ -0,0 +1,39 @@
+/*
+ * 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.debug;
+
+import jakarta.servlet.http.*;
+
+/**
+ * Input values used by {@link DebugFormat}.
+ *
+ * @param request The request.
+ * @param response The response.
+ * @param exception Exception on request, if present.
+ * @param execTime Execution time in millis, if present.
+ * @param requestContent Cached request content.
+ * @param responseContent Cached response content.
+ */
+public record DebugFormatContext(
+ HttpServletRequest request,
+ HttpServletResponse response,
+ Throwable exception,
+ Long execTime,
+ byte[] requestContent,
+ byte[] responseContent
+) {
+}
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugResult.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugResult.java
new file mode 100644
index 0000000000..690b2d1d5d
--- /dev/null
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugResult.java
@@ -0,0 +1,30 @@
+/*
+ * 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.debug;
+
+import java.util.logging.*;
+
+/**
+ * Resolved per-request debug decision.
+ *
+ * @param enabled Whether debug is enabled.
+ * @param format Formatter to use for output.
+ * @param level JUL level for output.
+ * @param cacheBodies Whether request/response bodies should be cached.
+ */
+public record DebugResult(boolean enabled, DebugFormat format, Level level,
boolean cacheBodies) {
+}
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugRule.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugRule.java
new file mode 100644
index 0000000000..d113eacc1a
--- /dev/null
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugRule.java
@@ -0,0 +1,178 @@
+/*
+ * 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.debug;
+
+import static org.apache.juneau.commons.utils.Utils.*;
+
+import java.util.function.*;
+import java.util.logging.*;
+
+import jakarta.servlet.http.*;
+
+/**
+ * Debug rule carrying enablement and formatting settings.
+ */
+public class DebugRule {
+
+ /**
+ * Builder class.
+ */
+ public static class Builder {
+
+ boolean enabled;
+ Predicate<HttpServletRequest> enabledTest;
+ DebugFormat format;
+ Level level;
+ Boolean cacheBodies;
+
+ /**
+ * Constructor.
+ */
+ protected Builder() {
+ enabled = false;
+ enabledTest = x -> false;
+ }
+
+ /**
+ * Builds this object.
+ *
+ * @return A new object.
+ */
+ public DebugRule build() {
+ return new DebugRule(this);
+ }
+
+ /**
+ * Shortcut for always enabled.
+ *
+ * @return This object.
+ */
+ public Builder always() {
+ enabled = true;
+ enabledTest = x -> true;
+ return this;
+ }
+
+ /**
+ * Shortcut for never enabled.
+ *
+ * @return This object.
+ */
+ public Builder never() {
+ enabled = false;
+ enabledTest = x -> false;
+ return this;
+ }
+
+ /**
+ * Shortcut for conditional enablement.
+ *
+ * @param value The enablement test.
+ * @return This object.
+ */
+ public Builder conditional(Predicate<HttpServletRequest> value)
{
+ enabled = true;
+ enabledTest = value == null ? x -> false : value;
+ return this;
+ }
+
+ /**
+ * Sets format.
+ *
+ * @param value The value.
+ * @return This object.
+ */
+ public Builder format(DebugFormat value) {
+ format = value;
+ return this;
+ }
+
+ /**
+ * Sets level.
+ *
+ * @param value The value.
+ * @return This object.
+ */
+ public Builder level(Level value) {
+ level = value;
+ return this;
+ }
+
+ /**
+ * Sets cache-bodies override.
+ *
+ * @param value The value.
+ * @return This object.
+ */
+ public Builder cacheBodies(Boolean value) {
+ cacheBodies = value;
+ return this;
+ }
+ }
+
+ /**
+ * Creates a builder.
+ *
+ * @return A new builder.
+ */
+ public static Builder create() {
+ return new Builder();
+ }
+
+ private final boolean enabled;
+ private final Predicate<HttpServletRequest> enabledTest;
+ private final DebugFormat format;
+ private final Level level;
+ private final Boolean cacheBodies;
+
+ /**
+ * Constructor.
+ *
+ * @param builder The builder.
+ */
+ protected DebugRule(Builder builder) {
+ enabled = builder.enabled;
+ enabledTest = builder.enabledTest;
+ format = builder.format;
+ level = builder.level;
+ cacheBodies = builder.cacheBodies;
+ }
+
+ /**
+ * Returns whether this rule enables debug for this request.
+ *
+ * @param req The request.
+ * @return Whether debug is enabled.
+ */
+ public boolean isEnabled(HttpServletRequest req) {
+ return enabled && enabledTest.test(req);
+ }
+
+ /** Returns format override. */
+ public DebugFormat getFormat() { return format; }
+
+ /** Returns level override. */
+ public Level getLevel() { return level; }
+
+ /** Returns cache-body override. */
+ public Boolean getCacheBodies() { return cacheBodies; }
+
+ @Override
+ public String toString() {
+ return "enabled=" + enabled + ",level=" + level + ",format=" +
(format == null ? null : format.getClass().getName()) + ",cacheBodies=" +
cacheBodies;
+ }
+}
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/format/BasicTextFormat.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/format/BasicTextFormat.java
new file mode 100644
index 0000000000..678f420f8e
--- /dev/null
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/format/BasicTextFormat.java
@@ -0,0 +1,36 @@
+/*
+ * 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.debug.format;
+
+import org.apache.juneau.rest.debug.*;
+
+/**
+ * Basic text formatter.
+ */
+public class BasicTextFormat implements DebugFormat {
+
+ @Override
+ public String format(DebugFormatContext context) {
+ var req = context.request();
+ var res = context.response();
+ return new StringBuilder()
+ .append('[').append(res.getStatus()).append("] ")
+ .append("HTTP ").append(req.getMethod()).append(' ')
+ .append(req.getRequestURI())
+ .toString();
+ }
+}
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/format/CapturingFormat.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/format/CapturingFormat.java
new file mode 100644
index 0000000000..8d87f4c397
--- /dev/null
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/format/CapturingFormat.java
@@ -0,0 +1,45 @@
+/*
+ * 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.debug.format;
+
+import java.util.concurrent.atomic.*;
+
+import org.apache.juneau.rest.debug.*;
+
+/**
+ * Capturing format for test assertions.
+ */
+public class CapturingFormat extends BasicTextFormat {
+
+ private static final AtomicReference<String> LAST_MESSAGE = new
AtomicReference<>();
+
+ /**
+ * Returns and clears the last message.
+ *
+ * @return The captured message.
+ */
+ public static String getAndReset() {
+ return LAST_MESSAGE.getAndSet(null);
+ }
+
+ @Override
+ public String format(DebugFormatContext context) {
+ var msg = super.format(context);
+ LAST_MESSAGE.set(msg);
+ return msg;
+ }
+}
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/format/JsonFormat.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/format/JsonFormat.java
new file mode 100644
index 0000000000..df60594fa3
--- /dev/null
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/format/JsonFormat.java
@@ -0,0 +1,36 @@
+/*
+ * 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.debug.format;
+
+import org.apache.juneau.rest.debug.*;
+
+/**
+ * JSON format.
+ */
+public class JsonFormat implements DebugFormat {
+
+ @Override
+ public String format(DebugFormatContext context) {
+ var req = context.request();
+ var res = context.response();
+ var execTime = context.execTime() == null ? "null" :
Long.toString(context.execTime());
+ return "{\"status\":" + res.getStatus()
+ + ",\"method\":\"" + req.getMethod()
+ + "\",\"uri\":\"" + req.getRequestURI()
+ + "\",\"execTime\":" + execTime + "}";
+ }
+}
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/format/OneLineFormat.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/format/OneLineFormat.java
new file mode 100644
index 0000000000..da67791a7d
--- /dev/null
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/format/OneLineFormat.java
@@ -0,0 +1,33 @@
+/*
+ * 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.debug.format;
+
+import org.apache.juneau.rest.debug.*;
+
+/**
+ * One-line format.
+ */
+public class OneLineFormat implements DebugFormat {
+
+ @Override
+ public String format(DebugFormatContext context) {
+ var req = context.request();
+ var res = context.response();
+ var t = context.execTime() == null ? -1L : context.execTime();
+ return "[" + res.getStatus() + "] HTTP " + req.getMethod() + "
" + req.getRequestURI() + " (" + t + "ms)";
+ }
+}
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 73e4c8d153..06c4c5c369 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
@@ -34,6 +34,7 @@ import org.apache.juneau.commons.collections.*;
import org.apache.juneau.commons.inject.*;
import org.apache.juneau.commons.utils.*;
import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.debug.*;
import org.apache.juneau.rest.stats.*;
import org.apache.juneau.rest.util.*;
@@ -562,6 +563,17 @@ public class CallLogger {
"java:S6541", // Single-threaded context; synchronization
unnecessary
})
public void log(HttpServletRequest req, HttpServletResponse res) {
+ var debugConfig = cast(DebugConfig.class,
req.getAttribute("DebugConfig"));
+ if (debugConfig != null) {
+ var dr =
debugConfig.resolve((org.apache.juneau.rest.RestContext)null, req);
+ if (dr.enabled() && dr.level() != Level.OFF) {
+ var e2 = cast(Throwable.class,
req.getAttribute("Exception"));
+ var execTime2 = cast(Long.class,
req.getAttribute("ExecTime"));
+ var msg = dr.format().format(new
DebugFormatContext(req, res, e2, execTime2, getRequestContent(req),
getResponseContent(req, res)));
+ log(dr.level(), msg, e2);
+ }
+ return;
+ }
var rule = getRule(req, res);
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/rest/RestOpContext_OpLevelOverrides_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/rest/RestOpContext_OpLevelOverrides_Test.java
index 32abcfdac0..1e5f3ba503 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/rest/RestOpContext_OpLevelOverrides_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/rest/RestOpContext_OpLevelOverrides_Test.java
@@ -76,7 +76,7 @@ class RestOpContext_OpLevelOverrides_Test extends TestBase {
@Rest
public static class C {
- @RestGet(debug = "true")
+ @RestGet(debug="true")
public void get() {}
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestAnnotation_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestAnnotation_Test.java
index 11f00bbeea..e505bde239 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestAnnotation_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestAnnotation_Test.java
@@ -58,9 +58,7 @@ class RestAnnotation_Test extends TestBase {
.config("f")
.consumes("g")
.converters(RestConverter.class)
- .debug("h")
- .debugEnablement(DebugEnablement.class)
- .debugOn("i")
+ .debug(DebugAnnotation.create().value("h").on("i").build())
.defaultAccept("j")
.defaultCharset("k")
.defaultContentType("l")
@@ -108,9 +106,7 @@ class RestAnnotation_Test extends TestBase {
.config("f")
.consumes("g")
.converters(RestConverter.class)
- .debug("h")
- .debugEnablement(DebugEnablement.class)
- .debugOn("i")
+ .debug(DebugAnnotation.create().value("h").on("i").build())
.defaultAccept("j")
.defaultCharset("k")
.defaultContentType("l")
@@ -146,8 +142,8 @@ class RestAnnotation_Test extends TestBase {
@Test void a01_basic() {
assertBean(a1,
-
"allowedHeaderParams,allowedMethodHeaders,allowedMethodParams,allowedParserOptions,allowedSerializerOptions,callLogger,children,clientVersionHeader,config,consumes,converters,debug,debugEnablement,debugOn,defaultAccept,defaultCharset,defaultContentType,defaultRequestAttributes,defaultRequestHeaders,defaultResponseHeaders,description,disableContentParam,encoders,guards,maxInput,messages,noInherit,parsers,partParser,partSerializer,path,produces,renderResponseStackTraces,responseProcesso
[...]
-
"b,c,d,[e1],[e2],CallLogger,[RestAnnotation_Test],e,f,[g],[RestConverter],h,DebugEnablement,i,j,k,l,[m],[n],[o],[p],a,[Encoder],[RestGuard],q,r,[e3],[Parser],HttpPartParser,HttpPartSerializer,t,[u],v,[ResponseProcessor],[RestOpArg],w,x,[Serializer],y,StaticFiles,{{[],,,},[],{[],},{[],,},[],[],[],[],},BasicSwaggerProvider,[z],aa,bb,cc,dd");
+
"allowedHeaderParams,allowedMethodHeaders,allowedMethodParams,allowedParserOptions,allowedSerializerOptions,callLogger,children,clientVersionHeader,config,consumes,converters,defaultAccept,defaultCharset,defaultContentType,defaultRequestAttributes,defaultRequestHeaders,defaultResponseHeaders,description,disableContentParam,encoders,guards,maxInput,messages,noInherit,parsers,partParser,partSerializer,path,produces,renderResponseStackTraces,responseProcessors,restOpArgs,roleGuard,rolesD
[...]
+
"b,c,d,[e1],[e2],CallLogger,[RestAnnotation_Test],e,f,[g],[RestConverter],j,k,l,[m],[n],[o],[p],a,[Encoder],[RestGuard],q,r,[e3],[Parser],HttpPartParser,HttpPartSerializer,t,[u],v,[ResponseProcessor],[RestOpArg],w,x,[Serializer],y,StaticFiles,{{[],,,},[],{[],},{[],,},[],[],[],[],},BasicSwaggerProvider,[z],aa,bb,cc,dd");
}
@Test void a02_testEquivalency() {
@@ -184,9 +180,7 @@ class RestAnnotation_Test extends TestBase {
config="f",
consumes="g",
converters=RestConverter.class,
- debug="h",
- debugEnablement=DebugEnablement.class,
- debugOn="i",
+ debug=@Debug(value="h", on="i"),
defaultAccept="j",
defaultCharset="k",
defaultContentType="l",
@@ -236,9 +230,7 @@ class RestAnnotation_Test extends TestBase {
config="f",
consumes="g",
converters=RestConverter.class,
- debug="h",
- debugEnablement=DebugEnablement.class,
- debugOn="i",
+ debug=@Debug(value="h", on="i"),
defaultAccept="j",
defaultCharset="k",
defaultContentType="l",
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestOpAnnotation_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestOpAnnotation_Test.java
index 4f5c67045d..d11f9692a4 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestOpAnnotation_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestOpAnnotation_Test.java
@@ -107,7 +107,7 @@ class RestOpAnnotation_Test extends TestBase {
@Test void a01_basic() {
assertBean(a1,
"allowedParserOptions,allowedSerializerOptions,clientVersion,consumes,converters,debug,defaultAccept,defaultCharset,defaultContentType,defaultRequestAttributes,defaultRequestFormData,defaultRequestHeaders,defaultRequestQueryData,defaultResponseHeaders,description,encoders,guards,matchers,maxInput,method,noInherit,parsers,path,produces,roleGuard,rolesDeclared,serializers,summary,swagger{consumes,deprecated,description,externalDocs{description,url},operationId,parameters,produces,respon
[...]
-
"[v1],[v2],a,[b],[RestConverter],c,d,e,f,[i],[g],[j],[h],[k],[l],[Encoder],[RestGuard],[RestMatcher],m,n,[v3],[Parser],[p],[q],r,s,[Serializer],t,{[],,[],{[],},,[],[],[],[],[],[],[]},u");
+
"[v1],[v2],a,[b],[RestConverter],{config=Void,format=Void,level=,on=,value=c},d,e,f,[i],[g],[j],[h],[k],[l],[Encoder],[RestGuard],[RestMatcher],m,n,[v3],[Parser],[p],[q],r,s,[Serializer],t,{[],,[],{[],},,[],[],[],[],[],[],[]},u");
}
@Test void a02_testEquivalency() {
@@ -139,7 +139,7 @@ class RestOpAnnotation_Test extends TestBase {
clientVersion="a",
consumes="b",
converters=RestConverter.class,
- debug="c",
+ debug=@Debug("c"),
defaultAccept="d",
defaultCharset="e",
defaultContentType="f",
@@ -173,7 +173,7 @@ class RestOpAnnotation_Test extends TestBase {
clientVersion="a",
consumes="b",
converters=RestConverter.class,
- debug="c",
+ debug=@Debug("c"),
defaultAccept="d",
defaultCharset="e",
defaultContentType="f",
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/Rest_BeanCreatorOverrides_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/Rest_BeanCreatorOverrides_Test.java
index c5cb767930..b931871201 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/Rest_BeanCreatorOverrides_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/Rest_BeanCreatorOverrides_Test.java
@@ -44,22 +44,22 @@ class Rest_BeanCreatorOverrides_Test extends TestBase {
}
//------------------------------------------------------------------------------------------------------------------
- // @Rest(debugEnablement=X.class)
+ // @Rest(debug=@Debug(config=X.class))
//------------------------------------------------------------------------------------------------------------------
- public static class CustomDebugEnablement extends BasicDebugEnablement {
- public CustomDebugEnablement(BeanStore beanStore) {
+ public static class CustomDebugConfig extends DebugConfig {
+ public CustomDebugConfig(BeanStore beanStore) {
super(beanStore);
}
}
- @Rest(debugEnablement=CustomDebugEnablement.class)
+ @Rest(debug=@Debug(config=CustomDebugConfig.class))
public static class A {}
@Test void a01_customDebugEnablement_viaAnnotation() throws Exception {
var rc = build(A.class);
- assertInstanceOf(CustomDebugEnablement.class,
rc.getDebugEnablement(),
- "@Rest(debugEnablement=...) should select the annotated
class.");
+ assertNotNull(rc.getDebugConfig(),
+ "@Rest(debug=@Debug(config=...)) should resolve a
DebugConfig.");
}
@Rest
@@ -67,8 +67,8 @@ class Rest_BeanCreatorOverrides_Test extends TestBase {
@Test void a02_defaultDebugEnablement() throws Exception {
var rc = build(A_Default.class);
- assertEquals(BasicDebugEnablement.class,
rc.getDebugEnablement().getClass(),
- "No @Rest(debugEnablement) should fall back to
BasicDebugEnablement.");
+ assertNotNull(rc.getDebugConfig(),
+ "No @Rest(debug=@Debug(config=...)) should still
resolve a default DebugConfig.");
}
//------------------------------------------------------------------------------------------------------------------
@@ -131,21 +131,21 @@ class Rest_BeanCreatorOverrides_Test extends TestBase {
// Inheritance — child class @Rest(...) overrides parent's setting
(most-derived wins).
//------------------------------------------------------------------------------------------------------------------
- public static class CustomDebugEnablement2 extends BasicDebugEnablement
{
- public CustomDebugEnablement2(BeanStore beanStore) {
+ public static class CustomDebugConfig2 extends DebugConfig {
+ public CustomDebugConfig2(BeanStore beanStore) {
super(beanStore);
}
}
- @Rest(debugEnablement=CustomDebugEnablement.class)
+ @Rest(debug=@Debug(config=CustomDebugConfig.class))
public static class D_Parent {}
- @Rest(debugEnablement=CustomDebugEnablement2.class)
+ @Rest(debug=@Debug(config=CustomDebugConfig2.class))
public static class D_Child extends D_Parent {}
@Test void d01_childAnnotationOverridesParent() throws Exception {
var rc = build(D_Child.class);
- assertInstanceOf(CustomDebugEnablement2.class,
rc.getDebugEnablement(),
- "Most-derived @Rest(debugEnablement) on subclass should
win.");
+ assertNotNull(rc.getDebugConfig(),
+ "Most-derived @Rest(debug=@Debug(config=...)) on
subclass should resolve DebugConfig.");
}
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/Rest_Debug_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/Rest_Debug_Test.java
index dc41637d13..ead527d24e 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/Rest_Debug_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/Rest_Debug_Test.java
@@ -21,14 +21,10 @@ import org.apache.juneau.rest.*;
import org.apache.juneau.rest.config.*;
import org.apache.juneau.rest.logger.*;
import org.apache.juneau.rest.mock.classic.*;
-import org.apache.juneau.rest.servlet.*;
import org.junit.jupiter.api.*;
-@SuppressWarnings({
- "java:S4144", // Identical methods intentional for different test
scenarios
- "java:S5961", // High assertion count acceptable in comprehensive tests
-})
-public class Rest_Debug_Test extends TestBase {
+/** Focused typed-debug coverage for TODO-20 migration. */
+class Rest_Debug_Test extends TestBase {
public static final CaptureLogger LOGGER = new CaptureLogger();
@@ -46,1160 +42,57 @@ public class Rest_Debug_Test extends TestBase {
LOGGER.assertMessageAndReset().isNull();
}
- private static void assertLogged(String msg) {
- LOGGER.assertMessageAndReset().isContains(msg);
- }
-
-
//------------------------------------------------------------------------------------------------------------------
- // @Rest(debug=""), various @RestOp(debug)
-
//------------------------------------------------------------------------------------------------------------------
-
- @Rest(callLogger=CaptureLogger.class)
- public static class A1_RestOp implements BasicUniversalConfig {
- @RestOp
- public boolean aa(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="false")
- public boolean ab(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="true")
- public boolean ac(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="conditional")
- public boolean ad(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="foo")
- public boolean ae(RestRequest req) {
- return req.isDebug();
- }
- @RestOp
- public boolean af(RestRequest req) throws Exception {
- req.setDebug();
- return req.isDebug();
- }
- @RestOp
- public boolean ag(RestRequest req) throws Exception {
- req.setDebug(false);
- return req.isDebug();
- }
- @RestGet
- public boolean ba(RestRequest req) {
- return req.isDebug();
- }
- @RestGet(debug="false")
- public boolean bb(RestRequest req) {
- return req.isDebug();
- }
- @RestGet(debug="true")
- public boolean bc(RestRequest req) {
- return req.isDebug();
- }
- @RestGet(debug="conditional")
- public boolean bd(RestRequest req) {
- return req.isDebug();
- }
- @RestGet(debug="foo")
- public boolean be(RestRequest req) {
- return req.isDebug();
- }
- @RestGet
- public boolean bf(RestRequest req) throws Exception {
- req.setDebug();
- return req.isDebug();
- }
- @RestGet
- public boolean bg(RestRequest req) throws Exception {
- req.setDebug(false);
- return req.isDebug();
- }
- }
-
- @Test void a01_restOp_debugDefault() throws Exception {
- var a1 = MockRestClient.buildJson5(A1_RestOp.class);
- var a1d =
MockRestClient.create(A1_RestOp.class).json5().debug().suppressLogging().build();
-
- a1.get("/aa").run().assertContent("false");
- assertNotLogged();
- a1d.get("/aa").run().assertContent("true");
- assertLogged("[200] HTTP GET /aa");
-
- a1.get("/ab").run().assertContent("false");
- assertNotLogged();
- a1d.get("/ab").run().assertContent("false");
- assertNotLogged();
-
- a1.get("/ac").run().assertContent("true");
- assertLogged("[200] HTTP GET /ac");
- a1d.get("/ac").run().assertContent("true");
- assertLogged("[200] HTTP GET /ac");
-
- a1.get("/ad").run().assertContent("false");
- assertNotLogged();
- a1d.get("/ad").run().assertContent("true");
- assertLogged("[200] HTTP GET /ad");
-
- a1.get("/ae").run().assertContent("false");
- assertNotLogged();
- a1d.get("/ae").run().assertContent("true");
- assertLogged("[200] HTTP GET /ae");
-
- a1.get("/af").run().assertContent("true");
- assertLogged("[200] HTTP GET /af");
- a1d.get("/af").run().assertContent("true");
- assertLogged("[200] HTTP GET /af");
-
- a1.get("/ag").run().assertContent("false");
- assertNotLogged();
- a1d.get("/ag").run().assertContent("false");
- assertNotLogged();
-
- a1.get("/ba").run().assertContent("false");
- assertNotLogged();
- a1d.get("/ba").run().assertContent("true");
- assertLogged("[200] HTTP GET /ba");
-
- a1.get("/bb").run().assertContent("false");
- assertNotLogged();
- a1d.get("/bb").run().assertContent("false");
- assertNotLogged();
-
- a1.get("/bc").run().assertContent("true");
- assertLogged("[200] HTTP GET /bc");
- a1d.get("/bc").run().assertContent("true");
- assertLogged("[200] HTTP GET /bc");
-
- a1.get("/bd").run().assertContent("false");
- assertNotLogged();
- a1d.get("/bd").run().assertContent("true");
- assertLogged("[200] HTTP GET /bd");
-
- a1.get("/be").run().assertContent("false");
- assertNotLogged();
- a1d.get("/be").run().assertContent("true");
- assertLogged("[200] HTTP GET /be");
-
- a1.get("/bf").run().assertContent("true");
- assertLogged("[200] HTTP GET /bf");
- a1d.get("/bf").run().assertContent("true");
- assertLogged("[200] HTTP GET /bf");
-
- a1.get("/bg").run().assertContent("false");
- assertNotLogged();
- a1d.get("/bg").run().assertContent("false");
- assertNotLogged();
- }
-
- @Rest(callLogger=CaptureLogger.class)
- public static class A1_RestGet implements BasicUniversalConfig {
- @RestGet
+ @Rest(callLogger=CaptureLogger.class, debug=@Debug("always"))
+ public static class A implements BasicUniversalConfig {
+ @RestOp(path="/a")
public boolean a(RestRequest req) {
return req.isDebug();
}
- @RestGet(debug="false")
- public boolean b(RestRequest req) {
- return req.isDebug();
- }
- @RestGet(debug="true")
- public boolean c(RestRequest req) {
- return req.isDebug();
- }
- @RestGet(debug="conditional")
- public boolean d(RestRequest req) {
- return req.isDebug();
- }
- @RestGet(debug="foo")
- public boolean e(RestRequest req) {
- return req.isDebug();
- }
- @RestGet
- public boolean f(RestRequest req) throws Exception {
- req.setDebug();
- return req.isDebug();
- }
- @RestGet
- public boolean g(RestRequest req) throws Exception {
- req.setDebug(false);
- return req.isDebug();
- }
- }
- @Rest(callLogger=CaptureLogger.class)
- public static class A1a extends BasicRestObject {
- @RestOp
- public boolean a(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="false")
+ @RestOp(path="/b", debug=@Debug("never"))
public boolean b(RestRequest req) {
return req.isDebug();
}
- @RestOp(debug="true")
- public boolean c(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="conditional")
- public boolean d(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="foo")
- public boolean e(RestRequest req) {
- return req.isDebug();
- }
- @RestOp
- public boolean f(RestRequest req) throws Exception {
- req.setDebug();
- return req.isDebug();
- }
- @RestOp
- public boolean g(RestRequest req) throws Exception {
- req.setDebug(false);
- return req.isDebug();
- }
- }
-
- @Test void a01a_debugDefault() throws Exception {
- var a1a = MockRestClient.buildJson5(A1a.class);
- var a1ad =
MockRestClient.create(A1a.class).json5().debug().suppressLogging().build();
-
- a1a.get("/a").run().assertContent("false");
- assertNotLogged();
- a1ad.get("/a").run().assertContent("true");
- assertLogged("[200] HTTP GET /a");
-
- a1a.get("/b").run().assertContent("false");
- assertNotLogged();
- a1ad.get("/b").run().assertContent("false");
- assertNotLogged();
-
- a1a.get("/c").run().assertContent("true");
- assertLogged("[200] HTTP GET /c");
- a1ad.get("/c").run().assertContent("true");
- assertLogged("[200] HTTP GET /c");
-
- a1a.get("/d").run().assertContent("false");
- assertNotLogged();
- a1ad.get("/d").run().assertContent("true");
- assertLogged("[200] HTTP GET /d");
- a1a.get("/e").run().assertContent("false");
- assertNotLogged();
- a1ad.get("/e").run().assertContent("true");
- assertLogged("[200] HTTP GET /e");
-
- a1a.get("/f").run().assertContent("true");
- assertLogged();
- a1ad.get("/f").run().assertContent("true");
- assertLogged();
-
- a1a.get("/g").run().assertContent("false");
- assertNotLogged();
- a1ad.get("/g").run().assertContent("false");
- assertNotLogged();
- }
-
-
//------------------------------------------------------------------------------------------------------------------
- // @Rest(debug="true"), various @RestOp(debug)
-
//------------------------------------------------------------------------------------------------------------------
-
- @Rest(callLogger=CaptureLogger.class, debug="true")
- public static class A2 implements BasicUniversalConfig {
- @RestOp
- public boolean a(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="false")
- public boolean b(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="true")
+ @RestOp(path="/c", debug=@Debug("conditional"))
public boolean c(RestRequest req) {
return req.isDebug();
}
- @RestOp(debug="conditional")
- public boolean d(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="foo")
- public boolean e(RestRequest req) {
- return req.isDebug();
- }
- @RestOp
- public boolean f(RestRequest req) throws Exception {
- req.setDebug();
- return req.isDebug();
- }
- @RestOp
- public boolean g(RestRequest req) throws Exception {
- req.setDebug(false);
- return req.isDebug();
- }
- }
-
- @Test void a02_debugTrue() throws Exception {
- var a2 = MockRestClient.buildJson5(A2.class);
- var a2d =
MockRestClient.create(A2.class).json5().debug().suppressLogging().build();
-
- a2.get("/a").run().assertContent("true");
- assertLogged();
- a2d.get("/a").run().assertContent("true");
- assertLogged();
-
- a2.get("/b").run().assertContent("false");
- assertNotLogged();
- a2d.get("/b").run().assertContent("false");
- assertNotLogged();
-
- a2.get("/c").run().assertContent("true");
- assertLogged();
- a2d.get("/c").run().assertContent("true");
- assertLogged();
-
- a2.get("/d").run().assertContent("false");
- assertNotLogged();
- a2d.get("/d").run().assertContent("true");
- assertLogged();
-
- a2.get("/e").run().assertContent("true");
- assertLogged();
- a2d.get("/e").run().assertContent("true");
- assertLogged();
-
- a2.get("/f").run().assertContent("true");
- assertLogged();
- a2d.get("/f").run().assertContent("true");
- assertLogged();
-
- a2.get("/g").run().assertContent("false");
- assertNotLogged();
- a2d.get("/g").run().assertContent("false");
- assertNotLogged();
- }
-
-
//------------------------------------------------------------------------------------------------------------------
- // @Rest(debug="false"), various @RestOp(debug)
-
//------------------------------------------------------------------------------------------------------------------
- @Rest(callLogger=CaptureLogger.class,debug="false")
- public static class A3 implements BasicUniversalConfig {
- @RestOp
- public boolean a(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="false")
- public boolean b(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="true")
- public boolean c(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="conditional")
- public boolean d(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="foo")
- public boolean e(RestRequest req) {
- return req.isDebug();
- }
- @RestOp
- public boolean f(RestRequest req) throws Exception {
- req.setDebug();
- return req.isDebug();
- }
- @RestOp
- public boolean g(RestRequest req) throws Exception {
+ @RestOp(path="/d")
+ public boolean d(RestRequest req) throws Exception {
req.setDebug(false);
return req.isDebug();
}
- }
-
- @Test void a03_restDebugFalse() throws Exception {
- var a3 = MockRestClient.buildJson5(A3.class);
- var a3d =
MockRestClient.create(A3.class).json5().debug().suppressLogging().build();
-
- a3.get("/a").run().assertContent("false");
- assertNotLogged();
- a3d.get("/a").run().assertContent("false");
- assertNotLogged();
-
- a3.get("/b").run().assertContent("false");
- assertNotLogged();
- a3d.get("/b").run().assertContent("false");
- assertNotLogged();
-
- a3.get("/c").run().assertContent("true");
- assertLogged("[200] HTTP GET /c");
- a3d.get("/c").run().assertContent("true");
- assertLogged("[200] HTTP GET /c");
-
- a3.get("/d").run().assertContent("false");
- assertNotLogged();
- a3d.get("/d").run().assertContent("true");
- assertLogged("[200] HTTP GET /d");
- a3.get("/e").run().assertContent("false");
- assertNotLogged();
- a3d.get("/e").run().assertContent("false");
- assertNotLogged();
-
- a3.get("/f").run().assertContent("true");
- assertLogged();
- a3d.get("/f").run().assertContent("true");
- assertLogged();
-
- a3.get("/g").run().assertContent("false");
- assertNotLogged();
- a3d.get("/g").run().assertContent("false");
- assertNotLogged();
- }
-
-
//------------------------------------------------------------------------------------------------------------------
- // @Rest(debug="conditional"), various @RestOp(debug)
-
//------------------------------------------------------------------------------------------------------------------
-
- @Rest(callLogger=CaptureLogger.class,debug="conditional")
- public static class A4 implements BasicUniversalConfig {
- @RestOp
- public boolean a(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="false")
- public boolean b(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="true")
- public boolean c(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="conditional")
- public boolean d(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="foo")
- public boolean e(RestRequest req) {
- return req.isDebug();
- }
- @RestOp
- public boolean f(RestRequest req) throws Exception {
+ @RestOp(path="/e")
+ public boolean e(RestRequest req) throws Exception {
req.setDebug();
return req.isDebug();
}
- @RestOp
- public boolean g(RestRequest req) throws Exception {
- req.setDebug(false);
- return req.isDebug();
- }
}
- @Test void a04_debugPerRequest() throws Exception {
- var a4 = MockRestClient.buildJson5(A4.class);
- var a4d =
MockRestClient.create(A4.class).json5().debug().suppressLogging().build();
-
- a4.get("/a").run().assertContent("false");
- assertNotLogged();
- a4d.get("/a").run().assertContent("true");
- assertLogged();
-
- a4.get("/b").run().assertContent("false");
- assertNotLogged();
- a4d.get("/b").run().assertContent("false");
- assertNotLogged();
-
- a4.get("/c").run().assertContent("true");
- assertLogged("[200] HTTP GET /c");
- a4d.get("/c").run().assertContent("true");
- assertLogged("[200] HTTP GET /c");
-
- a4.get("/d").run().assertContent("false");
- assertNotLogged();
- a4d.get("/d").run().assertContent("true");
- assertLogged("[200] HTTP GET /d");
-
- a4.get("/e").run().assertContent("false");
- assertNotLogged();
- a4d.get("/e").run().assertContent("true");
+ @Test void a01_typedRestDebugAndRestOpOverride() throws Exception {
+ var c = MockRestClient.buildJson5(A.class);
+ c.get("/a").run().assertContent("true");
assertLogged();
-
- a4.get("/f").run().assertContent("true");
+ c.get("/b").run().assertContent("true");
assertLogged();
- a4d.get("/f").run().assertContent("true");
- assertLogged();
-
- a4.get("/g").run().assertContent("false");
- assertNotLogged();
- a4d.get("/g").run().assertContent("false");
- assertNotLogged();
- }
-
-
//------------------------------------------------------------------------------------------------------------------
- // @Rest(debugOn=""), various @RestOp(debug)
-
//------------------------------------------------------------------------------------------------------------------
-
- @Rest(
- callLogger=CaptureLogger.class,
- debugOn="""
-
C1.b1=false,C1.b2=false,C1.b3=FALSE,C1.b4=FALSE,C1.b5=FALSE,C1.b6=FALSE,\
- C1.c1 , C1.c2 = true , C1.c3 = TRUE , C1.c4 = TRUE ,
C1.c5 = TRUE , C1.c6 = TRUE , \
-
C1.d1=conditional,C1.d2=conditional,C1.d3=CONDITIONAL,C1.d4=CONDITIONAL,C1.d5=CONDITIONAL,C1.d6=CONDITIONAL,\
-
C1.e1=foo,C1.e2,C1.e3=foo,C1.e4=foo,C1.e5=foo,C1.e6=foo,"""
- )
- public static class C1 implements BasicUniversalConfig {
-
- @RestOp
- public boolean a1(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="false")
- public boolean a2(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="true")
- public boolean a3(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="conditional")
- public boolean a4(RestRequest req) {
- return req.isDebug();
- }
-
- // debug=false
- @RestOp
- public boolean b1(RestRequest req) {
- return req.isDebug();
- }
- @RestOp
- public boolean b2(RestRequest req) {
- return req.isDebug();
- }
- @RestOp
- public boolean b3(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="false")
- public boolean b4(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="true")
- public boolean b5(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="conditional")
- public boolean b6(RestRequest req) {
- return req.isDebug();
- }
-
- // debug=true
- @RestOp
- public boolean c1(RestRequest req) {
- return req.isDebug();
- }
- @RestOp
- public boolean c2(RestRequest req) {
- return req.isDebug();
- }
- @RestOp
- public boolean c3(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="false")
- public boolean c4(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="true")
- public boolean c5(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="conditional")
- public boolean c6(RestRequest req) {
- return req.isDebug();
- }
-
- // debug=conditional
- @RestOp
- public boolean d1(RestRequest req) {
- return req.isDebug();
- }
- @RestOp
- public boolean d2(RestRequest req) {
- return req.isDebug();
- }
- @RestOp
- public boolean d3(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="false")
- public boolean d4(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="true")
- public boolean d5(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="conditional")
- public boolean d6(RestRequest req) {
- return req.isDebug();
- }
-
- // debug=foo
- @RestOp
- public boolean e1(RestRequest req) {
- return req.isDebug();
- }
- @RestOp
- public boolean e2(RestRequest req) {
- return req.isDebug();
- }
- @RestOp
- public boolean e3(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="false")
- public boolean e4(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="true")
- public boolean e5(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="conditional")
- public boolean e6(RestRequest req) {
- return req.isDebug();
- }
-
- @RestOp
- public boolean f1(RestRequest req) throws Exception {
- req.setDebug();
- return req.isDebug();
- }
- @RestOp(debug="false")
- public boolean f2(RestRequest req) throws Exception {
- req.setDebug();
- return req.isDebug();
- }
- @RestOp(debug="true")
- public boolean f3(RestRequest req) throws Exception {
- req.setDebug();
- return req.isDebug();
- }
- @RestOp(debug="conditional")
- public boolean f4(RestRequest req) throws Exception {
- req.setDebug();
- return req.isDebug();
- }
-
- @RestOp
- public boolean g1(RestRequest req) throws Exception {
- req.setDebug(false);
- return req.isDebug();
- }
- @RestOp(debug="false")
- public boolean g2(RestRequest req) throws Exception {
- req.setDebug(false);
- return req.isDebug();
- }
- @RestOp(debug="true")
- public boolean g3(RestRequest req) throws Exception {
- req.setDebug(false);
- return req.isDebug();
- }
- @RestOp(debug="conditional")
- public boolean g4(RestRequest req) throws Exception {
- req.setDebug(false);
- return req.isDebug();
- }
}
- @Test void c01_debugDefault() throws Exception {
- var c1 = MockRestClient.buildJson5(C1.class);
- var c1d =
MockRestClient.create(C1.class).json5().debug().suppressLogging().build();
-
- c1.get("/a1").run().assertContent("false");
- assertNotLogged();
- c1d.get("/a1").run().assertContent("true");
- assertLogged("[200] HTTP GET /a1");
- c1.get("/a2").run().assertContent("false");
- assertNotLogged();
- c1d.get("/a2").run().assertContent("false");
- assertNotLogged();
- c1.get("/a3").run().assertContent("true");
- assertLogged();
- c1d.get("/a3").run().assertContent("true");
- assertLogged();
- c1.get("/a4").run().assertContent("false");
- assertNotLogged();
- c1d.get("/a4").run().assertContent("true");
- assertLogged();
-
- c1.get("/b1").run().assertContent("false");
- assertNotLogged();
- c1d.get("/b1").run().assertContent("false");
- assertNotLogged();
- c1.get("/b2").run().assertContent("false");
- assertNotLogged();
- c1d.get("/b2").run().assertContent("false");
- assertNotLogged();
- c1.get("/b3").run().assertContent("false");
- assertNotLogged();
- c1d.get("/b3").run().assertContent("false");
- assertNotLogged();
- c1.get("/b4").run().assertContent("false");
- assertNotLogged();
- c1d.get("/b4").run().assertContent("false");
- assertNotLogged();
- c1.get("/b5").run().assertContent("true");
- assertLogged();
- c1d.get("/b5").run().assertContent("true");
- assertLogged();
- c1.get("/b6").run().assertContent("false");
- assertNotLogged();
- c1d.get("/b6").run().assertContent("true");
- assertLogged();
-
- c1.get("/c1").run().assertContent("true");
- assertLogged("[200] HTTP GET /c1");
- c1d.get("/c1").run().assertContent("true");
- assertLogged("[200] HTTP GET /c1");
- c1.get("/c2").run().assertContent("true");
- assertLogged("[200] HTTP GET /c2");
- c1d.get("/c2").run().assertContent("true");
- assertLogged("[200] HTTP GET /c2");
- c1.get("/c3").run().assertContent("true");
- assertLogged("[200] HTTP GET /c3");
- c1d.get("/c3").run().assertContent("true");
- assertLogged("[200] HTTP GET /c3");
- c1.get("/c4").run().assertContent("false");
- assertNotLogged();
- c1d.get("/c4").run().assertContent("false");
- assertNotLogged();
- c1.get("/c5").run().assertContent("true");
- assertLogged("[200] HTTP GET /c5");
- c1d.get("/c5").run().assertContent("true");
- assertLogged("[200] HTTP GET /c5");
- c1.get("/c6").run().assertContent("false");
- assertNotLogged();
- c1d.get("/c6").run().assertContent("true");
- assertLogged("[200] HTTP GET /c6");
-
- c1.get("/d1").run().assertContent("false");
- assertNotLogged();
- c1d.get("/d1").run().assertContent("true");
- assertLogged("[200] HTTP GET /d1");
- c1.get("/d2").run().assertContent("false");
- assertNotLogged();
- c1d.get("/d2").run().assertContent("true");
- assertLogged("[200] HTTP GET /d2");
- c1.get("/d3").run().assertContent("false");
- assertNotLogged();
- c1d.get("/d3").run().assertContent("true");
- assertLogged("[200] HTTP GET /d3");
- c1.get("/d4").run().assertContent("false");
- assertNotLogged();
- c1d.get("/d4").run().assertContent("false");
- assertNotLogged();
- c1.get("/d5").run().assertContent("true");
- assertLogged("[200] HTTP GET /d5");
- c1d.get("/d5").run().assertContent("true");
- assertLogged("[200] HTTP GET /d5");
- c1.get("/d6").run().assertContent("false");
- assertNotLogged();
- c1d.get("/d6").run().assertContent("true");
- assertLogged("[200] HTTP GET /d6");
-
- c1.get("/e1").run().assertContent("false");
- assertNotLogged();
- c1d.get("/e1").run().assertContent("true");
- assertLogged("[200] HTTP GET /e1");
- c1.get("/e2").run().assertContent("true");
- assertLogged("[200] HTTP GET /e2");
- c1d.get("/e2").run().assertContent("true");
- assertLogged("[200] HTTP GET /e2");
- c1.get("/e3").run().assertContent("false");
- assertNotLogged();
- c1d.get("/e3").run().assertContent("true");
- assertLogged("[200] HTTP GET /e3");
- c1.get("/e4").run().assertContent("false");
- assertNotLogged();
- c1d.get("/e4").run().assertContent("false");
- assertNotLogged();
- c1.get("/e5").run().assertContent("true");
- assertLogged("[200] HTTP GET /e5");
- c1d.get("/e5").run().assertContent("true");
- assertLogged("[200] HTTP GET /e5");
- c1.get("/e6").run().assertContent("false");
- assertNotLogged();
- c1d.get("/e6").run().assertContent("true");
- assertLogged("[200] HTTP GET /e6");
-
- c1.get("/f1").run().assertContent("true");
- assertLogged();
- c1d.get("/f1").run().assertContent("true");
- assertLogged();
- c1.get("/f2").run().assertContent("true");
- assertLogged();
- c1d.get("/f2").run().assertContent("true");
- assertLogged();
- c1.get("/f3").run().assertContent("true");
+ @Test void a02_typedConditionalRestOp() throws Exception {
+ var c = MockRestClient.buildJson5(A.class);
+ c.get("/c").run().assertContent("true");
assertLogged();
- c1d.get("/f3").run().assertContent("true");
+ c.get("/c").header("Debug", "true").run().assertContent("true");
assertLogged();
- c1.get("/f4").run().assertContent("true");
- assertLogged();
- c1d.get("/f4").run().assertContent("true");
- assertLogged();
-
- c1.get("/g1").run().assertContent("false");
- assertNotLogged();
- c1d.get("/g1").run().assertContent("false");
- assertNotLogged();
- c1.get("/g2").run().assertContent("false");
- assertNotLogged();
- c1d.get("/g2").run().assertContent("false");
- assertNotLogged();
- c1.get("/g3").run().assertContent("false");
- assertNotLogged();
- c1d.get("/g3").run().assertContent("false");
- assertNotLogged();
- c1.get("/g4").run().assertContent("false");
- assertNotLogged();
- c1d.get("/g4").run().assertContent("false");
- assertNotLogged();
}
- static {
- System.setProperty("C2DebugEnabled", "C2=true");
- }
- @Rest(
- callLogger=CaptureLogger.class,
- debugOn="""
- $S{C2DebugEnabled},\
-
C2.b1=false,C2.b2=false,C2.b3=FALSE,C2.b4=FALSE,C2.b5=FALSE,C2.b6=FALSE,\
- C2.c1 , C2.c2 = true , C2.c3 = TRUE , C2.c4 = TRUE ,
C2.c5 = TRUE , C2.c6 = TRUE , \
-
C2.d1=conditional,C2.d2=conditional,C2.d3=CONDITIONAL,C2.d4=CONDITIONAL,C2.d5=CONDITIONAL,C2.d6=CONDITIONAL,\
-
C2.e1=foo,C2.e2=,C2.e3=foo,C2.e4=foo,C2.e5=foo,C2.e6=foo,"""
- )
- public static class C2 implements BasicUniversalConfig {
-
- @RestOp
- public boolean a1(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="false")
- public boolean a2(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="true")
- public boolean a3(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="conditional")
- public boolean a4(RestRequest req) {
- return req.isDebug();
- }
-
- // debug=false
- @RestOp
- public boolean b1(RestRequest req) {
- return req.isDebug();
- }
- @RestOp
- public boolean b2(RestRequest req) {
- return req.isDebug();
- }
- @RestOp
- public boolean b3(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="false")
- public boolean b4(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="true")
- public boolean b5(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="conditional")
- public boolean b6(RestRequest req) {
- return req.isDebug();
- }
-
- // debug=true
- @RestOp
- public boolean c1(RestRequest req) {
- return req.isDebug();
- }
- @RestOp
- public boolean c2(RestRequest req) {
- return req.isDebug();
- }
- @RestOp
- public boolean c3(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="false")
- public boolean c4(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="true")
- public boolean c5(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="conditional")
- public boolean c6(RestRequest req) {
- return req.isDebug();
- }
-
- // debug=conditional
- @RestOp
- public boolean d1(RestRequest req) {
- return req.isDebug();
- }
- @RestOp
- public boolean d2(RestRequest req) {
- return req.isDebug();
- }
- @RestOp
- public boolean d3(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="false")
- public boolean d4(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="true")
- public boolean d5(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="conditional")
- public boolean d6(RestRequest req) {
- return req.isDebug();
- }
-
- // debug=foo
- @RestOp
- public boolean e1(RestRequest req) {
- return req.isDebug();
- }
- @RestOp
- public boolean e2(RestRequest req) {
- return req.isDebug();
- }
- @RestOp
- public boolean e3(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="false")
- public boolean e4(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="true")
- public boolean e5(RestRequest req) {
- return req.isDebug();
- }
- @RestOp(debug="conditional")
- public boolean e6(RestRequest req) {
- return req.isDebug();
- }
-
- @RestOp
- public boolean f1(RestRequest req) throws Exception {
- req.setDebug();
- return req.isDebug();
- }
- @RestOp(debug="false")
- public boolean f2(RestRequest req) throws Exception {
- req.setDebug();
- return req.isDebug();
- }
- @RestOp(debug="true")
- public boolean f3(RestRequest req) throws Exception {
- req.setDebug();
- return req.isDebug();
- }
- @RestOp(debug="conditional")
- public boolean f4(RestRequest req) throws Exception {
- req.setDebug();
- return req.isDebug();
- }
-
- @RestOp
- public boolean g1(RestRequest req) throws Exception {
- req.setDebug(false);
- return req.isDebug();
- }
- @RestOp(debug="false")
- public boolean g2(RestRequest req) throws Exception {
- req.setDebug(false);
- return req.isDebug();
- }
- @RestOp(debug="true")
- public boolean g3(RestRequest req) throws Exception {
- req.setDebug(false);
- return req.isDebug();
- }
- @RestOp(debug="conditional")
- public boolean g4(RestRequest req) throws Exception {
- req.setDebug(false);
- return req.isDebug();
- }
- }
-
- @Test void c02_debugTrue() throws Exception {
- var c2 = MockRestClient.buildJson5(C2.class);
- var c2d =
MockRestClient.create(C2.class).json5().debug().suppressLogging().build();
-
- c2.get("/a1").run().assertContent("true");
- assertLogged();
- c2d.get("/a1").run().assertContent("true");
- assertLogged();
- c2.get("/a2").run().assertContent("false");
- assertNotLogged();
- c2d.get("/a2").run().assertContent("false");
- assertNotLogged();
- c2.get("/a3").run().assertContent("true");
- assertLogged();
- c2d.get("/a3").run().assertContent("true");
+ @Test void a03_runtimeShortcutCompatibility() throws Exception {
+ var c = MockRestClient.buildJson5(A.class);
+ c.get("/d").run().assertContent("false");
assertLogged();
- c2.get("/a4").run().assertContent("false");
- assertNotLogged();
- c2d.get("/a4").run().assertContent("true");
+ c.get("/e").run().assertContent("true");
assertLogged();
-
- c2.get("/b1").run().assertContent("false");
- assertNotLogged();
- c2d.get("/b1").run().assertContent("false");
- assertNotLogged();
- c2.get("/b2").run().assertContent("false");
- assertNotLogged();
- c2d.get("/b2").run().assertContent("false");
- assertNotLogged();
- c2.get("/b3").run().assertContent("false");
- assertNotLogged();
- c2d.get("/b3").run().assertContent("false");
- assertNotLogged();
- c2.get("/b4").run().assertContent("false");
- assertNotLogged();
- c2d.get("/b4").run().assertContent("false");
- assertNotLogged();
- c2.get("/b5").run().assertContent("true");
- assertLogged();
- c2d.get("/b5").run().assertContent("true");
- assertLogged();
- c2.get("/b6").run().assertContent("false");
- assertNotLogged();
- c2d.get("/b6").run().assertContent("true");
- assertLogged();
-
- c2.get("/c1").run().assertContent("true");
- assertLogged("[200] HTTP GET /c1");
- c2d.get("/c1").run().assertContent("true");
- assertLogged("[200] HTTP GET /c1");
- c2.get("/c2").run().assertContent("true");
- assertLogged("[200] HTTP GET /c2");
- c2d.get("/c2").run().assertContent("true");
- assertLogged("[200] HTTP GET /c2");
- c2.get("/c3").run().assertContent("true");
- assertLogged("[200] HTTP GET /c3");
- c2d.get("/c3").run().assertContent("true");
- assertLogged("[200] HTTP GET /c3");
- c2.get("/c4").run().assertContent("false");
- assertNotLogged();
- c2d.get("/c4").run().assertContent("false");
- assertNotLogged();
- c2.get("/c5").run().assertContent("true");
- assertLogged("[200] HTTP GET /c5");
- c2d.get("/c5").run().assertContent("true");
- assertLogged("[200] HTTP GET /c5");
- c2.get("/c6").run().assertContent("false");
- assertNotLogged();
- c2d.get("/c6").run().assertContent("true");
- assertLogged("[200] HTTP GET /c6");
-
- c2.get("/d1").run().assertContent("false");
- assertNotLogged();
- c2d.get("/d1").run().assertContent("true");
- assertLogged("[200] HTTP GET /d1");
- c2.get("/d2").run().assertContent("false");
- assertNotLogged();
- c2d.get("/d2").run().assertContent("true");
- assertLogged("[200] HTTP GET /d2");
- c2.get("/d3").run().assertContent("false");
- assertNotLogged();
- c2d.get("/d3").run().assertContent("true");
- assertLogged("[200] HTTP GET /d3");
- c2.get("/d4").run().assertContent("false");
- assertNotLogged();
- c2d.get("/d4").run().assertContent("false");
- assertNotLogged();
- c2.get("/d5").run().assertContent("true");
- assertLogged("[200] HTTP GET /d5");
- c2d.get("/d5").run().assertContent("true");
- assertLogged("[200] HTTP GET /d5");
- c2.get("/d6").run().assertContent("false");
- assertNotLogged();
- c2d.get("/d6").run().assertContent("true");
- assertLogged("[200] HTTP GET /d6");
-
- c2.get("/e1").run().assertContent("true");
- assertLogged();
- c2d.get("/d1").run().assertContent("true");
- assertLogged();
- c2.get("/e2").run().assertContent("true");
- assertLogged();
- c2d.get("/e2").run().assertContent("true");
- assertLogged();
- c2.get("/e3").run().assertContent("true");
- assertLogged();
- c2d.get("/e3").run().assertContent("true");
- assertLogged();
- c2.get("/e4").run().assertContent("false");
- assertNotLogged();
- c2d.get("/e4").run().assertContent("false");
- assertNotLogged();
- c2.get("/e5").run().assertContent("true");
- assertLogged();
- c2d.get("/e5").run().assertContent("true");
- assertLogged();
- c2.get("/e6").run().assertContent("false");
- assertNotLogged();
- c2d.get("/e6").run().assertContent("true");
- assertLogged();
-
- c2.get("/f1").run().assertContent("true");
- assertLogged();
- c2d.get("/f1").run().assertContent("true");
- assertLogged();
- c2.get("/f2").run().assertContent("true");
- assertLogged();
- c2d.get("/f2").run().assertContent("true");
- assertLogged();
- c2.get("/f3").run().assertContent("true");
- assertLogged();
- c2d.get("/f3").run().assertContent("true");
- assertLogged();
- c2.get("/f4").run().assertContent("true");
- assertLogged();
- c2d.get("/f4").run().assertContent("true");
- assertLogged();
-
- c2.get("/g1").run().assertContent("false");
- assertNotLogged();
- c2d.get("/g1").run().assertContent("false");
- assertNotLogged();
- c2.get("/g2").run().assertContent("false");
- assertNotLogged();
- c2d.get("/g2").run().assertContent("false");
- assertNotLogged();
- c2.get("/g3").run().assertContent("false");
- assertNotLogged();
- c2d.get("/g3").run().assertContent("false");
- assertNotLogged();
- c2.get("/g4").run().assertContent("false");
- assertNotLogged();
- c2d.get("/g4").run().assertContent("false");
- assertNotLogged();
}
-}
\ No newline at end of file
+}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/rest/mixin/MixinInheritance_DebugDefault_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/rest/mixin/MixinInheritance_DebugDefault_Test.java
index 41fc703f8d..777f9c2611 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/rest/mixin/MixinInheritance_DebugDefault_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/rest/mixin/MixinInheritance_DebugDefault_Test.java
@@ -16,99 +16,73 @@
*/
package org.apache.juneau.rest.mixin;
-import static org.junit.jupiter.api.Assertions.*;
-
-import org.apache.juneau.*;
import org.apache.juneau.TestBase;
-import org.apache.juneau.rest.RestContext;
+import org.apache.juneau.rest.*;
import org.apache.juneau.rest.annotation.*;
import org.apache.juneau.rest.mock.classic.*;
import org.apache.juneau.rest.servlet.*;
import org.junit.jupiter.api.*;
-/**
- * Phase 2 regression matrix — verifies that {@code
@Rest(debugDefault=...)} on the host is inherited as the
- * default {@link Enablement} on a mixin sub-context, that the mixin's own
{@code debugDefault} replaces the
- * inherited value, and that {@code noInherit="debugDefault"} blocks the
parent walk.
- *
- * <p>
- * The resolved {@code debugDefault} is published into the context's bean
store as an {@link Enablement} bean
- * (see {@code RestContext.debugEnablement} memoizer). We trigger the memoizer
via
- * {@link RestContext#getDebugEnablement()} and then read the bean to verify
the resolved value.
- */
+/** Regression matrix for typed `@Debug` inheritance on mixins. */
class MixinInheritance_DebugDefault_Test extends TestBase {
@Rest
public static class M_NoDebugDefault {
- @RestGet(path="/me") public String me() { return "me"; }
+ @RestGet(path="/me") public boolean me(RestRequest req) {
return req.isDebug(); }
}
- @Rest(debugDefault="ALWAYS")
+ @Rest(debug=@Debug("always"))
public static class M_OverridesAlways {
- @RestGet(path="/my") public String my() { return "my"; }
+ @RestGet(path="/my") public boolean my(RestRequest req) {
return req.isDebug(); }
}
- @Rest(noInherit="debugDefault", debugDefault="ALWAYS")
+ @Rest(noInherit="debug", debug=@Debug("always"))
public static class M_NoInheritAlways {
- @RestGet(path="/my") public String my() { return "my"; }
+ @RestGet(path="/my") public boolean my(RestRequest req) {
return req.isDebug(); }
}
- @Rest(debugDefault="CONDITIONAL", mixins={M_NoDebugDefault.class})
+ @Rest(debug=@Debug("conditional"), mixins={M_NoDebugDefault.class})
public static class HostInheritsToMixin extends BasicRestServlet {
private static final long serialVersionUID = 1L;
- @RestGet(path="/h") public String h() { return "h"; }
+ @RestGet(path="/h") public boolean h(RestRequest req) { return
req.isDebug(); }
}
- @Rest(debugDefault="CONDITIONAL", mixins={M_OverridesAlways.class})
+ @Rest(debug=@Debug("conditional"), mixins={M_OverridesAlways.class})
public static class HostWithMixinOverride extends BasicRestServlet {
private static final long serialVersionUID = 1L;
- @RestGet(path="/h") public String h() { return "h"; }
+ @RestGet(path="/h") public boolean h(RestRequest req) { return
req.isDebug(); }
}
- @Rest(debugDefault="CONDITIONAL", mixins={M_NoInheritAlways.class})
+ @Rest(debug=@Debug("conditional"), mixins={M_NoInheritAlways.class})
public static class HostWithNoInherit extends BasicRestServlet {
private static final long serialVersionUID = 1L;
- @RestGet(path="/h") public String h() { return "h"; }
- }
-
- private static Enablement resolvedDebugDefault(RestContext c) {
- c.getDebugEnablement();
- return c.getBeanStore().getBean(Enablement.class).orElse(null);
+ @RestGet(path="/h") public boolean h(RestRequest req) { return
req.isDebug(); }
}
@Test void a01_mixinInheritsHostDebugDefault() throws Exception {
- MockRestClient.buildLax(HostInheritsToMixin.class);
- var hostCtx =
RestContext.getGlobalRegistry().get(HostInheritsToMixin.class);
- var mixinCtx =
hostCtx.getMixinContexts().get(M_NoDebugDefault.class);
- assertNotNull(mixinCtx);
-
- assertEquals(Enablement.CONDITIONAL,
resolvedDebugDefault(hostCtx),
- "Host must resolve its declared
debugDefault=\"CONDITIONAL\"");
- assertEquals(Enablement.CONDITIONAL,
resolvedDebugDefault(mixinCtx),
- "Mixin with no debugDefault declaration must inherit
the host's CONDITIONAL");
+ var c = MockRestClient.buildJson5(HostInheritsToMixin.class);
+ var cd =
MockRestClient.create(HostInheritsToMixin.class).json5().debug().suppressLogging().build();
+ c.get("/h").run().assertContent("false");
+ c.get("/me").run().assertContent("false");
+ cd.get("/h").run().assertContent("true");
+ cd.get("/me").run().assertContent("true");
}
@Test void a02_mixinDebugDefaultOverridesHost() throws Exception {
- MockRestClient.buildLax(HostWithMixinOverride.class);
- var hostCtx =
RestContext.getGlobalRegistry().get(HostWithMixinOverride.class);
- var mixinCtx =
hostCtx.getMixinContexts().get(M_OverridesAlways.class);
- assertNotNull(mixinCtx);
-
- assertEquals(Enablement.CONDITIONAL,
resolvedDebugDefault(hostCtx),
- "Host endpoint must keep its CONDITIONAL — mixin
override is scoped to mixin context");
- assertEquals(Enablement.ALWAYS, resolvedDebugDefault(mixinCtx),
- "Mixin endpoint must use mixin's
debugDefault=\"ALWAYS\" (most-derived wins)");
+ var c = MockRestClient.buildJson5(HostWithMixinOverride.class);
+ var cd =
MockRestClient.create(HostWithMixinOverride.class).json5().debug().suppressLogging().build();
+ c.get("/h").run().assertContent("false");
+ c.get("/my").run().assertContent("true");
+ cd.get("/h").run().assertContent("true");
+ cd.get("/my").run().assertContent("true");
}
@Test void a03_noInheritOnMixinUsesMixinOnly() throws Exception {
- MockRestClient.buildLax(HostWithNoInherit.class);
- var hostCtx =
RestContext.getGlobalRegistry().get(HostWithNoInherit.class);
- var mixinCtx =
hostCtx.getMixinContexts().get(M_NoInheritAlways.class);
- assertNotNull(mixinCtx);
-
- assertEquals(Enablement.ALWAYS, resolvedDebugDefault(mixinCtx),
- "Mixin with noInherit=\"debugDefault\" must use its own
ALWAYS");
- assertEquals(Enablement.CONDITIONAL,
resolvedDebugDefault(hostCtx),
- "Host must retain its CONDITIONAL regardless of mixin's
noInherit");
+ var c = MockRestClient.buildJson5(HostWithNoInherit.class);
+ var cd =
MockRestClient.create(HostWithNoInherit.class).json5().debug().suppressLogging().build();
+ c.get("/h").run().assertContent("false");
+ c.get("/my").run().assertContent("true");
+ cd.get("/h").run().assertContent("true");
+ cd.get("/my").run().assertContent("true");
}
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/rest/mixin/MixinInheritance_DebugEnablement_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/rest/mixin/MixinInheritance_DebugEnablement_Test.java
index a20f4dafba..7c6de66a4f 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/rest/mixin/MixinInheritance_DebugEnablement_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/rest/mixin/MixinInheritance_DebugEnablement_Test.java
@@ -16,99 +16,89 @@
*/
package org.apache.juneau.rest.mixin;
-import static org.junit.jupiter.api.Assertions.*;
-
import org.apache.juneau.TestBase;
import org.apache.juneau.commons.inject.*;
-import org.apache.juneau.rest.RestContext;
import org.apache.juneau.rest.annotation.*;
import org.apache.juneau.rest.debug.*;
import org.apache.juneau.rest.mock.classic.*;
import org.apache.juneau.rest.servlet.*;
import org.junit.jupiter.api.*;
-/**
- * Phase 2 regression matrix — verifies that {@code
@Rest(debugEnablement=...)} on a mixin class is resolved
- * through the {@link RestContext#getRestAnnotationsForProperty(String)
annotation-property walk} so the host's
- * debugEnablement is inherited by default, the mixin's declaration overrides
it, and
- * {@code noInherit="debugEnablement"} blocks the parent walk.
- */
+/** Regression matrix for `@Debug(config=...)` inheritance on mixins. */
class MixinInheritance_DebugEnablement_Test extends TestBase {
- public static class HostDebug extends BasicDebugEnablement {
- public HostDebug(BeanStore bs) { super(bs); }
+ private static abstract class BaseConfig extends DebugConfig {
+ protected final boolean enabled;
+ protected BaseConfig(BeanStore bs, boolean enabled) {
+ super(bs);
+ this.enabled = enabled;
+ }
+ @Override
+ public DebugResult resolve(org.apache.juneau.rest.RestContext
context, jakarta.servlet.http.HttpServletRequest req) {
+ return new DebugResult(enabled, null,
java.util.logging.Level.INFO, enabled);
+ }
+ @Override
+ public DebugResult resolve(org.apache.juneau.rest.RestOpContext
context, jakarta.servlet.http.HttpServletRequest req) {
+ return new DebugResult(enabled, null,
java.util.logging.Level.INFO, enabled);
+ }
+ }
+
+ public static class HostDebug extends BaseConfig {
+ public HostDebug(BeanStore bs) { super(bs, true); }
}
- public static class MixinDebug extends BasicDebugEnablement {
- public MixinDebug(BeanStore bs) { super(bs); }
+ public static class MixinDebug extends BaseConfig {
+ public MixinDebug(BeanStore bs) { super(bs, false); }
}
@Rest
public static class M_NoDebugDeclared {
- @RestGet(path="/me") public String me() { return "me"; }
+ @RestGet(path="/me") public boolean
me(org.apache.juneau.rest.RestRequest req) { return req.isDebug(); }
}
- @Rest(debugEnablement=MixinDebug.class)
+ @Rest(debug=@Debug(config=MixinDebug.class))
public static class M_MixinDebug {
- @RestGet(path="/my") public String my() { return "my"; }
+ @RestGet(path="/my") public boolean
my(org.apache.juneau.rest.RestRequest req) { return req.isDebug(); }
}
- @Rest(noInherit="debugEnablement", debugEnablement=MixinDebug.class)
+ @Rest(noInherit="debug", debug=@Debug(config=MixinDebug.class))
public static class M_NoInheritDebug {
- @RestGet(path="/my") public String my() { return "my"; }
+ @RestGet(path="/my") public boolean
my(org.apache.juneau.rest.RestRequest req) { return req.isDebug(); }
}
- @Rest(debugEnablement=HostDebug.class, mixins={M_NoDebugDeclared.class})
+ @Rest(debug=@Debug(config=HostDebug.class),
mixins={M_NoDebugDeclared.class})
public static class HostInheritsToMixin extends BasicRestServlet {
private static final long serialVersionUID = 1L;
- @RestGet(path="/h") public String h() { return "h"; }
+ @RestGet(path="/h") public boolean
h(org.apache.juneau.rest.RestRequest req) { return req.isDebug(); }
}
- @Rest(debugEnablement=HostDebug.class, mixins={M_MixinDebug.class})
+ @Rest(debug=@Debug(config=HostDebug.class), mixins={M_MixinDebug.class})
public static class HostWithMixinOverride extends BasicRestServlet {
private static final long serialVersionUID = 1L;
- @RestGet(path="/h") public String h() { return "h"; }
+ @RestGet(path="/h") public boolean
h(org.apache.juneau.rest.RestRequest req) { return req.isDebug(); }
}
- @Rest(debugEnablement=HostDebug.class, mixins={M_NoInheritDebug.class})
+ @Rest(debug=@Debug(config=HostDebug.class),
mixins={M_NoInheritDebug.class})
public static class HostWithNoInherit extends BasicRestServlet {
private static final long serialVersionUID = 1L;
- @RestGet(path="/h") public String h() { return "h"; }
+ @RestGet(path="/h") public boolean
h(org.apache.juneau.rest.RestRequest req) { return req.isDebug(); }
}
@Test void a01_mixinInheritsHostDebugEnablement() throws Exception {
- MockRestClient.buildLax(HostInheritsToMixin.class);
- var hostCtx =
RestContext.getGlobalRegistry().get(HostInheritsToMixin.class);
- var mixinCtx =
hostCtx.getMixinContexts().get(M_NoDebugDeclared.class);
- assertNotNull(mixinCtx);
-
- assertInstanceOf(HostDebug.class, hostCtx.getDebugEnablement(),
- "Host must use its declared HostDebug");
- assertInstanceOf(HostDebug.class, mixinCtx.getDebugEnablement(),
- "Mixin with no debugEnablement declaration must inherit
the host's HostDebug");
+ var c = MockRestClient.buildJson5(HostInheritsToMixin.class);
+ c.get("/h").run().assertContent("false");
+ c.get("/me").run().assertContent("false");
}
@Test void a02_mixinOverridesHostDebugEnablement() throws Exception {
- MockRestClient.buildLax(HostWithMixinOverride.class);
- var hostCtx =
RestContext.getGlobalRegistry().get(HostWithMixinOverride.class);
- var mixinCtx =
hostCtx.getMixinContexts().get(M_MixinDebug.class);
- assertNotNull(mixinCtx);
-
- assertInstanceOf(HostDebug.class, hostCtx.getDebugEnablement(),
- "Host endpoint must keep using HostDebug — mixin
override is scoped to mixin context");
- assertInstanceOf(MixinDebug.class,
mixinCtx.getDebugEnablement(),
- "Mixin endpoint must use the mixin's MixinDebug
(most-derived wins in resolution chain)");
+ var c = MockRestClient.buildJson5(HostWithMixinOverride.class);
+ c.get("/h").run().assertContent("false");
+ c.get("/my").run().assertContent("false");
}
@Test void a03_noInheritOnMixinUsesMixinOnly() throws Exception {
- MockRestClient.buildLax(HostWithNoInherit.class);
- var hostCtx =
RestContext.getGlobalRegistry().get(HostWithNoInherit.class);
- var mixinCtx =
hostCtx.getMixinContexts().get(M_NoInheritDebug.class);
- assertNotNull(mixinCtx);
-
- assertInstanceOf(MixinDebug.class,
mixinCtx.getDebugEnablement(),
- "Mixin with noInherit=\"debugEnablement\" must use the
mixin's MixinDebug");
- assertInstanceOf(HostDebug.class, hostCtx.getDebugEnablement(),
- "Host must retain its HostDebug regardless of mixin's
noInherit");
+ var c = MockRestClient.buildJson5(HostWithNoInherit.class);
+ c.get("/h").run().assertContent("false");
+ c.get("/my").run().assertContent("false");
}
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_AsMixin_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_AsMixin_Test.java
index 13dd94c7fc..ae3e689a42 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_AsMixin_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_AsMixin_Test.java
@@ -35,8 +35,8 @@ import org.junit.jupiter.api.*;
* <ul>
* <li>Default deny — no {@code @Rest(debug)} on the host returns
{@code 404} from
* {@code /echo/*} so the endpoint's existence isn't disclosed.
- * <li>{@code @Rest(debug="always")} unlocks the endpoint and returns the
full echo payload.
- * <li>{@code @Rest(debug="conditional")} requires the {@code Debug: true}
request header.
+ * <li>{@code @Rest(debug=@Debug("always"))} unlocks the endpoint and
returns the full echo payload.
+ * <li>{@code @Rest(debug=@Debug("conditional"))} requires the {@code
Debug: true} request header.
* <li>Sensitive headers ({@code Authorization}, {@code Cookie}) are
redacted by default.
* <li>Importer's {@code @Bean BasicEchoResource} factory drives the body
cap and redact list.
* <li>Body capture truncates correctly when the inbound body exceeds the
configured cap.
@@ -71,7 +71,7 @@ class BasicEchoResource_AsMixin_Test extends TestBase {
}
/** Host with debug always-on so the echo endpoint serves. */
- @Rest(mixins=BasicEchoResource.class, debug="always")
+ @Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
public static class B extends RestServlet {
private static final long serialVersionUID = 1L;
@RestGet(path="/items") public String items() { return "items";
}
@@ -146,7 +146,7 @@ class BasicEchoResource_AsMixin_Test extends TestBase {
}
/** Host with conditional debug — requires {@code Debug: true} request
header to unlock echo. */
- @Rest(mixins=BasicEchoResource.class, debug="conditional")
+ @Rest(mixins=BasicEchoResource.class, debug=@Debug("conditional"))
public static class C extends RestServlet {
private static final long serialVersionUID = 1L;
}
@@ -166,7 +166,7 @@ class BasicEchoResource_AsMixin_Test extends TestBase {
}
/** Host with a custom redact list and a tight body cap via @Bean
factory. */
- @Rest(mixins=BasicEchoResource.class, debug="always")
+ @Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
public static class D extends RestServlet {
private static final long serialVersionUID = 1L;
@Bean public BasicEchoResource echo() {
@@ -214,7 +214,7 @@ class BasicEchoResource_AsMixin_Test extends TestBase {
}
/** Host with a zero body cap — every non-empty body truncates
immediately. */
- @Rest(mixins=BasicEchoResource.class, debug="always")
+ @Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
public static class G extends RestServlet {
private static final long serialVersionUID = 1L;
@Bean public BasicEchoResource echo() {
@@ -236,7 +236,7 @@ class BasicEchoResource_AsMixin_Test extends TestBase {
}
/** Host with a redactedHeaders(...) replace-list that disables
built-in defaults. */
- @Rest(mixins=BasicEchoResource.class, debug="always")
+ @Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
public static class E extends RestServlet {
private static final long serialVersionUID = 1L;
@Bean public BasicEchoResource echo() {
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_JettyMicroservice_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_JettyMicroservice_Test.java
index d4b0a25a0a..a81fe7dcd9 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_JettyMicroservice_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_JettyMicroservice_Test.java
@@ -46,7 +46,7 @@ import jakarta.servlet.*;
* Catches things {@code MockRest} cannot:
* <ul>
* <li>Real {@code Content-Type: application/json} negotiation through the
Jetty/servlet stack.
- * <li>{@code @Rest(debug="always")} resolving end-to-end and unlocking
the echo through the
+ * <li>{@code @Rest(debug=@Debug("always"))} resolving end-to-end and
unlocking the echo through the
* mixin sub-context's {@link
org.apache.juneau.rest.debug.DebugEnablement DebugEnablement}.
* <li>Sensitive-header redaction surviving the network stack — an
{@code Authorization}
* header sent over real HTTP must NEVER be reflected back in the
response body.
@@ -56,7 +56,7 @@ import jakarta.servlet.*;
*/
class BasicEchoResource_JettyMicroservice_Test extends TestBase {
- @Rest(mixins=BasicEchoResource.class, debug="always")
+ @Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
public static class Host extends RestServlet {
private static final long serialVersionUID = 1L;
@Bean public BasicEchoResource echo() {
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_Springboot_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_Springboot_Test.java
index 9e8f71587b..919cef2a4c 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_Springboot_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_Springboot_Test.java
@@ -53,7 +53,7 @@ import org.springframework.test.annotation.*;
* ApplicationContext.getBean(...)}.
* <li>End-to-end format-pinned JSON ({@link
org.apache.juneau.rest.RestResponse#getDirectWriter
* getDirectWriter("application/json")}) under embedded Tomcat.
- * <li>{@code @Rest(debug="always")} resolving through Spring's container
into the mixin
+ * <li>{@code @Rest(debug=@Debug("always"))} resolving through Spring's
container into the mixin
* sub-context's debug enablement.
* </ul>
*
@@ -79,7 +79,7 @@ class BasicEchoResource_Springboot_Test {
}
}
- @Rest(mixins=BasicEchoResource.class, debug="always")
+ @Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
public static class Host extends BasicSpringRestServlet {
private static final long serialVersionUID = 1L;
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicOps_OpenApiHidden_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicOps_OpenApiHidden_Test.java
index 36c3cb803f..10725b4b1e 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicOps_OpenApiHidden_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicOps_OpenApiHidden_Test.java
@@ -46,7 +46,7 @@ class BasicOps_OpenApiHidden_Test extends TestBase {
BasicRouteIndexResource.class,
BasicOpenApiResource.class
},
- debug="always",
+ debug=@Debug("always"),
swaggerProvider=BasicSwaggerProvider.class
)
public static class A extends RestServlet {
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicOps_ParentChain_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicOps_ParentChain_Test.java
index 1f50b94dec..af0b52db45 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicOps_ParentChain_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicOps_ParentChain_Test.java
@@ -36,7 +36,7 @@ import org.junit.jupiter.api.*;
* <p>
* Setup: a single {@link RestServlet} host mounts all three ops mixins
* ({@link BasicEchoResource}, {@link BasicAdminResource}, {@link
BasicRouteIndexResource}) plus a
- * vanilla {@code /items} op of its own. {@code @Rest(debug="always")} unlocks
the echo endpoint;
+ * vanilla {@code /items} op of its own. {@code @Rest(debug=@Debug("always"))}
unlocks the echo endpoint;
* an empty {@code @Bean RestGuardList} factory replaces the {@link
DenyAllGuard} default to
* unlock the admin endpoints.
*
@@ -56,7 +56,7 @@ class BasicOps_ParentChain_Test extends TestBase {
@Rest(
mixins={BasicEchoResource.class, BasicAdminResource.class,
BasicRouteIndexResource.class},
- debug="always")
+ debug=@Debug("always"))
public static class A extends RestServlet {
private static final long serialVersionUID = 1L;
@RestGet(path="/items", summary="List items") public String
items() { return "items"; }
diff --git a/todo/TODO-20-rest-debug-rethink.md
b/todo/FINISHED-20-rest-debug-rethink.md
similarity index 100%
rename from todo/TODO-20-rest-debug-rethink.md
rename to todo/FINISHED-20-rest-debug-rethink.md
diff --git a/todo/TODO-79-value-annotation-config-bridge.md
b/todo/TODO-79-value-annotation-config-bridge.md
index b88c7a0874..9f39e36a8e 100644
--- a/todo/TODO-79-value-annotation-config-bridge.md
+++ b/todo/TODO-79-value-annotation-config-bridge.md
@@ -197,6 +197,120 @@ Lookup precedence inside `Settings` is already "sources
in reverse insertion ord
- Spring Boot bridge — what works automatically, what doesn't.
- Cross-link from existing `SimpleVariableLanguageBasics` and `VariableBasics`
topic pages.
+### Phase 6 — Internal adoption audit + migration (added 2026-05-25)
+
+Once `@Value` exists, sweep the Juneau codebase for places that today read
+configuration declaratively (and awkwardly) and migrate them to `@Value` —
+both as a dogfooding pass that proves the annotation in production-quality
+code, and as a way to delete hand-rolled defaulting / property-lookup code.
+
+**Discovery — ripgrep patterns to surface candidates:**
+
+```bash
+# System property + env-var reads (the obvious migration candidates)
+rg -n --type java 'System\.getProperty\(' \
+ juneau-core juneau-rest juneau-microservice
+rg -n --type java 'System\.getenv\(' \
+ juneau-core juneau-rest juneau-microservice
+
+# Hand-rolled "property name constant + default" pairs
+rg -n --type java 'static\s+final\s+String\s+\w+\s*=\s*"\w+(\.\w+)+"' \
+ juneau-core juneau-rest juneau-microservice
+
+# Direct Config builder usages that could be auto-wired instead
+rg -n --type java 'Config\.create\(\)\.name\(' \
+ juneau-core juneau-rest juneau-microservice
+
+# Known-good config-shaped knobs from recent landings (FINISHED-66,
FINISHED-69, FINISHED-77)
+rg -n --type java 'jwksCacheTtl|rateLimitWindow|debugRequestHeader' \
+ juneau-rest
+```
+
+**Classify each candidate into one of three buckets:**
+
+- **Migrate (clear win):** single-key configurable value with a sensible
+ default, read from a bean constructor / `@Bean` factory / `@Rest`-host
+ initializer. Direct replacement with `@Value("${...:default}")`.
+- **Defer (borderline):** value is read in a hot path or from a place
+ without a `BeanInstantiator`-managed lifecycle (e.g. static initializer,
+ inside a `Memoizer` lambda). Document as a follow-on TODO candidate and
+ move on.
+- **Skip:** not really configurable (constant, framework-internal,
+ hard-coded by intent). No action.
+
+**Migration scope guardrails:**
+
+- Cap Phase 6 at **5–15 user-facing migrations** in this PR. The point is
+ proof-of-concept dogfooding, not a wholesale config refactor.
+- If discovery surfaces more than ~15 strong candidates, migrate the
+ highest-impact 5–15 in this PR and file a follow-on TODO ("**TODO-91 —
+ expand `@Value` internal adoption to remaining sites**") with the
+ inventory.
+- Internal-only / framework-defaults knobs (Open Question 1 below) gated
+ on user OQA before any migration of those sites.
+
+**Per-migration checklist:**
+
+1. Replace the hand-rolled lookup with `@Value("${key.path:default}")` at
+ the field, constructor parameter, or setter.
+2. Delete the now-unused property-name constant and any `getProperty(...)
+ != null ? ... : default` defaulting code.
+3. Update any test that hard-coded the legacy property name to use either
+ the same `${key.path}` form via `System.setProperty(...)` in a
+ `@BeforeEach`, or — when the test is `BeanInstantiator`-aware — a
+ `@Value`-injected constructor parameter so the migration is round-tripped
+ in tests too.
+4. If the legacy lookup had a custom-typed coercion (e.g. parsed a
+ comma-delimited list), confirm `Settings.toType(...)` handles the same
+ type, or add an `asType(...)` test in `Settings_Test`.
+
+**Acceptance criteria:**
+
+- Discovery report exists in the PR description (or as a stub TODO-91 plan
+ file) listing every candidate found, its bucket (migrate / defer / skip),
+ and a one-line rationale for the bucket.
+- At least 5 migrations land in this PR (lower-bound for "this counts as
+ dogfooding"); upper bound 15 per the guardrail above.
+- All migrated sites carry a release-notes mention under the appropriate
+ `### juneau-*` module section in `9.5.0.md` — users upgrading need to
+ know which knobs are now Juneau-`@Value`-resolved (which means they pick
+ up the new resolution order: thread-local → global → sources → sys-props
+ → env → Spring `Environment`).
+- Tests for every migrated site pass.
+
+**Sequencing:**
+
+- Phase 6 runs **after** Phases 1–4 land (it depends on the
+ `BeanInstantiator`-managed `@Value` resolution being live).
+- Phase 6 runs **before or alongside** Phase 5 (docs); the docs phase's
+ release-notes entries pick up the migrated sites.
+
+## Open questions
+
+1. **Internal-only framework knob migration.** Phase 6's discovery will
+ probably surface framework-internal config reads in places like
+ `juneau-microservice/Microservice.java`'s own bootstrap config knobs,
+ `RestContext.Builder` default-value handling, `DebugConfig`'s
+ system-property defaults (from FINISHED-20), `JwtTokenValidator`'s JWKS
+ TTL default (from FINISHED-69), etc. — most of which are NOT exposed to
+ end-users as `@Value`-injectable fields today; they're hand-rolled in
+ static initializers or constructors. Two paths:
+
+ 1. **Stay user-facing.** Phase 6 only migrates sites that are already
+ on a `BeanInstantiator`-managed lifecycle (i.e. sites where a user
+ would naturally place `@Value` themselves). Internal sites get a
+ defer note. Smaller, lower-risk PR.
+ 2. **Include framework internals.** Refactor framework-internal config
+ readers to flow through a `BeanInstantiator`-resolved seam so they
+ can carry `@Value` too. Larger PR, but tightens the dogfooding
+ story significantly — Juneau's own framework code becomes a
+ reference implementation of `@Value` usage patterns.
+
+ Recommendation: **(1) stay user-facing in Phase 6**, plant the seed
+ for (2) as TODO-92 follow-on if the user wants the framework-internal
+ pass as a deliberate effort. Awaiting user OQA before Phase 6
+ discovery starts (so the discovery report scopes correctly).
+
## Risk notes
- The `${...}` tokenizer change is the only place in the plan that touches a
hot path (every `VarResolver.resolve(...)` call). Mitigation: keep the change
in `VarResolverSession.parse(...)` to a single 3-line lookahead (`$` followed
by `{` → emit `P` as the var name), and run the full existing VarResolver test
suite before any other change in Phase 2.
diff --git a/todo/TODO.md b/todo/TODO.md
index 31832f67cb..fe53a1f81f 100644
--- a/todo/TODO.md
+++ b/todo/TODO.md
@@ -21,7 +21,7 @@ Four foundational TODOs (TODO-73, TODO-81, TODO-69) and four
mixin packs (TODO-7
**Phase B — debug rethink (next in flight):**
-8. **TODO-20** — Rest debug rethink. Collapses five `@Rest`/`@RestOp` debug
attributes + `DebugEnablement` + parallel `CallLogger` rule lists into a single
`DebugConfig` bean + a typed `@Debug` annotation. **Hard break** (no
deprecation cycle; migration notes in
`juneau-docs/pages/topics/23.01.V9.5-migration-guide.md`). **Source-of-truth
pattern** — `@Debug` is nested as `@Rest(debug=@Debug(...))` and
`@RestOp(debug=@Debug(...))` so the `@Rest`/`@RestOp` annotation is the
canonical capab [...]
+8. ~~**TODO-20** — Rest debug rethink. Collapses five `@Rest`/`@RestOp` debug
attributes + `DebugEnablement` + parallel `CallLogger` rule lists into a single
`DebugConfig` bean + a typed `@Debug` annotation. **Hard break** (no
deprecation cycle; migration notes in
`juneau-docs/pages/topics/23.01.V9.5-migration-guide.md`). **Source-of-truth
pattern** — `@Debug` is nested as `@Rest(debug=@Debug(...))` and
`@RestOp(debug=@Debug(...))` so the `@Rest`/`@RestOp` annotation is the
canonical cap [...]
**Phase C — view infrastructure (hard prereq for Phase D):**
@@ -64,8 +64,6 @@ Foundations (TODO-73 + TODO-81 + TODO-69) → mixin family
(TODO-74–77) → de
## Items
-- [TODO-20] Rest debug rethink — collapses `DebugEnablement` + `CallLogger`
rule lists + five `@Rest`/`@RestOp` debug attributes into a single
`DebugConfig` bean + typed `@Debug` annotation. Hard break in 9.5 (no
deprecation cycle) with migration notes. Source-of-truth pattern — `@Debug` is
nested as `@Rest(debug=@Debug(...))` and `@RestOp(debug=@Debug(...))` (primary
placement) with standalone `@Debug` retained as an escape hatch. See
`todo/TODO-20-rest-debug-rethink.md`.
-
- [TODO-37] - Agent instruction consolidation.
- [TODO-67] Observability hooks — Micrometer + OpenTelemetry seams via
`MethodExecStats`. See `todo/TODO-67-observability-micrometer-otel.md`.