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 5d85769e3e docs: ConventionEndpointsMixins topic page + 9.5 release
notes (FINISHED-76)
5d85769e3e is described below
commit 5d85769e3e22f2c4c9f7eac6841014afd4f5c471
Author: James Bognar <[email protected]>
AuthorDate: Sun May 24 17:43:28 2026 -0400
docs: ConventionEndpointsMixins topic page + 9.5 release notes (FINISHED-76)
Co-authored-by: Cursor <[email protected]>
---
pages/release-notes/9.5.0.md | 44 +++++++
pages/topics/10.14b.ConventionEndpointsMixins.md | 158 +++++++++++++++++++++++
sidebars.ts | 5 +
3 files changed, 207 insertions(+)
diff --git a/pages/release-notes/9.5.0.md b/pages/release-notes/9.5.0.md
index afb79dfaad..6ce6ca608d 100644
--- a/pages/release-notes/9.5.0.md
+++ b/pages/release-notes/9.5.0.md
@@ -2239,6 +2239,50 @@ roots flow through unchanged. See
multi-mount semantics, HEAD support, Cache-Control behavior, the
OpenAPI-hidden mechanism, and
the path-override constraint documented in the topic page's *Path-override
constraint* section.
+#### Convention-Endpoints Mixin Pack (TODO-76)
+
+`juneau-rest-server` now ships a four-class **convention-endpoints mixin
pack** in the new
+`org.apache.juneau.rest.convention` package covering the de-facto-standard
URLs every
+public-facing service eventually wires up by hand. 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.
+
+- **`BasicFaviconResource`** — serves `/favicon.ico`. Default body is the
Juneau-branded
+ `juneau-favicon.ico` shipped on the framework classpath; `Cache-Control:
max-age=2592000, public`
+ (30 days). Builder methods: `bytes(byte[])`, `classpath(String)`,
`cacheControl(String)`.
+- **`BasicSeoResource`** — serves `/robots.txt` and `/sitemap.xml`. Default
robots body is the
+ RFC 9309 deny-all `User-agent: *\nDisallow: /\n`; default sitemap is an
empty `<urlset>`.
+ Builder methods: `robotsAllow(...)` / `robotsDisallow(...)` /
`robotsTxt(String)` for rule-based
+ or fully-formed bodies; `sitemapEntry(String url[, lastmod, changefreq,
priority])` for
+ per-URL entries.
+- **`BasicVersionResource`** — serves `/version`, `/info`, and `/about`
(synonyms in v1) as a
+ format-pinned JSON map sourced from `META-INF/MANIFEST.MF`,
`git.properties`, and the running
+ JVM version. Default builder chain is
`fromManifest().fromGitProperties().fromJavaVersion()`;
+ programmatic overrides via `entry(String, String)` / `entries(Map)` suppress
the defaults so
+ the importer opts into each `fromXxx()` call explicitly. Spring Boot fat-jar
tip:
+ pass `Builder.fromManifest(importerClassLoader)` to read the app's manifest
instead of the
+ framework's.
+- **`BasicWellKnownResource`** — reserves `/.well-known/security.txt`. Returns
`404 Not Found`
+ by default (RFC 9116 makes the absence of `security.txt` itself meaningful);
set a body via
+ `Builder.securityTxt(String)`. The class reserves room for future
`/.well-known/*` entries
+ (OIDC discovery, change-password, etc.) without committing v1 to a
placeholder.
+
+All four endpoints carry `@OpSwagger(ignore = true)` so they're excluded from
any Swagger /
+OpenAPI spec generated alongside them — convention URLs aren't API-meaningful
and would just
+clutter the contract. See [Convention-Endpoints Mixin
Pack](/docs/topics/ConventionEndpointsMixins)
+for the full reference, including per-mixin builder semantics,
default-vs-override behavior, and
+real-container parity tests under both Jetty microservice and Spring Boot.
+
+**Framework alignment:** `RestContext.buildMixinContext(...)` was retuned to
first consult the
+host's `BeanStore` for a pre-registered mixin instance
(`beanStore.getBean(MixinClass)`) before
+falling back to `beanStore.instantiate(MixinClass)`. This makes `@Bean
MixinClass` factory
+methods on the host first-class for **every** `@Rest(mixins=...)` mixin (not
just the
+convention-endpoints pack), so a host can configure a mixin's builder once and
let the framework
+use that exact instance instead of constructing a fresh one. The previous
behavior — always
+instantiating via the mixin's own `Builder` — meant `@Bean` factories were
silently bypassed
+for any mixin that exposed a `create()` static method. Existing mixins without
`@Bean` factory
+methods on their hosts continue to instantiate via their builders unchanged.
+
- **`@OpSwagger(ignore = true)` (new annotation member).** New boolean member
on
`org.apache.juneau.rest.annotation.OpSwagger` that excludes an operation
from the published
Swagger / OpenAPI specification. Honored by `BasicSwaggerProviderSession`
(and therefore by
diff --git a/pages/topics/10.14b.ConventionEndpointsMixins.md
b/pages/topics/10.14b.ConventionEndpointsMixins.md
new file mode 100644
index 0000000000..ba0979cf9b
--- /dev/null
+++ b/pages/topics/10.14b.ConventionEndpointsMixins.md
@@ -0,0 +1,158 @@
+---
+title: "Convention-Endpoints Mixin Pack"
+slug: ConventionEndpointsMixins
+---
+
+The Juneau REST server ships with a four-class **convention-endpoints mixin
pack** in
+[`org.apache.juneau.rest.convention`](/site/apidocs/org/apache/juneau/rest/convention/package-summary.html)
+covering the small set of de-facto-standard URLs that every public-facing
service eventually has
+to wire up by hand: the browser favicon, the SEO contracts (`robots.txt`,
`sitemap.xml`), a
+JSON deployment-introspection endpoint, and the RFC 8615 `/.well-known/*`
discovery root. 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 four mixins at a glance
+
+| Mixin | Default `paths` | Default body | Why it exists |
+|---|---|---|---|
+|
[`BasicFaviconResource`](/site/apidocs/org/apache/juneau/rest/convention/BasicFaviconResource.html)
| `/favicon.ico` | Juneau-branded `juneau-favicon.ico` shipped on the
framework classpath. | Stops every browser from logging a `404` on
`/favicon.ico`. |
+|
[`BasicSeoResource`](/site/apidocs/org/apache/juneau/rest/convention/BasicSeoResource.html)
| `/robots.txt`, `/sitemap.xml` | `User-agent: *\nDisallow: /\n` (deny-all)
and an empty `<urlset>`. | RFC 9309 + sitemaps.org compliance. Deny-all default
is intentional — opt into indexing explicitly. |
+|
[`BasicVersionResource`](/site/apidocs/org/apache/juneau/rest/convention/BasicVersionResource.html)
| `/version`, `/info`, `/about` | JSON map sourced from
`META-INF/MANIFEST.MF`, `git.properties`, and the running JVM version. |
Deployment introspection. All three URLs return the same payload (resolved
decision: synonyms in v1). |
+|
[`BasicWellKnownResource`](/site/apidocs/org/apache/juneau/rest/convention/BasicWellKnownResource.html)
| `/.well-known/security.txt` | None — returns `404 Not Found` until the
importer configures a body. | RFC 9116 `security.txt` discovery; absence is
itself meaningful, so v1 ships with no placeholder. |
+
+All four endpoints 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). Convention URLs
+are not API-meaningful, and shipping them in the spec just clutters the
contract.
+
+## Composing the pack
+
+Each mixin is independent — drop in only the ones you need. The plan-A
composition mounts all
+four with builder-driven configuration:
+
+```java
+@Rest(
+ path = "/api",
+ mixins = {
+ BasicFaviconResource.class,
+ BasicSeoResource.class,
+ BasicVersionResource.class,
+ BasicWellKnownResource.class
+ }
+)
+public class ApiResource extends RestServlet {
+
+ @RestGet("/items") public List<Item> items() { ... }
+
+ @Bean BasicFaviconResource favicon() {
+ return BasicFaviconResource.create().bytes(myLogoBytes).build();
+ }
+
+ @Bean BasicSeoResource seo() {
+ return BasicSeoResource.create()
+ .robotsAllow("*", "/")
+ .sitemapEntry("https://example.com/api/items")
+ .build();
+ }
+
+ @Bean BasicVersionResource version() {
+ // Defaults read MANIFEST.MF + git.properties + JVM version when no
entries are set;
+ // override programmatically here:
+ return BasicVersionResource.create()
+ .entry("name", "my-app")
+ .entry("version", "1.2.3")
+ .fromGitProperties()
+ .fromJavaVersion()
+ .build();
+ }
+
+ @Bean BasicWellKnownResource wellKnown() {
+ return BasicWellKnownResource.create()
+ .securityTxt("Contact: [email protected]\nExpires:
2027-01-01T00:00:00Z\n")
+ .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.
+Mixins without a `@Bean` factory get default behavior: `BasicFaviconResource`
serves the framework
+icon; `BasicSeoResource` returns deny-all robots and an empty sitemap;
`BasicVersionResource`
+reads `MANIFEST.MF` + `git.properties` from the framework classloader;
`BasicWellKnownResource`
+returns `404` on `/.well-known/security.txt`.
+
+## 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 = {"/favicon.ico"})
+public class FaviconResource extends BasicFaviconResource { }
+```
+
+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
+[`BasicVersionResource_JettyMicroservice_Test`](https://github.com/apache/juneau/tree/master/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicVersionResource_JettyMicroservice_Test.java)
+and
+[`BasicVersionResource_Springboot_Test`](https://github.com/apache/juneau/tree/master/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicVersionResource_Springboot_Test.java)
+parity tests cover both paths end-to-end.
+
+## Per-mixin notes
+
+### `BasicFaviconResource`
+
+* `Cache-Control: max-age=2592000, public` (30 days) by default — favicons
rarely change and
+ browsers re-fetch frequently when uncached. Override via
`Builder.cacheControl(String)`.
+* `Content-Type: image/x-icon`.
+* Builder methods: `bytes(byte[])`, `classpath(String)`,
`cacheControl(String)`. `bytes` and
+ `classpath` are mutually exclusive — whichever is set last wins. A
`classpath` path that
+ resolves to a missing resource silently falls back to the framework default.
+
+### `BasicSeoResource`
+
+* `/robots.txt` content type: `text/plain; charset=UTF-8`. Default body is the
deny-all
+ `User-agent: *\nDisallow: /\n`. Replace via `Builder.robotsAllow(String,
String...)` /
+ `robotsDisallow(...)` for rule-based policies, or `robotsTxt(String)` for a
fully-formed body
+ (useful when you need `Sitemap:` / `Crawl-delay:` directives the rule
builder doesn't model).
+* `/sitemap.xml` content type: `application/xml; charset=UTF-8`. Default body
is an empty
+ `<urlset>`. Add entries via `Builder.sitemapEntry(String url)` (URL-only) or
the four-arg form
+ with `lastmod`, `changefreq`, and `priority`.
+
+### `BasicVersionResource`
+
+* `Content-Type: application/json` — format-pinned via
+
[`RestResponse.getDirectWriter("application/json")`](/site/apidocs/org/apache/juneau/rest/RestResponse.html#getDirectWriter(java.lang.String))
+ so the endpoint serves JSON even on a vanilla `RestServlet` host that hasn't
wired up JSON
+ serializers explicitly.
+* All three URLs (`/version`, `/info`, `/about`) return the same JSON payload
— resolved decision
+ for v1. Future iterations may differentiate (e.g. `/info` = condensed,
`/about` = full).
+* Default builder chain: `fromManifest()` + `fromGitProperties()` +
`fromJavaVersion()`. The
+ manifest reader prefers a `META-INF/MANIFEST.MF` whose
`Implementation-Title` is set; if no
+ candidate has one, the first manifest found wins. Spring Boot fat-jar
caveat: the no-arg
+ reader uses the framework classloader, so to see the importer's app manifest
under a fat jar
+ call `Builder.fromManifest(importerClassLoader)` (or
`Builder.fromManifest(Manifest)` with a
+ pre-loaded manifest).
+* Programmatic override: `Builder.entry(String, String)` /
`Builder.entries(Map)`. Calling any
+ builder method suppresses the default chain — you opt into each `fromXxx()`
call you want.
+
+### `BasicWellKnownResource`
+
+* `Content-Type: text/plain; charset=UTF-8`.
+* No default body — per RFC 9116 the file's presence is itself meaningful. If
you don't call
+ `Builder.securityTxt(String)`, the endpoint returns `404 Not Found`.
Document loudly to your
+ ops team.
+* The class reserves room for future `/.well-known/*` entries (OIDC discovery,
change-password,
+ etc.); v1 ships only `security.txt` to stay scoped.
+
+## 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.
+* [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.
diff --git a/sidebars.ts b/sidebars.ts
index b055d50a56..bf24d9152c 100644
--- a/sidebars.ts
+++ b/sidebars.ts
@@ -1377,6 +1377,11 @@ const sidebars: SidebarsConfig = {
id:
'topics/10.14a.StaticFilesMixin',
label: '10.14a.
Static-Files Mixin',
},
+ {
+ type: 'doc',
+ id:
'topics/10.14b.ConventionEndpointsMixins',
+ label: '10.14b.
Convention-Endpoints Mixin Pack',
+ },
{
type: 'doc',
id:
'topics/10.15.ClientVersioning',