This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch docs
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/docs by this push:
     new c9155a5f9d docs: OpsIntrospectionMixins topic page + 9.5 release notes 
(FINISHED-77)
c9155a5f9d is described below

commit c9155a5f9d2d0c3d948fcd29fa036dc2aa88c4f1
Author: James Bognar <[email protected]>
AuthorDate: Sun May 24 19:12:34 2026 -0400

    docs: OpsIntrospectionMixins topic page + 9.5 release notes (FINISHED-77)
    
    Co-authored-by: Cursor <[email protected]>
---
 pages/release-notes/9.5.0.md                  |  50 +++++
 pages/topics/10.14c.OpsIntrospectionMixins.md | 269 ++++++++++++++++++++++++++
 sidebars.ts                                   |   5 +
 3 files changed, 324 insertions(+)

diff --git a/pages/release-notes/9.5.0.md b/pages/release-notes/9.5.0.md
index 6ce6ca608d..660fc17b12 100644
--- a/pages/release-notes/9.5.0.md
+++ b/pages/release-notes/9.5.0.md
@@ -2300,6 +2300,56 @@ methods on their hosts continue to instantiate via their 
builders unchanged.
   any `@RestOp`-annotated handler that returns an `HttpResource`, not just the 
static-files
   mixin.
 
+#### Ops / Introspection Mixin Pack (work item 77)
+
+`juneau-rest-server` now ships a three-class **ops / introspection mixin 
pack** in the new
+`org.apache.juneau.rest.ops` package covering the operational surface every 
long-running service
+eventually grows. Each mixin is a single-purpose `@Rest`-annotated resource 
designed to be
+grafted into any servlet via `@Rest(mixins=...)` — configure via a `@Bean 
MixinClass` factory
+method on the host, leave the rest unmounted.
+
+- **`BasicEchoResource`** — serves `/echo/*` and `/debug/echo/*` request-echo 
/ round-trip
+  introspection. Default `404 Not Found` until the host's
+  `@Rest(debug=...)` chain unlocks it (`@Rest(debug="conditional")` paired 
with a `Debug: true`
+  request header is the recommended posture). When debug is on, returns a JSON 
body reflecting
+  the inbound method, path, query string, headers (with `Authorization` / 
`Cookie` /
+  `Set-Cookie` / `Proxy-Authorization` / `X-API-Key` redacted to `[REDACTED]` 
by default), query
+  params, attributes, and bounded body capture (1 MB cap, configurable via
+  `Builder.bodyLimit(long)`). Builder methods: `bodyLimit(long)`, 
`redactedHeaders(String...)`
+  (replace), `redactHeader(String)` (additive). The handler dispatches on
+  `@RestOp(method="*")` so every HTTP verb hits the same code path.
+- **`BasicAdminResource`** — serves `/admin/threads` (JSON thread dump), 
`/admin/heap`
+  (`Runtime` + `MemoryMXBean` stats), `POST /admin/cache/flush` (run 
registered hooks),
+  and `/admin/ratelimit` (registered `RateLimitGuard` beans). Default-deny via 
the new
+  `org.apache.juneau.rest.guard.DenyAllGuard` until the host registers a 
`@Bean RestGuardList`
+  factory; the framework's bean-store override seam **replaces** the entire 
annotation-derived
+  guard list (including the deny-all) with the user-supplied chain. Builder 
methods:
+  `cacheFlush(String, Runnable)`, `cacheFlushAll(Map)`, 
`threadNamePrefixExclude(String...)`.
+  Bucket-level rate-limit inspection (per-key counters) is reserved for a 
follow-on once
+  `RateLimitGuard.Storage` exposes a snapshot SPI; v1 emits configuration only 
and returns
+  `404 Not Found` when no `RateLimitGuard` bean is registered.
+- **`BasicRouteIndexResource`** — serves `/options` and `/routes` (synonyms) 
returning a JSON
+  list of every `@RestOp`-annotated method on the host (and on every mixin on 
the host),
+  ordered by path. Each entry has `path`, `methods` (single-element list of 
the HTTP method),
+  `summary`, `description`, and `deprecated`. The route-index endpoint omits 
itself and every
+  `@OpSwagger(ignore=true)` operation, mirroring the audience separation 
between the OpenAPI
+  surface and the operator surface.
+
+All endpoints carry `@OpSwagger(ignore = true)` so they're excluded from any 
Swagger / OpenAPI
+spec generated alongside them — ops endpoints are not API-meaningful and 
shipping them in the
+spec leaks operator surface to consumers. See
+[Ops / Introspection Mixin Pack](/docs/topics/OpsIntrospectionMixins) for the 
full reference,
+including per-mixin builder semantics, default-vs-override behavior, the 
deny-all override seam
+for `BasicAdminResource`, sensitive-header-redaction guarantees, and 
real-container parity
+tests under both Jetty microservice and Spring Boot.
+
+- **`DenyAllGuard` (new class).** Companion `RestGuard` in the existing
+  `org.apache.juneau.rest.guard` package that rejects every request with `403 
Forbidden`. Used
+  as the secure-by-default placeholder on operations or resources that require 
an
+  authentication / authorization chain the importer must explicitly opt into. 
Reusable on any
+  `@Rest(guards=...)` or `@RestOp(guards=...)` site that wants the same 
default-deny posture
+  with a `@Bean RestGuardList` override seam.
+
 #### Server-side SSE Helpers (TODO-62)
 
 `juneau-rest-server` now includes an SSE helper layer for streaming endpoints:
diff --git a/pages/topics/10.14c.OpsIntrospectionMixins.md 
b/pages/topics/10.14c.OpsIntrospectionMixins.md
new file mode 100644
index 0000000000..d94132e393
--- /dev/null
+++ b/pages/topics/10.14c.OpsIntrospectionMixins.md
@@ -0,0 +1,269 @@
+---
+title: "Ops / Introspection Mixin Pack"
+slug: OpsIntrospectionMixins
+---
+
+The Juneau REST server ships with a three-class **ops / introspection mixin 
pack** in
+[`org.apache.juneau.rest.ops`](/site/apidocs/org/apache/juneau/rest/ops/package-summary.html)
+covering the operational surface every long-running service eventually grows: 
a request-echo
+endpoint for round-trip debugging, a JVM admin surface for thread / heap / 
cache /
+rate-limit visibility, and a route-index endpoint for navigation. Each mixin 
is a single-purpose
+`@Rest`-annotated resource designed to be grafted into your servlet via
+[`@Rest(mixins=...)`](/docs/topics/RestServerCompositionMixinsAndPaths) — pick 
the URLs you want,
+configure them via a `@Bean` factory, and leave the rest unmounted.
+
+## The three mixins at a glance
+
+| Mixin | Default `paths` | Default behavior | Why it exists |
+|---|---|---|---|
+| 
[`BasicEchoResource`](/site/apidocs/org/apache/juneau/rest/ops/BasicEchoResource.html)
 | `/echo/*`, `/debug/echo/*` | `404 Not Found` until `Debug` is enabled. When 
debug-on, returns a JSON body reflecting the inbound method, path, query 
string, headers (sensitive ones redacted), query params, attributes, and 
bounded body capture. | Round-trip request introspection — invaluable for 
diagnosing proxy / mTLS / auth-header issues without spinning up a packet 
capture. |
+| 
[`BasicAdminResource`](/site/apidocs/org/apache/juneau/rest/ops/BasicAdminResource.html)
 | `/admin/threads`, `/admin/heap`, `/admin/cache/flush`, `/admin/ratelimit` | 
`403 Forbidden` until the host registers a `@Bean RestGuardList`. Once 
unlocked: `GET /admin/threads` (JSON thread dump), `GET /admin/heap` (Runtime + 
MemoryMXBean stats), `POST /admin/cache/flush` (run registered hooks), `GET 
/admin/ratelimit` (configured `RateLimitGuard` beans). | JVM operational 
visibility on a deploye [...]
+| 
[`BasicRouteIndexResource`](/site/apidocs/org/apache/juneau/rest/ops/BasicRouteIndexResource.html)
 | `/options`, `/routes` | JSON list of every `@RestOp`-annotated method on the 
host (and its mixins), excluding `@OpSwagger(ignore=true)` ops and itself. Each 
entry: `path`, `methods`, `summary`, `description`, `deprecated`. | 
Machine-readable navigation index for tooling that needs a non-Swagger view of 
the URL surface (smoke-test scripts, auto-generated nav, etc.). |
+
+All endpoints across the pack carry
+[`@OpSwagger(ignore = 
true)`](/site/apidocs/org/apache/juneau/rest/annotation/OpSwagger.html#ignore())
+on their handlers — they show up in the runtime URL space but are excluded 
from any Swagger /
+OpenAPI spec generated by the [api-docs mixin 
pack](/docs/topics/ApiDocsMixins). Ops endpoints
+are not API-meaningful, and shipping them in the spec leaks operator surface 
to consumers.
+
+## Composing the pack
+
+Each mixin is independent — drop in only the ones you need. The plan-A 
composition mounts all
+three with builder-driven configuration plus the deny-all override seam:
+
+```java
+@Rest(
+    path = "/api",
+    mixins = {
+        BasicEchoResource.class,
+        BasicAdminResource.class,
+        BasicRouteIndexResource.class
+    },
+    debug = "conditional"   // gates BasicEchoResource per-request
+)
+public class ApiResource extends RestServlet {
+
+    @RestGet("/items") public List<Item> items() { ... }
+
+    // Required: register an auth guard chain.  This REPLACES the 
BasicAdminResource
+    // DenyAllGuard default — the framework's bean-store override seam swaps 
the entire
+    // annotation-derived guard list for the user-supplied chain.
+    @Bean(name = "guards")
+    public RestGuardList guards(BeanStore bs) {
+        return RestGuardList.create(bs)
+            .append(new MyAuthGuard())
+            .build();
+    }
+
+    // Optional: tighten echo body cap or extend the redacted-header list.
+    @Bean public BasicEchoResource echo() {
+        return BasicEchoResource.create()
+            .bodyLimit(64 * 1024L)
+            .redactHeader("X-Internal-Trace")
+            .build();
+    }
+
+    // Optional: register cache-flush hooks.
+    @Bean public BasicAdminResource admin() {
+        return BasicAdminResource.create()
+            .cacheFlush("primary", () -> primaryCache.invalidateAll())
+            .cacheFlush("session", () -> sessionStore.purge())
+            .build();
+    }
+}
+```
+
+The framework's mixin walk picks up each `@Bean <MixinClass>` factory 
**before** falling back to
+no-arg construction (see [Mixin 
Sub-Contexts](/docs/topics/RestServerMixinSubContexts) for the
+underlying lookup), so the host controls the configuration end-to-end without 
subclassing. A
+mixin without a `@Bean` factory gets default behavior: `BasicEchoResource` 
uses the 1 MB body cap
+and the standard redacted-header set; `BasicAdminResource` runs zero 
cache-flush hooks and the
+default thread-name-prefix exclude list; `BasicRouteIndexResource` has no 
configurable state.
+
+## Standalone deployment
+
+Each mixin is a fully-fledged `@Rest`-annotated resource and can also be 
subclassed and mounted as
+its own top-level servlet:
+
+```java
+@Rest(paths = {"/echo/*"}, debug = "conditional")
+public class EchoResource extends BasicEchoResource { }
+```
+
+Both deployment styles (mixin into an existing servlet vs. mount as a sibling 
servlet) work the
+same way under Spring Boot and under the Jetty microservice — the
+[`BasicEchoResource_JettyMicroservice_Test`](https://github.com/apache/juneau/tree/master/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_JettyMicroservice_Test.java)
+and
+[`BasicEchoResource_Springboot_Test`](https://github.com/apache/juneau/tree/master/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_Springboot_Test.java)
+parity tests cover both paths end-to-end, including the secure-by-default 
sensitive-header
+redaction surviving the network stack.
+
+## Per-mixin notes
+
+### `BasicEchoResource`
+
+**Debug-gating.** The handler is gated behind the host's
+[`DebugEnablement`](/site/apidocs/org/apache/juneau/rest/debug/DebugEnablement.html)
 chain. When
+`Debug` resolves to `OFF` for the current request, the endpoint returns `404 
Not Found` so the
+existence of the URL isn't disclosed. The recommended posture for production is
+`@Rest(debug="conditional")` paired with a guard chain so only authorized 
operators can flip the
+`Debug: true` request header. `@Rest(debug="always")` is fine for staging / 
dev but should never
+hit production — every request to `/echo/*` then reflects the full payload 
back to anyone who can
+reach the URL.
+
+**Sensitive-header redaction.** Token-bearing headers MUST never be reflected 
back; that would
+defeat any auth scheme in front of the endpoint. The default redacted list is, 
case-insensitively,
+`Authorization`, `Cookie`, `Set-Cookie`, `Proxy-Authorization`, and 
`X-API-Key`. Each redacted
+header surfaces in the echo body with the literal value `[REDACTED]` so the 
caller can see the
+header was present without leaking its value. Override via 
`Builder.redactedHeaders(String...)`
+(replaces) or `Builder.redactHeader(String)` (additive).
+
+**Body capture and truncation.** The handler reads up to 
`Builder.bodyLimit(long)` bytes (default
+1 MB) of the inbound body and emits it as a UTF-8 string in the `content` 
field of the JSON
+response. When the body exceeds the cap, the captured portion is truncated and 
the `truncated`
+flag is set to `true` so callers can see the response is incomplete.
+
+**Response shape.**
+
+```json
+{
+  "method": "POST",
+  "path": "/echo/foo/bar",
+  "queryString": "x=1",
+  "pathRemainder": "foo/bar",
+  "headers": { "User-Agent": "curl", "Authorization": "[REDACTED]" },
+  "queryParams": { "x": "1" },
+  "attributes": { },
+  "contentLength": 5,
+  "content": "hello",
+  "truncated": false
+}
+```
+
+### `BasicAdminResource`
+
+**Default-deny security posture.** The mixin is annotated with
+`@Rest(guards=DenyAllGuard.class)`, so every admin path returns `403 
Forbidden` until the host
+registers a `@Bean RestGuardList` factory. The framework's bean-store override 
seam **replaces**
+the entire annotation-derived guard list (including this deny-all) with the 
user-supplied chain —
+pair the mixin with whatever authentication / authorization story your service 
uses (bearer-token
+guard, API-key guard, Spring Security adapter, etc.).
+
+> **Why deny-all rather than a placeholder role name?** A "non-existent role" 
placeholder
+> (e.g. `roleGuard="ROLE_ADMIN_NONE_DEFAULT"`) is overridable but couples the 
host's auth
+> strategy to a framework-internal role identifier. Replacing a 
`RestGuardList` via `@Bean` keeps
+> the override surface narrow — one factory method on the host — and matches 
the way every other
+> Juneau op-context bean is wired.
+
+The integration with Juneau's eventual AuthN guard pack (bearer-token guard, 
API-key guard, JWT
+verification — currently slated as work item TODO-69 and not yet landed) 
requires no change to
+this mixin: those guards plug into the same `@Bean RestGuardList` seam, so 
dropping in the
+canonical auth chain when it ships will unlock the admin paths automatically.
+
+**Endpoints.**
+
+* `GET /admin/threads` — JSON list of currently-live threads. The default 
thread filter excludes
+  framework noise (JVM internals, servlet container, Spring Boot 
infrastructure); override via
+  `Builder.threadNamePrefixExclude(String...)`. Each entry: `name`, `id`, 
`state`, `daemon`,
+  `priority`, `stack` (frames as strings).
+* `GET /admin/heap` — JSON map: `heap.{total,free,max,used}` from `Runtime`,
+  `nonHeap.{init,used,committed,max}` from 
`MemoryMXBean.getNonHeapMemoryUsage()`, plus
+  `availableProcessors`. No heap-dump file generation in v1 (security risk; 
revisit when
+  paired with an authenticated egress channel).
+* `POST /admin/cache/flush` — runs all registered cache-flush hooks, or just a 
comma-separated
+  `?names=...` subset. Hooks register name-keyed via 
`Builder.cacheFlush(String, Runnable)`;
+  callers that want async semantics own the threading model. Unknown names are 
silently
+  ignored (404-on-unknown would leak the registered hook set).
+* `GET /admin/ratelimit` — JSON map keyed by bean name listing every registered
+  
[`RateLimitGuard`](/site/apidocs/org/apache/juneau/rest/guard/RateLimitGuard.html).
 Returns
+  `404 Not Found` when no `RateLimitGuard` bean is registered. Bucket-level 
inspection
+  (per-key counters) is reserved for a follow-on once `RateLimitGuard.Storage` 
exposes a
+  snapshot SPI; v1 emits configuration only.
+
+### `BasicRouteIndexResource`
+
+**Output.** A JSON list of every `@RestOp`-annotated method on the host (and 
on every mixin on
+the host), ordered by path. Each entry has `path`, `methods` (single-element 
list of the HTTP
+method), `summary`, `description`, and `deprecated`. The two URLs (`/options` 
and `/routes`)
+return identical bodies — synonyms for caller convenience.
+
+**Excluded entries.**
+
+* The route-index endpoint itself (it shouldn't echo its own listing).
+* Any operation marked `@OpSwagger(ignore=true)` — consistent with how those 
operations are
+  excluded from the OpenAPI spec by `BasicSwaggerProviderSession`. Convention 
endpoints (favicon,
+  robots, version, etc.), static-files mixin handlers, and the sibling 
ops-pack endpoints all
+  carry that annotation and are therefore omitted from the index, matching the 
audience
+  separation: api-docs is for documented public API, route-index is for the 
same surface but in
+  machine-readable form.
+* Lifecycle / filter beans — only methods carrying a `@RestOp`-group annotation
+  (`@RestGet` / `@RestPost` / `@RestPut` / `@RestDelete` / `@RestPatch` / 
`@RestOptions` /
+  `@RestOp`) are listed.
+
+The mixin has no configurable state — `new BasicRouteIndexResource()` is the 
only constructor,
+and the listing is computed off the host `RestContext` at request time.
+
+## Deployment notes
+
+### MockRest
+
+All three mixins work with `MockRestClient`. The Echo mixin's debug-gating 
relies on the host's
+`@Rest(debug=...)`; pair `@Rest(debug="always")` with 
`MockRestClient.buildLax(...)` to exercise
+the full echo. For the Admin mixin, register an empty `@Bean RestGuardList` to 
bypass the
+deny-all in unit tests:
+
+```java
+@Rest(mixins = BasicAdminResource.class)
+public static class TestHost extends RestServlet {
+    @Bean public RestGuardList guards(BeanStore bs) { return 
RestGuardList.create(bs).build(); }
+}
+```
+
+### Spring Boot
+
+Spring's bean-store adapter resolves both the per-mixin configuration `@Bean`s
+(`@Bean BasicEchoResource`, `@Bean BasicAdminResource`) and the override 
`@Bean RestGuardList` /
+`@Bean RateLimitGuard` through the standard 
`ApplicationContext.getBean(Class)` path, so all
+override seams behave identically under Spring Boot. The Spring Boot parity 
test
+([`BasicEchoResource_Springboot_Test`](https://github.com/apache/juneau/tree/master/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_Springboot_Test.java))
+boots a full embedded Tomcat context to confirm the redaction guarantee 
survives the network
+stack and Spring's serialization wrapper.
+
+### Jetty microservice
+
+The bare-Jetty parity test
+([`BasicEchoResource_JettyMicroservice_Test`](https://github.com/apache/juneau/tree/master/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_JettyMicroservice_Test.java))
+boots a `Microservice` on an ephemeral port via `MicroserviceTestFixture` to 
assert the same
+end-to-end behavior (real HTTP, real `Content-Type` negotiation, real 
`Authorization` redaction)
+without Spring in the picture.
+
+## Migration
+
+There's no pre-existing version of this pack to migrate from — TODO-77 is the 
first cut. If your
+service hand-rolled an echo / admin / routes endpoint before adopting the pack:
+
+1. Replace the hand-rolled `/echo` handler with `mixins = 
BasicEchoResource.class` plus
+   `@Rest(debug="conditional")` and a guard chain. **Audit your old echo for 
sensitive-header
+   leaks** — the pre-pack hand-rolls almost always reflected `Authorization` 
and `Cookie`
+   headers verbatim.
+2. Replace any hand-rolled JVM-introspection endpoints with `mixins = 
BasicAdminResource.class`
+   plus a `@Bean RestGuardList`. The cache-flush hooks register via the 
builder rather than via
+   per-handler subclassing.
+3. Replace any hand-rolled "list my endpoints" handler with
+   `mixins = BasicRouteIndexResource.class`. The output shape is intentionally 
minimal; if you
+   need the full Swagger/OpenAPI surface, add the [api-docs mixin 
pack](/docs/topics/ApiDocsMixins)
+   instead.
+
+## See also
+
+* [`@Rest(mixins=...)`](/docs/topics/RestServerCompositionMixinsAndPaths) — 
the underlying
+  composition primitive.
+* [Mixin Sub-Contexts](/docs/topics/RestServerMixinSubContexts) — host-to-mixin
+  `RestContext` inheritance and bean-store layering.
+* [Convention-Endpoints Mixin Pack](/docs/topics/ConventionEndpointsMixins) — 
sibling pack of
+  public-facing convention URLs (favicon, robots, sitemap, version, 
well-known).
+* [Static-Files Mixin](/docs/topics/StaticFilesMixin) — sibling mixin pack for 
arbitrary
+  classpath-served assets.
+* [API-Docs Mixin Pack](/docs/topics/ApiDocsMixins) — Swagger / OpenAPI / 
Redoc mixins.
+* [Guards](/docs/topics/Guards) — the `@Rest(guards=...)` mechanism 
`BasicAdminResource`
+  builds on.
diff --git a/sidebars.ts b/sidebars.ts
index bf24d9152c..5bb78b8d23 100644
--- a/sidebars.ts
+++ b/sidebars.ts
@@ -1382,6 +1382,11 @@ const sidebars: SidebarsConfig = {
                                                        id: 
'topics/10.14b.ConventionEndpointsMixins',
                                                        label: '10.14b. 
Convention-Endpoints Mixin Pack',
                                                },
+                                               {
+                                                       type: 'doc',
+                                                       id: 
'topics/10.14c.OpsIntrospectionMixins',
+                                                       label: '10.14c. Ops / 
Introspection Mixin Pack',
+                                               },
                                                {
                                                        type: 'doc',
                                                        id: 
'topics/10.15.ClientVersioning',

Reply via email to