Repository navigation
CAMEL-25750: camel-core, camel-jbang - keep the body types each step has been reached with, and use them in the hint, JMX, the coverage dump and the validator - #28178
Conversation
…hed with, and show them with the route structure The message history records the type of the body at each node for one exchange. The new BodyTypeRegistry, a context plugin, keeps per node every distinct type across all the exchanges, and its kind: null, payload (text, bytes, a stream, a file, a single value), data (a Map, a List, an object) or iterator. All four message history factories feed it, with the registry looked up once in doInit. A route's body types are forgotten when the route is removed. The route-structure console, camel cmd route-structure, and the AI and MCP route structure tools show the types and kinds of each step. A step reached with bodies of more than one kind needs attention. --varied (varied=true for the tools) shows only the steps reached with more than one type, and the route console counts them per route (variedBodyTypes, mixedBodyKinds). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
|
🌟 Thank you for your contribution to the Apache Camel project! 🌟 🐫 Apache Camel Committers, please review the following items:
|
gnodet-bot
left a comment
There was a problem hiding this comment.
Clean implementation of a diagnostic-only BodyTypeRegistry SPI. The concurrency model (volatile arrays + synchronized mutation + copy-on-write) is correct and efficient for the read-heavy / write-rare access pattern. The MAX_TYPES_PER_NODE=8 cap prevents unbounded memory growth. The eager plugin registration avoids the double-creation race documented in the comment. All four message history factories are symmetrically updated (DefaultMessageHistoryFactory, MetricsMessageHistoryFactory, MicrometerMessageHistoryFactory, and OpenTelemetryMessageHistoryFactory all call the 2-arg captureBody). Test coverage is thorough: unit tests for DefaultBodyTypeRegistry, integration tests for BodyTypeRegistry lifecycle (including route removal), dev console tests for both JSON and text output, CLI and MCP tests for the varied filter.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
…ges reached the step with another kind of body When a step fails on the body and the message history is on, the hint of BodyFacts ends with what the BodyTypeRegistry knows about the step: "; other messages reached this step with data (LinkedHashMap), so the body here depends on the path a message takes". The step is right for those messages, and a path to it, such as a branch that does not unmarshal, is what differs. BodyTypeRegistry.getBodyTypes(routeId, nodeId, kind) gives the types of one kind. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
…X, in the route coverage dump and in a body-type console So the body types a workload brought to each step can be read and kept, such as after a test run: - JMX: each processor has BodyTypes and BodyKinds, and each route counts its steps reached with more than one type (VariedBodyTypes) and with more than one kind (MixedBodyKinds). The route console reads its counts from there. - The route coverage dump (dumpRoutesCoverageAsXml, written by the Camel test kits) has bodyTypes and bodyKinds attributes on each step a message reached. - The new body-type dev console lists those steps per route, with varied and filter options. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
Collection is easier to understand than iterator. Data is one Java object with fields to read (a Map or an object), and a collection is many objects: a List, a Set or an array, or an iterator or a stream over them. A step reached with a List by some messages and a Map by others now needs attention too, such as a path that did not split. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
…coverage dump name the collection kind Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
gnodet-bot
left a comment
There was a problem hiding this comment.
Following up on my earlier review — no issues were raised previously, and the new commit holds up.
The commit renames Kind.ITERATOR to Kind.COLLECTION and corrects the kindOf() classification: ArrayList and arrays were previously DATA; they are now correctly COLLECTION since they implement Collection<?> / are arrays. The rename is propagated consistently across the SPI, implementation, all four message history factories, consoles, JBang CLI, MCP tools, and docs. The test in BodyFactsTest is updated to match — ArrayList → COLLECTION, iterator() → COLLECTION, and a new array case (String[] {"a"}) confirms array classification. The terminology change (iterator → collection) aligns the kind names with Java type semantics: a List is a collection, not just an iterator. No behavioral impact other than the reclassification of ArrayList from DATA to COLLECTION, which was a bug.
LGTM.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
…alk with the body types a running app saw
The YAML validator walks the body through the steps of a route and says nothing where the file does not
tell what the body is: after a consumer other than the file consumer, or a to: it cannot follow. When an
integration runs routes of the file, camel-jbang asks it for its route structure and passes what each
consumer gave and each producer returned to the validator (ProjectFacts.observed). Where all those bodies
were of one kind, the walk knows the body there: text for a payload, parsed data for data or a
collection, and the existing checks apply with the origin in the message ("in the running app, from:
platform-http:/orders gave a ByteArrayInputStreamCache").
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
… of each processor The processor console, and the route console with processors, has bodyTypes and bodyKinds per processor. The status file of the Camel CLI has the routes with their processors, so the tools that read it, such as the views of camel tui, get the body types of every step with no action. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
gnodet-bot
left a comment
There was a problem hiding this comment.
Following up on my earlier review — no issues were raised previously, and the new commit holds up.
The commit extends the processor dev console JSON output with bodyTypes and bodyKinds, using the same BodyTypeRegistry lookup that was introduced in the earlier commits. The implementation delegates to ProcessorDevConsole.includeProcessorsJSon() (already called by RouteDevConsole when processors=true), so the body-type data is automatically available in the CLI status file and any tool reading the route console with processors. The null/empty guard (if (!types.isEmpty())) is correctly applied — fields are omitted when no message history has been recorded, consistent with the (only present when ...) documentation in the JSON schema. The test (RouteStructureDevConsoleTest) exercises the processors=true path on routeConsole (not processorConsole directly), which is the right surface to test since that is the path the CLI status file follows. The OpenAPI schema and the two JSON descriptor files are updated consistently with the Java change.
Note: the text output of ProcessorDevConsole (includeProcessorsText) does not show body types, but that is a deliberate scope limit — the PR description says the text column is on route-structure, not on the processor console text view. No gap.
LGTM.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
…nt' into fold-25750
…mp' into fold-25750
…served' into fold-25750
…ion runs; Javadoc paragraphs in BodyFacts Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
gnodet-bot
left a comment
There was a problem hiding this comment.
Following up on my earlier review — no issues were raised previously, and the new commits hold up. The TypeKind refactor maintains correct concurrent access invariants, the observed-bodies integration is well-guarded, and the refactoring of RouteDevConsole to delegate to JMX MBeans is a clean improvement. All five risk areas verified clean.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
|
🧪 CI tested the following changed modules:
🔁 4 tests passed only after a retry on JDK 21 (4 retried attempts) Recovered flaky tests on JDK 21 (4)
🔬 Scalpel shadow comparison — Scalpel: 577 of 707 tested, 24 compile-only — current: 578 all testedMaveniverse Scalpel detected 577 affected modules (current approach: 578). Skip-tests mode would test 577 modules (16 direct + 564 downstream), skip tests for 24 (generated code, meta-modules) Modules only in current approach (1)
Modules Scalpel would test (577)
Modules with tests skipped (24)
💡 Manual integration tests recommended:
Build reactor — dependencies compiled but only changed modules were tested (16 modules, 3m 17s total)Total reactor time: 3m 17s
Top 20 slowest modules:
|
The message history records the body type at each node, but only for one exchange:
camel historyshows the last one. This keeps, per node, every type the node has been reached with across all the messages, so a tool can show what the body was at each step. That includes a branch the last message did not take, and every type where they varied. This is CAMEL-25750. The registry is the base, and three parts build on it: the hint on a body error (was #28180), reading the types over JMX, in the route coverage dump and in a dev console (was #28181), and the validator using what a running app saw (was #28185, part 2 of the issue). The three were stacked on this PR and are merged into it, so CI builds them once.The registry
BodyTypeRegistry(camel-api SPI, a context plugin): per route and node, the distinct body types in the order first seen, at most 8 per node. Each type also has a kind, for what a step can do with it:null;payload: text, bytes, a stream, a file, or a single value such as a number;data: one Java object with fields to read, a Map or an object;collection: many objects, a List, a Set or an array, or an iterator or a stream over them.A step reached with types of one kind is dynamic. A step reached with more than one kind needs attention: a step that writes or sends fails for the data, and one that reads fields fails for the payload.
Fed by all four message history factories (default, micrometer, metrics, opentelemetry) through
DefaultMessageHistory.captureBody. So it costs nothing unless the message history is on, as the dev profile turns it on. A route's types are forgotten when the route is removed, so a reload never mixes two versions.route-structure console:
bodyTypesandbodyKindson each processor line (JSON). In the text output, the types line up in a column after the code.processor console:
bodyTypesandbodyKindsper processor. The route console withprocessors=trueincludes them too, and that is what the Camel CLI's status file holds, so tools that read the status file get each step's body types with no extra action.route console:
variedBodyTypesandmixedBodyKindsper route. The CLI status file andcamel_runtime_routespass them on, so an AI that lists the routes sees them without asking.camel cmd route-structureshows the column.--variedshows only the steps reached with more than one type.AI and MCP tools:
get_route_structureandcamel_runtime_route_structuredescribe the fields and takevaried=true.Docs: a "Body types across messages" section on the Message History page, the Camel CLI diagnostics page, and the MCP tool table.
Choices
doInitand calls the newcaptureBody(exchange, registry). The oldcaptureBody(exchange)stays, and does the lookup, for factories outside Camel.BodyFacts.kindOf:isPayload, which the hints use, is unchanged.The hint on a body error (was #28180)
When a step fails on the body and the message history is on, the hint now says when other messages have reached the same step with another kind of body:
The step is right for some messages, and a path to it is what differs, such as a branch that does not unmarshal. A person or a model reading the error then looks at the branches before the step, instead of changing the step itself. The benchmark runs showed exactly this case: some paths marshal or unmarshal, others don't.
How
BodyFacts.hint(...)appends the clause, so every caller gets it with no change: the bean OGNL read, Groovy, jsonpath, jq, unmarshal, the HTTP producer, the platform-http reply and the file write. It is the last part of every message, and it reads right also where a caller wraps the hint in parentheses.BodyTypeRegistry.getBodyTypes(routeId, nodeId, kind)gives the types of one kind. The default registry keeps each type with its kind in one array, so a reader never sees the two half-updated.Reading the body types: JMX, the route coverage dump, a dev console (was #28181)
This lets the body types a workload brought to each step be read and kept. For example, run the messages of the expected production workload in a test, then see what each EIP has been reached with. There are three ways to read them, besides the Java API (the
BodyTypeRegistrycontext plugin):JMX:
BodyTypesandBodyKinds(String[], kinds asnull,payload,dataorcollection);VariedBodyTypes) and with bodies of more than one kind (MixedBodyKinds), which need attention.The route console now reads its
variedBodyTypes/mixedBodyKindsfrom these attributes, instead of counting them itself.The route coverage dump (
dumpRoutesCoverageAsXml, which the Camel test kits write when route coverage is enabled) hasbodyTypesandbodyKindsattributes on each step a message reached:A new
body-typedev console lists the steps a message has reached, per route, with their types and kinds.varied=truekeeps only the steps reached with more than one type, andfilterselects routes. The text form uses the same column ascamel cmd route-structure.The Message History docs get a "Dumping the body types" section, and the catalog gets the new console.
The validator uses what the running app saw (was #28185)
The YAML validator walks the body through the steps of each route, and stays quiet where the file does not say what the body is: after a consumer other than the file consumer, or after a
to:it cannot follow. When an integration runs routes of the file, with the message history on ascamel run --devdoes, those gaps are now filled with what the integration saw:So the next version of a file is checked against what the integration does with the current one. In dev mode a reload makes the registry forget the old route, so the facts always describe the version that runs. The checks reach
camel validate sourceand the AI and MCP tools that validate before they write.How
ProjectFacts.observed, keyedfrom:<uri>/to:<uri>without the query, asRouteGraph.normalizekeys the routes;TextBodyFlowuses it where its walk would not know the body, at the start of a route and after ato:. The walk's own knowledge comes first: the file consumer still gives the file as read.payloadis text,dataandcollectionare parsed data. More than one kind stays unknown, so nothing is said.add marshal: null.ValidatorFacts.readgets the file name, andObservedBodiesdoes the work:~/.camelfor status files first, and stops there when no integration runs, which is most validations;fromRouteStructure):tothat ends its block gives nothing, because the step after its block is reached by other paths too, and atoDhas no endpoint of its own.Also: when no integration runs,
ObservedBodieskeeps that answer for its 2 seconds too, so a write that validates twice lists~/.camelonce.Not in this PR
Tests
The registry:
Run locally against the changed core modules, through a scratch local repository:
BodyTypeRegistryTest: types across exchanges, the branch the last message did not take, null, no message history, route removal;DefaultBodyTypeRegistryTest: order, the cap, kinds beyond the cap, per route;BodyFactsTest(kindOf);MessageHistory*and route removal tests.CamelRouteStructureActionTest: column, kinds,--variedtable and JSON;ToolRegistryTest:variedfor the tool;AiPanelPromptBudgetTeststill within budget.RuntimeToolsTest,RuntimeToolsWiringTest(31).The hint:
BodyFactsOtherBodiesTest(camel-core):DefaultBodyTypeRegistryTest: the types of one kind.SimpleSyntaxHintsTest(33),BodyFactsTest,BodyTypeRegistryTestand theMessageHistory*tests, 58 in all.JMX, coverage dump, console:
ManagedBodyTypesTest(camel-management): the JMX attributes, the counts, nothing before a message, and the coverage dump.BodyTypeDevConsoleTest(camel-console): steps and kinds,varied,filter, nothing reached yet.The validator:
TextBodyFlowTest:The whole validator module (272) passes.
ObservedBodiesTest: the derivation, on the JSON shape a real route-structure console returns, captured from a test run; merging across routes; and matching a route's source to a file.ValidateWithObservedBodiesTest, end to end against a fake running integration (status file and answered action):SourceValidatorreports from what the integration saw;The whole camel-jbang-core suite passes (2,374 + 237).
After merging the three into this branch, the new and changed test classes of all four pass together, run locally through a scratch local repository: camel-core (19), camel-console (9), camel-management (3), camel-micrometer (1), camel-yaml-dsl-validator (31) and camel-jbang-core (35).
Claude Code on behalf of davsclaus
🤖 Generated with Claude Code