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 485ae56081 TODO-142 + Tomcat microservice module - standalone servlet 
companions/self-registration and embedded Tomcat parity
485ae56081 is described below

commit 485ae56081ff57a6704030c1f341411449156752
Author: James Bognar <[email protected]>
AuthorDate: Sun May 31 12:34:45 2026 -0400

    TODO-142 + Tomcat microservice module - standalone servlet 
companions/self-registration and embedded Tomcat parity
---
 pages/release-notes/9.5.0.md                       |  75 +++++++++++++++
 pages/topics/10.07c.RestServerStandaloneVsMixin.md | 100 +++++++++++++++++++
 pages/topics/10.07d.RestServerSelfRegistration.md  | 106 +++++++++++++++++++++
 sidebars.ts                                        |  10 ++
 4 files changed, 291 insertions(+)

diff --git a/pages/release-notes/9.5.0.md b/pages/release-notes/9.5.0.md
index 84d55f1acc..2794385059 100644
--- a/pages/release-notes/9.5.0.md
+++ b/pages/release-notes/9.5.0.md
@@ -3720,6 +3720,60 @@ public Order get(@Path long id, RestRequest req, 
RestResponse res) {
 
 See [REST Server — Conditional-GET / ETag 
Helpers](/docs/topics/RestServerConditionalGet) for the full topic, including 
the 16-cell precedence matrix, weak/strong ETag rules, and a worked round-trip 
example.
 
+#### Standalone servlet companions for dual-use resources
+
+The bundled `Basic*Resource` mixins are designed for composition into a host 
via `@Rest(mixins=...)`,
+where each pins its operations at an op-level sub-path (e.g. 
`@RestGet(path="/jsp/*")`). That shape
+is awkward when a resource needs to be deployed *on its own* as a top-level 
servlet: mounting a
+mixin-shaped resource at its own path doubles the prefix (`/jsp/jsp/...`).
+
+This release adds thin **standalone `Basic*Servlet` companions** for the 
dual-use resources. Each
+companion declares its mount at the class level via 
`@Rest(paths="/<token>/*")` (sibling top-level
+registration) and pins its op at `/*`, so it serves at a clean sibling path 
(e.g. JSPs at `/jsp/*`)
+without prefix-doubling. The mixin `Basic*Resource` forms are unchanged and 
keep their op-pinned
+sub-path behavior.
+
+| Standalone servlet | Mounts at | Delegates to (mixin) |
+| --- | --- | --- |
+| `BasicJspServlet` | `/jsp/*` | `BasicJspResource` |
+| `BasicFreemarkerServlet` | `/freemarker/*` | `BasicFreemarkerResource` |
+| `BasicMustacheServlet` | `/mustache/*` | `BasicMustacheResource` |
+| `BasicThymeleafServlet` | `/thymeleaf/*` | `BasicThymeleafResource` |
+| `BasicStaticFilesServlet` | `/static/*` | `BasicStaticFilesResource` |
+| `BasicVersionServlet` | `/version/*` | `BasicVersionResource` |
+| `BasicAdminServlet` | `/admin/*` | `BasicAdminResource` |
+
+- **Shared logic, no drift.** The four view servlets share an abstract base, 
`BasicViewServlet`
+  (in `org.apache.juneau.rest.view`), whose single `@RestGet(path="/*")` op 
delegates to a new
+  engine-agnostic `RawTemplateDispatcher` interface. The four `Basic*Resource` 
view mixins implement
+  `RawTemplateDispatcher`, so the standalone and mixin forms route raw `.jsp` 
/ `.ftlh` / `.mustache`
+  / `.html` dispatch through the *same* code and cannot diverge. The non-view 
companions
+  (`BasicStaticFilesServlet`, `BasicVersionServlet`, `BasicAdminServlet`) hold 
their matching
+  `Basic*Resource` as a delegate and forward each op to it.
+- **`BasicAdminServlet`** carries the same `@Rest(guards=DenyAllGuard.class)` 
default-deny posture as
+  the mixin — every admin path returns `403` until an auth `RestGuardList` is 
registered.
+- **`BasicHealthResource`** is intentionally *not* given a companion: it 
already extends
+  `BasicRestServlet` (it is itself a standalone-capable servlet) and mounts at 
fixed absolute paths
+  (`/healthz`, `/readyz`, `/livez`) that do not suffer the prefix-doubling 
problem.
+
+See [REST Server — Standalone vs Mixin 
Resources](/docs/topics/RestServerStandaloneVsMixin) for the
+full convention.
+
+#### Servlet self-registration (top-level paths)
+
+Three mechanisms register a servlet at the top-level paths it self-declares 
(`@Rest(paths)` /
+`getPaths()`), all reading the same resolution chain via
+`RestContext.resolveTopLevelPaths(Class, Object, BeanStore)` — the same 
resolver the Jetty
+microservice auto-discovery loop already uses, so mount paths are identical 
across runtimes:
+
+- **Non-Spring (`web.xml` / external WAR):** a 
`jakarta.servlet.ServletContainerInitializer`,
+  `JuneauRestServletContainerInitializer` (in `juneau-rest-server`), 
auto-mounts concrete
+  `@Rest`-annotated `RestServlet` subclasses that self-declare top-level 
paths. It is opt-in via the
+  `juneau.rest.auto-register=true` context init parameter, so it never 
collides with servlets
+  declared manually in `web.xml`.
+
+See [REST Server — Self-Registration](/docs/topics/RestServerSelfRegistration) 
for the full topic.
+
 ### juneau-rest-client
 
 #### REST session option wire helpers
@@ -3875,6 +3929,27 @@ public View hello(@Path String name) {
 
 ### juneau-rest-server-springboot
 
+#### Servlet self-registration (Spring Boot)
+
+Two Spring Boot mechanisms register a Juneau servlet at the top-level paths it 
self-declares
+(`@Rest(paths)` / `getPaths()`), both deriving the mapping from
+`RestContext.resolveTopLevelPaths(Class, Object, BeanStore)` — no hard-coded 
path strings:
+
+- **`JuneauRestAutoConfiguration`** — an opt-in `@AutoConfiguration` 
(registered via
+  
`META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`)
 gated behind
+  the property `juneau.rest.auto-register=true` (`matchIfMissing=false`). When 
active, it contributes
+  a single `ServletContextInitializer` that walks every `RestServlet` bean and 
mounts each at its
+  self-declared path, **skipping** any servlet instance already wired into a 
manual
+  `ServletRegistrationBean` (explicit registrations win) and any servlet that 
resolves to no
+  top-level paths.
+- **`JuneauServletRegistrations.forServlet(servlet, beanStore)`** — an 
always-available factory
+  helper that returns a `ServletRegistrationBean` whose URL mappings are 
derived from the servlet's
+  own `@Rest(paths)` (or `getPaths()`). Use it from an explicit `@Bean` method 
when you want the
+  mount path to come from the servlet rather than a hard-coded string. A 
servlet that resolves to no
+  top-level paths is rejected with an `IllegalArgumentException`.
+
+See [REST Server — Self-Registration](/docs/topics/RestServerSelfRegistration) 
for the full topic.
+
 #### Spring `Environment` bridge — `@Value` reads `application.yaml` (TODO-79)
 
 - Added `org.apache.juneau.rest.springboot.SpringEnvironmentPropertySource` — 
a `PropertySource`
diff --git a/pages/topics/10.07c.RestServerStandaloneVsMixin.md 
b/pages/topics/10.07c.RestServerStandaloneVsMixin.md
new file mode 100644
index 0000000000..8ad9dc6dcd
--- /dev/null
+++ b/pages/topics/10.07c.RestServerStandaloneVsMixin.md
@@ -0,0 +1,100 @@
+---
+title: "REST Server — Standalone vs Mixin Resources"
+slug: RestServerStandaloneVsMixin
+---
+
+Several of Juneau's bundled `Basic*Resource` classes are **dual-use**: they're 
useful both as a
+*mixin* composed into a host resource via 
[`@Rest(mixins=...)`](/docs/topics/RestServerCompositionMixinsAndPaths)
+*and* as a *standalone* top-level servlet deployed on their own. Starting in 
9.5.0 each dual-use
+resource ships a thin standalone **`Basic*Servlet`** companion so both 
deployment styles are
+first-class and behave identically.
+
+## The two shapes
+
+### Mixin form (`Basic*Resource`)
+
+A mixin pins its operations at an **op-level sub-path** so it can be grafted 
into a host mounted at
+`/`:
+
+```java
+@Rest(mixins=BasicJspResource.class)
+public class AppResource extends RestServlet { /* ... */ }
+// JSPs now served at /jsp/* alongside the host's own endpoints.
+```
+
+Internally the mixin declares something like `@RestGet(path="/jsp/*")` on its 
handler. This is the
+right shape for composition, but it doesn't work if you try to deploy the 
resource *by itself* at a
+top-level path: mounting a `/jsp/*`-pinned resource at `/jsp/*` would double 
the prefix to
+`/jsp/jsp/...`.
+
+### Standalone form (`Basic*Servlet`)
+
+A standalone companion moves the mount to the **class level** via 
`@Rest(paths="/<token>/*")` and
+pins its op at `/*`, so it serves at a clean sibling top-level path with no 
prefix-doubling:
+
+```java
+// Microservice: auto-mounted at /jsp/* by the Jetty auto-discovery loop.
+@Bean Servlet jsp() { return new BasicJspServlet(); }
+```
+
+The standalone servlet registers as a **sibling** of the application's other 
servlets
+(`/rest/*`, `/jsp/*`, `/static/*`, ...), not as a child composed under a host 
root.
+
+## Dual-use catalog
+
+| Standalone servlet | Mounts at | Delegates to (mixin) | Module |
+| --- | --- | --- | --- |
+| `BasicJspServlet` | `/jsp/*` | `BasicJspResource` | 
`juneau-rest-server-view-jsp` |
+| `BasicFreemarkerServlet` | `/freemarker/*` | `BasicFreemarkerResource` | 
`juneau-rest-server-view-freemarker` |
+| `BasicMustacheServlet` | `/mustache/*` | `BasicMustacheResource` | 
`juneau-rest-server-view-mustache` |
+| `BasicThymeleafServlet` | `/thymeleaf/*` | `BasicThymeleafResource` | 
`juneau-rest-server-view-thymeleaf` |
+| `BasicStaticFilesServlet` | `/static/*` | `BasicStaticFilesResource` | 
`juneau-rest-server` |
+| `BasicVersionServlet` | `/version/*` | `BasicVersionResource` | 
`juneau-rest-server` |
+| `BasicAdminServlet` | `/admin/*` | `BasicAdminResource` | 
`juneau-rest-server` |
+
+True mixins (the API-docs pack — Swagger / SwaggerUi / OpenApi / Redoc — plus 
`BasicRouteIndexResource`
+and `BasicEchoResource`) and the root-convention helpers 
(`BasicFaviconResource`,
+`BasicSeoResource`, `BasicWellKnownResource`) remain **mixin-only**: they're 
only meaningful composed
+into a host, so they get no standalone companion.
+
+## Shared logic — the two forms can't drift
+
+The companions are deliberately thin. They never re-implement the resource's 
behavior; they delegate
+to the matching mixin instance so there is a single source of truth.
+
+- **View servlets** share an abstract base, `BasicViewServlet` (in 
`org.apache.juneau.rest.view`),
+  whose single `@RestGet(path="/*")` op delegates to a 
`RawTemplateDispatcher`. The four view mixins
+  (`BasicJspResource`, ...) implement `RawTemplateDispatcher`, so raw `.jsp` / 
`.ftlh` / `.mustache` /
+  `.html` dispatch runs through the *same* code in both forms.
+
+  ```java
+  @Rest(paths="/jsp/*", responseProcessors=JspViewRenderer.class)
+  public class BasicJspServlet extends BasicViewServlet {
+      private final BasicJspResource delegate = 
BasicJspResource.create().build();
+      @Override protected RawTemplateDispatcher dispatcher() { return 
delegate; }
+  }
+  ```
+
+- **Non-view servlets** (`BasicStaticFilesServlet`, `BasicVersionServlet`, 
`BasicAdminServlet`) hold
+  their matching `Basic*Resource` as a delegate and forward each op to it 
verbatim.
+
+> **Note — `RawTemplateDispatcher` vs `ViewRenderer`.** These are different 
contracts.
+> 
[`ViewRenderer`](/site/apidocs/org/apache/juneau/rest/view/ViewRenderer.html) 
is the
+> response-processor marker that renders `View`-typed `@RestOp` *return 
values*.
+> `RawTemplateDispatcher` covers the *raw* file-serving path (serving a 
template directly by its
+> trailing request path). A view mixin implements both.
+
+## Why `BasicHealthResource` has no companion
+
+`BasicHealthResource` is intentionally left without a `Basic*Servlet`. It 
already extends
+`BasicRestServlet` — it *is* a standalone-capable servlet — and it mounts at 
fixed absolute paths
+(`/healthz`, `/readyz`, `/livez`) that don't suffer the prefix-doubling 
problem the `/<token>/*`
+mixins do. Adding a companion would be redundant.
+
+## See also
+
+- [REST Server — Mixins and Multi-Mount 
Paths](/docs/topics/RestServerCompositionMixinsAndPaths)
+- [REST Server — Self-Registration](/docs/topics/RestServerSelfRegistration)
+- [Static-Files Mixin](/docs/topics/StaticFilesMixin)
+- [Convention-Endpoints Mixin Pack](/docs/topics/ConventionEndpointsMixins)
+- [Ops / Introspection Mixin Pack](/docs/topics/OpsIntrospectionMixins)
diff --git a/pages/topics/10.07d.RestServerSelfRegistration.md 
b/pages/topics/10.07d.RestServerSelfRegistration.md
new file mode 100644
index 0000000000..06a12578dd
--- /dev/null
+++ b/pages/topics/10.07d.RestServerSelfRegistration.md
@@ -0,0 +1,106 @@
+---
+title: "REST Server — Self-Registration"
+slug: RestServerSelfRegistration
+---
+
+A Juneau servlet can declare *where it wants to be mounted* via the top-level 
path chain —
+[`@Rest(paths=...)`](/docs/topics/RestServerCompositionMixinsAndPaths) or a 
`getPaths()` override —
+rather than having the deployer hard-code a URL pattern at registration time. 
Three mechanisms read
+that self-declaration and register the servlet accordingly, one per runtime.
+
+All three resolve the mount path through the **same** resolver:
+
+```java
+String[] paths = RestContext.resolveTopLevelPaths(servletClass, 
servletInstance, beanStore);
+```
+
+`resolveTopLevelPaths(...)` walks the precedence chain *programmatic override 
→ `getPaths()` getter →
+`@Rest(paths)` annotation default* (with SVL substitution on annotation 
elements, so `$C{...}` /
+`${env}` templates resolve against the supplied bean store). It reads the 
**top-level** chain
+(`paths()` / `getPaths()`), **never** `path()` (which is for 
child-composition). Because every
+mechanism funnels through this one method, mount paths are identical across 
runtimes.
+
+## Microservice (Jetty)
+
+No new API — the existing `JettyServerComponent` auto-discovery loop already 
calls
+`resolveTopLevelPaths(...)` for each `@Bean Servlet` it finds and mounts it at 
the resolved paths.
+A standalone companion (or any `@Rest(paths=...)` servlet) is mounted simply 
by exposing it as a
+bean:
+
+```java
+@Bean Servlet version() { return new BasicVersionServlet(); }   // 
auto-mounted at /version/*
+```
+
+## Spring Boot
+
+### Opt-in auto-configuration
+
+`JuneauRestAutoConfiguration` (in `juneau-rest-server-springboot`, registered 
via
+`META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`)
 walks every
+`RestServlet` bean in the application context and mounts each at its 
self-declared path.
+
+It is **gated behind a property** (no default — `matchIfMissing=false`):
+
+```properties
+juneau.rest.auto-register=true
+```
+
+When unset (or any value other than `true`) the auto-configuration contributes 
nothing and servlet
+registration stays fully manual. When active it:
+
+1. Skips any servlet instance already wired into a manual 
`ServletRegistrationBean` (explicit
+   registrations always win — no double-mounts).
+2. Resolves each servlet's mount paths via `resolveTopLevelPaths(...)` using 
the application's
+   registered `BeanStore`.
+3. Registers the servlet at each resolved path. Servlets that resolve to no 
top-level paths (pure
+   mixins / child-composed resources) are skipped.
+
+### Explicit factory helper
+
+When you'd rather register servlets yourself but still want the mount path to 
come from the servlet
+(not a hard-coded string), use the always-available factory helper:
+
+```java
+@Bean
+ServletRegistrationBean<?> jspServlet(BeanStore beanStore) {
+    return JuneauServletRegistrations.forServlet(new BasicJspServlet(), 
beanStore);
+}
+```
+
+`JuneauServletRegistrations.forServlet(...)` derives the registration's URL 
mappings from the
+servlet's `@Rest(paths)` / `getPaths()`. A servlet that resolves to no 
top-level paths is rejected
+with an `IllegalArgumentException`.
+
+## Non-Spring (`web.xml` / external WAR)
+
+For plain Servlet-spec deployments, `juneau-rest-server` ships a
+`jakarta.servlet.ServletContainerInitializer`, 
`JuneauRestServletContainerInitializer` (discovered
+via `META-INF/services/jakarta.servlet.ServletContainerInitializer`). Through
+`@HandlesTypes(RestServlet.class)` the container hands it every `RestServlet` 
on the webapp
+classpath; it auto-mounts each concrete, `@Rest`-annotated, 
no-arg-instantiable servlet that
+self-declares top-level paths.
+
+It is **opt-in** via a context init parameter (parity with the Spring Boot 
property), so it never
+collides with servlets the deployer declared manually in `web.xml`:
+
+```xml
+<context-param>
+    <param-name>juneau.rest.auto-register</param-name>
+    <param-value>true</param-value>
+</context-param>
+```
+
+## Cross-runtime parity
+
+| Runtime | Mechanism | Activation | Reads |
+| --- | --- | --- | --- |
+| Jetty microservice | `JettyServerComponent` auto-discovery | always (for 
`@Bean Servlet`) | `resolveTopLevelPaths(...)` |
+| Spring Boot | `JuneauRestAutoConfiguration` | 
`juneau.rest.auto-register=true` | `resolveTopLevelPaths(...)` |
+| Spring Boot | `JuneauServletRegistrations.forServlet(...)` | explicit 
`@Bean` | `resolveTopLevelPaths(...)` |
+| `web.xml` / WAR | `JuneauRestServletContainerInitializer` | 
`juneau.rest.auto-register` context-param | `resolveTopLevelPaths(...)` |
+
+## See also
+
+- [REST Server — Standalone vs Mixin 
Resources](/docs/topics/RestServerStandaloneVsMixin)
+- [REST Server — Mixins and Multi-Mount 
Paths](/docs/topics/RestServerCompositionMixinsAndPaths)
+- [juneau-rest-server-springboot 
Basics](/docs/topics/JuneauRestServerSpringbootBasics)
diff --git a/sidebars.ts b/sidebars.ts
index 5a4c61afbd..9a1232bb3c 100644
--- a/sidebars.ts
+++ b/sidebars.ts
@@ -1342,6 +1342,16 @@ const sidebars: SidebarsConfig = {
                                                        id: 
'topics/10.07a.RestServerComposition',
                                                        label: '10.7a. Mixins 
and Multi-Mount Paths',
                                                },
+                                               {
+                                                       type: 'doc',
+                                                       id: 
'topics/10.07c.RestServerStandaloneVsMixin',
+                                                       label: '10.7c. 
Standalone vs Mixin Resources',
+                                               },
+                                               {
+                                                       type: 'doc',
+                                                       id: 
'topics/10.07d.RestServerSelfRegistration',
+                                                       label: '10.7d. Servlet 
Self-Registration',
+                                               },
                                                {
                                                        type: 'doc',
                                                        id: 
'topics/10.08.RestServerSse',

Reply via email to