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 b5dc7e848e Document @Child host-side seeding of child resources
(TODO-182)
b5dc7e848e is described below
commit b5dc7e848e9033bb5b5de2dc9275e52047898e8a
Author: James Bognar <[email protected]>
AuthorDate: Fri Jul 24 16:53:12 2026 -0400
Document @Child host-side seeding of child resources (TODO-182)
Co-authored-by: Cursor <[email protected]>
---
pages/topics/10.02.02.ChildResources.md | 108 +++++++++++++++++++++++++++++++-
1 file changed, 107 insertions(+), 1 deletion(-)
diff --git a/pages/topics/10.02.02.ChildResources.md
b/pages/topics/10.02.02.ChildResources.md
index 1dbfa4f57e..0ee9b0ef79 100644
--- a/pages/topics/10.02.02.ChildResources.md
+++ b/pages/topics/10.02.02.ChildResources.md
@@ -150,7 +150,113 @@ MyRest.builder().lazyChildInit(true).build();
The first request to a lazy child pays the full construction cost, which can
be significant for heavyweight children.
If predictable first-request latency is required, do not opt in — keep the
default eager behavior.
+## Host-side seeding with `@Child` (10.0.0)
+
+Children are deliberately **isolated** from the host's resolution chain (see
[Mixin-vs-child
+divergence](/docs/topics/RestServerMixinSubContexts#mixin-vs-child-divergence))
— a child's
+serializers, parsers, guards, call logger, etc. are resolved against the
child's own `RestContext`
+only, never walked through the parent. `@Rest(childrenDefs=@Child(...))`
doesn't change that
+isolation; it gives the host an explicit, opt-in way to **seed** a curated set
of settings onto a
+specific child's otherwise-isolated context, at the point the host declares it:
+
+```java
+@Rest(
+ childrenDefs = @Child(type = AdminResource.class, callLogger =
StructuredJsonLogger.class, guards = AdminBearerGuard.class)
+)
+public class ApiResource extends BasicRestServlet {
+ @RestGet("/items") public List<Item> items() { ... }
+}
+```
+
+`AdminResource`'s endpoints now log through `StructuredJsonLogger` and require
`AdminBearerGuard`
+— as declared by the host — without editing `AdminResource` itself and without
those settings
+leaking into `ApiResource`'s own `/items` resolution chain.
+
+`childrenDefs` is **additive to and coexists with** `children`: bare-class
entries in `children=`
+and rich `@Child` entries in `childrenDefs=` are discovered together (bare
entries first, then
+`childrenDefs` entries). A `@Child(type=X.class)` with no seed members is the
exact equivalent of
+a bare `children=X.class` entry; if both name the same class, the rich entry
wins (the bare entry
+is upgraded in place) — the same equivalence rule `@Mixin` establishes for
`mixins`/`mixinDefs`.
+
+### The unifying principle: seeds never remove or override the child's own
config
+
+Unlike a host `@Mixin` override — which *wins* over the mixin class's own
declarations — a
+`@Child` seed can only **add to** or **fill gaps in** the child's own
configuration. It never lets
+the child weaken or discard something the host imposed, and it never masks
something the child
+explicitly declared for itself. Every seed member falls into exactly one of
two buckets:
+
+| Bucket | Members | Behavior |
+|---|---|---|
+| **Additive-security** | `guards`, `converters`, `roleGuard`, `rolesDeclared`
| The host's contribution is added alongside whatever the child declares — the
child can never remove or weaken it. |
+| **Child-wins scalars** | `callLogger`, `partSerializer`, `partParser`,
`debug`, `defaultCharset`, `maxInput` | The seed is a fallback/default. If the
child's own `@Rest` chain explicitly declares the same setting, the child's
value wins; the seed applies only when the child is silent. |
+
+**Additive-security, list-shaped (`guards`, `converters`) — PREPEND.** The
host-seeded entries run
+first (outermost), then the child's own declared entries. A seeded guard
degrades to a plain "set"
+when the child declares no guards of its own, and the child's own
guards/converters are never
+dropped:
+
+```java
+@Rest(path = "/admin", guards = AdminOwnGuard.class) // the child's own guard
+public class AdminResource {
+ @RestGet(path = "/threads") public String threads() { ... }
+}
+
+@Rest(childrenDefs = @Child(type = AdminResource.class, guards =
HostGateGuard.class))
+public class ApiResource extends BasicRestServlet { ... }
+```
+
+A request to `/admin/threads` must satisfy **both** `HostGateGuard` (seeded,
runs first) and
+`AdminOwnGuard` (the child's own, runs second) — neither is optional.
+
+**Additive-security, role-based (`roleGuard`/`rolesDeclared`) — AND-STACK.** A
host-seeded
+`roleGuard`/`rolesDeclared` and the child's own explicit value both apply as
independent, ANDed
+role checks, rather than one overriding the other. If the host seeds
`roleGuard="admin"` and the
+child declares its own `roleGuard="on-call"`, a request must satisfy **both**
roles.
+
+**Child-wins scalars — fallback only.** If the child leaves `partSerializer`
(or any other
+child-wins member) undeclared, the host's seeded value takes effect. If the
child declares its own
+`@Rest(partSerializer=...)`, the child's value wins outright and the seed is
ignored for that
+property — same "does this property have an explicit value anywhere
more-derived?" resolution the
+framework already uses everywhere else.
+
+### Not yet supported: `defaultAccept` / `defaultContentType`
+
+`defaultAccept` and `defaultContentType` are **not** `@Child` seed members.
They're resolved
+through the framework's shared default-request-headers mechanism
(`HttpHeaderList.setDefault(...)`),
+which keeps the *first* value set while walking the annotation chain
least-derived-first —
+independently of `@Child`, this already resolves in favor of the least-derived
declaration in any
+class hierarchy. Seeding these two through `@Child` would therefore make the
host's seed win over
+the child's own explicit value, the opposite of the child-wins contract every
other scalar in this
+bucket honors. Rather than ship that contract violation, they're deferred
until `HttpHeaderList`'s
+first-wins resolution is revisited.
+
+### `noInherit` interaction
+
+`@Child` itself has no `noInherit()` member — there's no inherited chain to
cut in an isolated
+context. But a child's own `@Rest(noInherit="<property>")` **does** cut the
corresponding host
+seed too, for any seedable property, additive-security or child-wins-scalar
alike:
+
+```java
+@Rest(path = "/admin", noInherit = "guards") // opts out of the host's seeded
guard entirely
+public class AdminResource {
+ @RestGet(path = "/threads") public String threads() { ... }
+}
+
+@Rest(childrenDefs = @Child(type = AdminResource.class, guards =
HostGateGuard.class))
+public class ApiResource extends BasicRestServlet { ... }
+```
+
+With `noInherit = "guards"` on `AdminResource`, `HostGateGuard` is **not**
applied at all — the
+child stays fully in control, exactly as if the host had never seeded it. This
falls out of the
+same generic `noInherit` cutoff the framework already applies uniformly to
every `@Rest` property;
+there's no special-casing for seeded vs. non-seeded values.
+
+### Lazy children
+
+A `@Child` seed survives deferred construction under
`@Rest(lazyChildren="true")` — a lazily
+materialized child receives its seed on first request exactly as an
eagerly-built child would.
+
## See also
- [REST Server — Children vs Mixins](/docs/topics/RestServerChildrenVsMixins)
— the same-vs-different matrix for choosing children (isolation) vs mixins
(inheritance).
-- [REST Server — Mixin Sub-Contexts](/docs/topics/RestServerMixinSubContexts)
— the inheritance-based alternative composition primitive.
+- [REST Server — Mixin Sub-Contexts](/docs/topics/RestServerMixinSubContexts)
— the inheritance-based alternative composition primitive, including the
host-side `@Mixin` override this page's `@Child` section mirrors.