Skip to content

CAMEL-25138: Add Java and XML declarations in camel-semantic - #27082

Merged
luigidemasi merged 11 commits into
apache:mainfrom
luigidemasi:feature/CAMEL-25138-semantic-declarations
Sep 30, 2026
Merged

luigidemasi merged 11 commits into
apache:mainfrom
luigidemasi:feature/CAMEL-25138-semantic-declarations

Conversation

@luigidemasi

@luigidemasi luigidemasi commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Adds named semantic question declarations through extensions supplied by camel-semantic, with no changes to the Camel core model, SPI, standard XML loaders, model writers or core XSDs.

Java uses a static fluent helper inside an ordinary RouteBuilder:

semanticQuestions(this)
    .question("department").type("choice")
        .instructions("Which department should handle this message?")
        .criterion("billing", "Invoices and payments")
        .criterion("technical", "Bugs and outages")
    .register();

Import org.apache.camel.semantic.SemanticQuestionsBuilder.semanticQuestions. Multiple questions form one group through .end().question(name); .register() validates the complete group before replacing its source's definitions.

XML support is discovered automatically when camel-semantic and the XML DSL are present. Ordinary *.xml files (including the *.camel.xml alias) can contain either a standalone <semantic> document or <routes><semantic>...</semantic><route>...</route></routes>. The component recognizes declaration blocks or the semantic namespace. A declaration block can use its own semantic namespace inside standard Camel routes. No application loader registration is required. Dotted filenames such as my.tickets.xml and my.tickets.semantic.xml are supported. Route parsing uses the original resource bytes and preserves source locations and line numbers, including parser diagnostics.

The component wrapper delegates XML without declarations to the standard XML loader, retaining its beans and route configuration support. Custom application loaders registered before route resource discovery retain precedence. The wrapper lazily discovers other loaders once per registry and refreshes that snapshot at startup and reload, avoiding a registry scan for every extension check. The wrapper preserves resource ownership for reload and deletion tracking, manages its delegate lifecycle, and handles context restarts and application registries. Detection shares a resource snapshot only for the current call; it adds no persistent preparse cache. The probe skips non-declaration subtrees and stops at the end of the routes root, while still detecting declarations placed after routes.

Java, XML and existing YAML declarations share the context-wide registry, adapter contract, defaults and ref:/refs: evaluation. Coverage includes mixed batches, state selection, policies, duplicate and malformed declarations, numeric placeholders, cross-resource loading, replacement/removal, and deleted or renamed resources. Loading declarations performs no inference. The Java/XML dependencies are optional; existing language and YAML use does not require the new XML extension.

Declarations remain outside the core route model. Generic route dumps omit them, so applications must keep and load declarations separately. MCP conversion rejects Java/YAML inputs containing declarations instead of silently losing them. Its guard discovers the optional semantic registry at runtime; camel-semantic is a test dependency only, so ordinary conversions do not require the component. A failure to inspect an available registry propagates as a conversion failure. Extended XML also needs to be separated before generic conversion. The XML extension does not validate against the standard core XSDs; its syntax is validated by its loader. Automatic discovery applies to Camel route resources, not Spring XML application-context parsing or direct JAXB unmarshalling. Component documentation uses ordinary XML filenames throughout. There is no migration-guide entry because camel-semantic is new in 4.23.

The failed CI run also exposed a pre-existing downloader test double defect when combined with the service-restart changes on main (CAMEL-25070, #27080). The recording proxy now implements identity-based hashCode and equals, plus toString, so service registration can use it as a map key. All existing downloader assertions are unchanged; this follow-up changes test code only.

Validation:

  • Java 17: 611 tests passed (150 semantic, 421 MCP, 31 YAML interoperability, 5 standard XML loader and 4 documentation schema checks). New coverage verifies reuse and lifecycle refresh of loader discovery, custom loader precedence and declarations following nested routes.
  • CI regression: reproduced all nine downloader test errors against the PR merge's core, then ran the full Kamelet Main suite successfully with the fix on Java 25 and Java 17 (45 tests on each, zero failures/errors, eight existing skips).
  • Full repository clean install -DskipTests: all 695 modules passed.
  • Formatting, generated loader discovery, catalog documentation mirror and the absence of changes under core/ verified.

https://issues.apache.org/jira/browse/CAMEL-25138

Generated by Codex via /oss-address-review and /oss-fix-ci-errors on behalf of luigidemasi.

luigidemasi and others added 2 commits September 29, 2026 12:32
Register fluent Java and native XML declarations in the shared semantic
question registry before routes initialize. Preserve validation, source
ownership, reload behavior, and the existing YAML and provider contracts.

Preserve declarations in Java/YAML model exports, generate XML schemas and
catalog metadata, and document both declaration forms.

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Luigi De Masi <ldemasi@redhat.com>
Order semantic declarations before routes in the generated schemas and XML
writer, with schema validation coverage for routes and camel documents.
Use the supplied source consistently for question ownership, document the
reserved model: prefix, and remove redundant XML declaration assignments.

Document Java builder ordering and the runtime route dump limitation:
direct model exports preserve declarations, but runtime dumps omit them.
Explain the handwritten semantic serializers in the generator templates.

XML resources are parsed during preparse so shared questions exist before
route initialization. The loader's pending-cache invalidation is retained
because a failed batch prevents earlier builders from clearing their input;
retrying must read corrected resources.

Validation: 928 focused tests passed, 2 skipped; full repository clean
install with tests skipped; generated XML IO and Spring schema validation;
formatter validation and import-order checks.

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Luigi De Masi <ldemasi@redhat.com>
@github-actions

Copy link
Copy Markdown
Contributor

🌟 Thank you for your contribution to the Apache Camel project! 🌟
🤖 CI automation will test this PR automatically.

🐫 Apache Camel Committers, please review the following items:

  • First-time contributors require MANUAL approval for the GitHub Actions to run
  • You can use the command /component-test (camel-)component-name1 (camel-)component-name2.. to request a test from the test bot although they are normally detected and executed by CI.
  • You can label PRs using skip-tests and test-dependents to fine-tune the checks executed by this PR.
  • Build and test logs are available in the summary page. Only Apache Camel committers have access to the summary.

⚠️ Be careful when sharing logs. Review their contents before sharing them publicly.

@gnodet-bot gnodet-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well-structured feature PR with comprehensive test coverage (382-line dedicated test class, 11 parameterized invalid-input scenarios, Java+XML+YAML roundtrip, batch-failure recovery, reload/rename/delete lifecycle). A few observations for consideration:

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@apupier apupier left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

 [ERROR] Failures: 
[ERROR] org.apache.camel.catalog.DocExamplesXmlSchemaTest.everyXmlExampleOfTheDocumentationValidates
[ERROR]   Run 1: DocExamplesXmlSchemaTest.everyXmlExampleOfTheDocumentationValidates:145 Documentation XML examples that do not validate:
  semantic-language.adoc:144 example 1: cvc-elt.1.a: Cannot find the declaration of element 'camel'. ==> expected: <true> but was: <false>
[ERROR]   Run 2: DocExamplesXmlSchemaTest.everyXmlExampleOfTheDocumentationValidates:145 Documentation XML examples that do not validate:
  semantic-language.adoc:144 example 1: cvc-elt.1.a: Cannot find the declaration of element 'camel'. ==> expected: <true> but was: <false>
[ERROR]   Run 3: DocExamplesXmlSchemaTest.everyXmlExampleOfTheDocumentationValidates:145 Documentation XML examples that do not validate:
  semantic-language.adoc:144 example 1: cvc-elt.1.a: Cannot find the declaration of element 'camel'. ==> expected: <true> but was: <false>

Use the XML IO namespace for the native XML declaration example so the
catalog documentation schema check validates it, and regenerate its mirror.

Identify threshold and uncertainty in non-numeric validation errors while
preserving the question context and original cause. Cover both fields in
the existing invalid-reload tests, including preservation of the previous
definitions and recovery after corrected input. Clarify why duplicate
discovery of the stateless default configurer is harmless.

Validation: 109 semantic tests and 1082 catalog tests passed, including the
documentation schema checks. Full 695-module clean install passed with
tests skipped.

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Luigi De Masi <ldemasi@redhat.com>
@luigidemasi

Copy link
Copy Markdown
Contributor Author

@apupier Fixed the namespace in b6a0e7e: the XML IO example now declares http://camel.apache.org/schema/xml-io, and the catalog documentation mirror has been regenerated. All 1,082 catalog tests pass locally, including all four DocExamplesXmlSchemaTest checks; the full 695-module clean install also passes with tests skipped. This addresses your requested change.

Codex on behalf of luigidemasi.

@gnodet-bot gnodet-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of b6a0e7e — all previous findings addressed.

apupier's CHANGES_REQUESTED (DocExamplesXmlSchemaTest failure): Addressed — the XML documentation example namespace was corrected from camel-spring to camel-xml-io. The <camel> root element is declared in the xml-io schema, so the catalog XSD validation test should now pass.

gnodet-bot threshold parsing (DefaultSemanticDefinitionConfigurer.java): Addressed — parseDouble(String, String) helper added exactly as suggested, wrapping NumberFormatException into a clean IllegalArgumentException with field name and value. Two new parameterized test cases cover threshold="abc" and uncertainty="abc".

gnodet-bot TOCTOU on configurer discovery (SemanticDefinition.java): Addressed — clarifying comment explains that concurrent discovery creates equivalent stateless configurer instances, while the semantic module synchronizes access to shared question state.

No new issues in the follow-up commit.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@github-actions

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

🧪 CI tested the following changed modules:

  • catalog/camel-catalog
  • components/camel-ai/camel-semantic
  • dsl/camel-jbang/camel-jbang-mcp
  • dsl/camel-kamelet-main
  • dsl/camel-yaml-dsl/camel-yaml-dsl

🔬 Scalpel shadow comparison — Scalpel: 10 of 697 tested, 2 compile-only — current: 10 all tested

Maveniverse Scalpel detected 10 affected modules (current approach: 10).

Skip-tests mode would test 10 modules (5 direct + 0 downstream), skip tests for 2 (generated code, meta-modules)

Modules Scalpel would test (10)
  • camel-jbang-mcp ← dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/TransformTools.java, dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/TransformToolsTest.java, own pom dsl/camel-jbang/camel-jbang-mcp/pom.xml changed
  • camel-jbang-plugin-mcp ← depends on affected reactor module org.apache.camel:camel-jbang-core
  • camel-jbang-plugin-route-parser ← depends on affected reactor module org.apache.camel:camel-route-parser
  • camel-jbang-plugin-tui ← depends on affected reactor module org.apache.camel:camel-yaml-dsl-validator
  • camel-jbang-plugin-validate ← depends on affected reactor module org.apache.camel:camel-yaml-dsl-validator
  • camel-launcher-container ← depends on affected reactor module org.apache.camel:camel-launcher
  • camel-semantic ← components/camel-ai/camel-semantic/src/generated/resources/META-INF/services/org/apache/camel/routes-loader/semantic-xml, components/camel-ai/camel-semantic/src/main/docs/semantic-language.adoc, components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticQuestionBuilder.java, components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticQuestions.java, components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticQuestionsBuilder.java, components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticReloadPlugin.java, components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticXmlLoader.java, components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticXmlRoutesBuilderLoader.java, components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/yaml/SemanticDefinitionDeserializer.java, components/camel-ai/camel-semantic/src/test/java/org/apache/camel/semantic/SemanticDeclarationDslTest.java, components/camel-ai/camel-semantic/src/test/java/org/apache/camel/semantic/SemanticXmlAutoDiscoveryTest.java, components/camel-ai/camel-semantic/src/test/java/org/apache/camel/semantic/SemanticXmlLoaderTest.java, own pom components/camel-ai/camel-semantic/pom.xml changed
  • camel-typesafe-ai ← depends on affected reactor module org.apache.camel:camel-semantic
  • camel-yaml-dsl-validator ← depends on affected reactor module org.apache.camel:camel-catalog
  • camel-yaml-dsl-validator-maven-plugin ← depends on affected reactor module org.apache.camel:camel-yaml-dsl-validator
Modules with tests skipped (2)
  • camel-itest
  • camel-yaml-dsl-deserializers

ℹ️ Shadow mode — Scalpel observes but does not affect test execution. Learn more

⚠️ Some tests are disabled on GitHub Actions (@DisabledIfSystemProperty(named = "ci.env.name")) and require manual verification:

  • dsl/camel-jbang/camel-jbang-mcp: 1 test(s) disabled on GitHub Actions
All tested modules (37 modules, 5m 35s total)

Total reactor time: 5m 35s

Module Duration Status
Camel :: Launcher 49.9s SUCCESS
Camel :: JBang :: MCP 39.3s SUCCESS
Camel :: JBang :: Plugin :: TUI 33.4s SUCCESS
Camel :: Component DSL 27.3s SUCCESS
Camel :: Catalog :: Camel Catalog 23.1s SUCCESS
Camel :: YAML DSL 17.5s SUCCESS
Camel :: Docs 15.3s SUCCESS
Camel :: AI :: TypeSafe AI 15.1s SUCCESS
Camel :: JBang :: Plugin :: Kubernetes 14.7s SUCCESS
Camel :: Kamelet Main 12.5s SUCCESS
Camel :: AI :: Semantic Evaluation 11.6s SUCCESS
Camel :: YAML DSL :: Validator 9.8s SUCCESS
Camel :: YAML DSL :: Deserializers 8.1s SUCCESS
Camel :: Catalog :: Camel Route Parser 7.7s SUCCESS
Camel :: JBang :: Plugin :: Testing 7.6s SUCCESS
Camel :: Catalog :: Camel Report Maven Plugin 6.8s SUCCESS
Camel :: All Components Sync point 5.3s SUCCESS
Camel :: JBang :: Plugin :: Validate 4.6s SUCCESS
Camel :: YAML DSL :: Validator Maven Plugin 3.6s SUCCESS
Camel :: Catalog :: Maven 3.6s SUCCESS
Camel :: YAML DSL :: Maven Plugins 2.8s SUCCESS
Camel :: Catalog :: Suggest (deprecated) 2.3s SUCCESS
Camel :: Assembly 2.0s SUCCESS
Camel :: JBang :: Plugin :: Edit 1.6s SUCCESS
Camel :: Coverage 1.6s SUCCESS
Camel :: JBang :: Main 1.2s SUCCESS
Camel :: Catalog :: Dummy Component 0.9s SUCCESS
Camel :: JBang :: Plugin :: Generate 0.9s SUCCESS
Camel :: JBang :: Integration tests 0.9s SUCCESS
Camel :: Endpoint DSL :: Support 0.8s SUCCESS
Camel :: Catalog :: Console 0.8s SUCCESS
Camel :: Launcher :: Container 0.7s SUCCESS
Camel :: JBang :: Plugin :: Route Parser 0.6s SUCCESS
Camel :: JBang :: Plugin :: MCP 0.5s SUCCESS
Camel :: Endpoint DSL n/a
Camel :: Integration Tests n/a
Camel :: JBang :: Core n/a

Top 20 slowest modules:

  • Camel :: Launcher (49.9s)
  • Camel :: JBang :: MCP (39.3s)
  • Camel :: JBang :: Plugin :: TUI (33.4s)
  • Camel :: Component DSL (27.3s)
  • Camel :: Catalog :: Camel Catalog (23.1s)
  • Camel :: YAML DSL (17.5s)
  • Camel :: Docs (15.3s)
  • Camel :: AI :: TypeSafe AI (15.1s)
  • Camel :: JBang :: Plugin :: Kubernetes (14.7s)
  • Camel :: Kamelet Main (12.5s)
  • Camel :: AI :: Semantic Evaluation (11.6s)
  • Camel :: YAML DSL :: Validator (9.8s)
  • Camel :: YAML DSL :: Deserializers (8.1s)
  • Camel :: Catalog :: Camel Route Parser (7.7s)
  • Camel :: JBang :: Plugin :: Testing (7.6s)
  • Camel :: Catalog :: Camel Report Maven Plugin (6.8s)
  • Camel :: All Components Sync point (5.3s)
  • Camel :: JBang :: Plugin :: Validate (4.6s)
  • Camel :: YAML DSL :: Validator Maven Plugin (3.6s)
  • Camel :: Catalog :: Maven (3.6s)

⚙️ View full build and test results

@apupier

apupier commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

slightly different test error:

 [camel-xml-io] [ERROR] Tests run: 398, Failures: 0, Errors: 2, Skipped: 0, Time elapsed: 4.663 s <<< FAILURE! -- in org.apache.camel.xml.out.ModelWriterTest
  [camel-xml-io] [ERROR] org.apache.camel.xml.out.ModelWriterTest.semanticDeclarationsBeforeRoutesConformToTheSchema(String)[1] -- Time elapsed: 0.175 s <<< ERROR!
  org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 55; cvc-elt.1.a: Cannot find the declaration of element 'routes'.
  	at java.xml/com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:204)
  	at java.xml/com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.error(ErrorHandlerWrapper.java:135)
  	at java.xml/com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:396)
  	at java.xml/com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:327)
  	at java.xml/com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:284)
  	at java.xml/com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.handleStartElement(XMLSchemaValidator.java:2133)
  	at java.xml/com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.startElement(XMLSchemaValidator.java:830)
  	at java.xml/com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.scanStartElement(XMLNSDocumentScannerImpl.java:374)
  	at java.xml/com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl$NSContentDriver.scanRootElementHook(XMLNSDocumentScannerImpl.java:613)
  	at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:3079)
  	at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$PrologDriver.next(XMLDocumentScannerImpl.java:836)
  	at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:605)
  	at java.xml/com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:112)
  	at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:542)
  	at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:889)
  	at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:825)
  	at java.xml/com.sun.org.apache.xerces.internal.jaxp.validation.StreamValidatorHelper.validate(StreamValidatorHelper.java:178)
  	at java.xml/com.sun.org.apache.xerces.internal.jaxp.validation.ValidatorImpl.validate(ValidatorImpl.java:115)
  	at java.xml/javax.xml.validation.Validator.validate(Validator.java:124)
  	at org.apache.camel.xml.out.ModelWriterTest.semanticDeclarationsBeforeRoutesConformToTheSchema(ModelWriterTest.java:90)

Read the generated schema's target namespace in the declaration-order test.
A clean test build produces the Spring namespace, while an incremental
build after packaging can retain the XML IO namespace. The hard-coded
Spring namespace therefore failed in CI's Java 17 test phase.

Accept only the two expected namespaces and keep schema validation for
semantic declarations before routes under both routes and camel roots.

Validation: reproduced the two CI failures on Java 17 before the fix.
The XML IO suite passes on Java 17 with clean and packaged schemas and on
Java 25 (425 tests per run, including two existing skips). The full
695-module clean install passes with tests skipped. All 11 CI-selected modules also pass on Java 17: 2928
tests, no failures or errors, and two existing skips.

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Luigi De Masi <ldemasi@redhat.com>

@davsclaus davsclaus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @luigidemasi, bringing Java and XML to parity with the YAML semantic block is nicely done, and the generated artifacts (xml-io parser/writer, XSDs, model JSON, catalog mirror) are all committed. Some findings:

  1. JAXB XML DSL drops <semantic>: JaxbXmlRoutesBuilderLoader (camel-xml-jaxb-dsl, not touched by this PR) only copies routes.getRoutes(). Now that the model and schema accept <routes><semantic>, JAXB parses it into RoutesDefinition.semantic, but it is silently ignored and the routes then fail with "Unknown semantic question". Either add getRouteCollection().setSemantic(routes.getSemantic()) there, or document that only xml-io supports it.
  2. Catalog metadata for the numeric options (inline).
  3. Stale preparse cache in the xml-io loader (inline).
  4. Java export crashes on placeholders (inline).
  5. Design question: this puts an AI-specific concept into the core model (RoutesDefinition/BeansDefinition) and RouteBuilder.semanticQuestions(). There is precedent (tokenizer()), so I'm not against it, but two things would make it sit better in core: SemanticDefinition does a FactoryFinder/context-plugin lookup in a static configure(), and I'd rather keep model classes data-only and move that into a helper; and SemanticDefinitionConfigurer is an SPI placed in model.app rather than an spi package. A @since 4.23 on the new RouteBuilder method would also be good.
  6. Minor: the MCP TransformTools keeps declarations for XML→YAML but drops them for YAML→XML and Java→YAML/XML.
  7. Nit: in RoutesDefinition the new semantic getter/setter sit between the fields and the constructor; please move them next to the other accessors.

Also, @apupier's CHANGES_REQUESTED (the DocExamplesXmlSchemaTest namespace failure) looks addressed by b6a0e7e, so a re-review from him would unblock that.

Claude Code on behalf of davsclaus

This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.

…port

Keep semantic declarations when loading JAXB routes and converting YAML or
Java routes through MCP. Export registered question policies and prepare
Java expression models without starting routes or invoking providers.

Refresh XML preparse caches when resource content changes after a failed
batch, while preserving unchanged preparse and deferred bean ownership.
Add regression coverage for foreign-loader and earlier-builder failures.

Preserve numeric placeholders in Java exports and resolve them before
runtime validation. Add numeric catalog types and defaults, move optional
component discovery into a helper, relocate the configurer SPI, and keep
RoutesDefinition accessors together.

Validation: 2978 tests across the affected Java 17 module suites, with no
failures or errors and two existing skips. Full repository clean install
with tests skipped passes on Java 25. Regenerated artifacts are included.

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Luigi De Masi <ldemasi@redhat.com>
@luigidemasi

luigidemasi commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor Author

@davsclaus, the remaining points from your review are fixed in 02f4ad4b4f46:

  • 1 — JAXB: the loader now retains semantic declarations before registration, with runtime regression coverage and documented ordering for separate declaration resources.
  • 5 — Model design: optional-component lookup moved to SemanticDefinitionHelper, the configurer SPI moved to org.apache.camel.model.spi, and RouteBuilder.semanticQuestions() now has @since 4.23.
  • 6 — MCP conversion: YAML-to-XML and Java-to-YAML/XML retain registered questions and their effective policies. Java semantic references also survive serialization. Regression tests reload each conversion and verify boolean, choice and score declarations.
  • 7 — Accessors: the semantic getter and setter now sit beside the other accessors in RoutesDefinition.

Generated by Codex via /oss-address-review on behalf of luigidemasi.

@luigidemasi
luigidemasi requested a review from apupier September 29, 2026 15:12

@davsclaus davsclaus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @luigidemasi, all points from my previous review are addressed, with good regression coverage (JAXB retention, content-aware preparse cache incl. <camel> roots and deferred beans, placeholder-preserving Java export, MCP round-trips). The model.spi move and SemanticDefinitionHelper make the core side sit much better.

A few optional follow-ups (inline), none blocking.

@apupier your CHANGES_REQUESTED (schema failures) appears addressed by 987488d and CI is green; could you re-review?

Claude Code on behalf of davsclaus

This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.


RoutesDefinition rd = new RoutesDefinition();
rd.setRoutes(routeDefs);
rd.setSemantic(DefaultSemanticDefinitionConfigurer.getDefinition(ctx));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this ties the MCP module to the camel-semantic implementation class through a public static helper. Adding an export method to org.apache.camel.model.spi.SemanticDefinitionConfigurer (looked up as a context plugin like SemanticDefinitionHelper does) would keep MCP on the SPI only. Also note the conversion writes resolved values and explicit defaults, and a placeholder with no default would probably fail to resolve in this throwaway context.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 0bb166a78dfe. Export now goes through SemanticDefinitionConfigurer, with context-plugin lookup and lazy discovery in SemanticDefinitionHelper; this also covers YAML-only registrations. MCP no longer references the implementation class.

The placeholder observation is correct: context-based conversion needs a resolved numeric value. The documentation now states that requirement, and tests cover both defaulted placeholders and missing required properties. Conversion errors now include the underlying cause so YAML reports the missing key instead of only a preparse failure.

Generated by Codex via /oss-address-review on behalf of luigidemasi.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The architecture changed in 4e03971ee3ec to keep declarations entirely inside camel-semantic; the core SPI and model exporter are removed. MCP now checks the public SemanticQuestions registry and rejects conversions containing declarations, explaining that declarations must be kept separately. This avoids silently dropping definitions without adding a semantic SPI to core. Numeric placeholder errors remain covered.

Generated by Codex via /oss-address-review on behalf of luigidemasi.


// Java expression clauses are normally materialized when processors are created.
routeDefs.forEach(route -> ProcessorDefinitionHelper.filterTypeInOutputs(route.getOutputs(), ExpressionNode.class)
.forEach(ExpressionNode::preCreateProcessor));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pre-creates expression clauses for every converted Java route, not only semantic ones. Looks like a general fix for Java→YAML/XML. Could you add a small test with a non-semantic expression clause (e.g. .filter().simple(...)) so it's covered?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 0bb166a78dfe. The parameterized regression converts a non-semantic .filter().simple(...) with a nested .setBody().simple(...) to both YAML and XML, reloads each output, and verifies that both Simple expressions are preserved.

Generated by Codex via /oss-address-review on behalf of luigidemasi.

@davsclaus
davsclaus self-requested a review September 29, 2026 17:24
luigidemasi and others added 2 commits September 30, 2026 08:47
Export registered questions through the model configurer SPI, including
lazy discovery for YAML-only declarations, and omit default boolean
decision policies from converted output.

Cover Java filter and nested Simple expression conversion to YAML/XML,
default-policy round trips, and numeric placeholders. Include the
underlying error when conversion fails and document temporary-context
property resolution.

Validation: 549 tests passed on Java 17 across core-model, semantic and
MCP. Full repository clean install with tests skipped passed on Java 21
across all 695 modules. Regenerated catalog documentation is included.

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Luigi De Masi <ldemasi@redhat.com>
Use a fluent helper in ordinary Java RouteBuilder classes and an optional
semantic.xml loader for standalone declarations or declarations alongside
routes. Reuse existing builder lifecycle and XML parsing hooks, removing
the semantic model, SPI, writers and schema changes from core.

Preserve atomic validation and source ownership during reload. Reject
lossy generic conversions and document the declaration/export boundary.

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Luigi De Masi <ldemasi@redhat.com>
@luigidemasi luigidemasi changed the title CAMEL-25138: Add semantic question declarations to Java and XML DSLs CAMEL-25138: Add Java and XML declarations in camel-semantic Sep 30, 2026

@gnodet-bot gnodet-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of 4e03971 — the architectural pivot to keep declarations entirely in camel-semantic is clean.

Previous findings status:

  • apupier's CHANGES_REQUESTED (DocExamplesXmlSchemaTest namespace failure): Addressed since b6a0e7e. The semantic-language documentation examples are now correctly skipped in DocExamplesXmlSchemaTest since they use a component-owned loader outside the core schemas.

  • apupier's follow-up (ModelWriterTest.semanticDeclarationsBeforeRoutesConformToTheSchema failure): Addressed — the test no longer exists because 4e03971 removes the semantic model from camel-core-model entirely. No semantic references remain in ModelWriterTest.

  • davsclaus review (7 findings): All addressed. The major design concern (#5 — AI concept in core model) is fully resolved by moving declarations, the configurer SPI, and the XML loader into camel-semantic. MCP TransformTools now rejects inputs with declarations instead of silently losing them.

  • gnodet-bot findings (threshold parsing, TOCTOU, cache clearing): All previously addressed; the refactored code in SemanticQuestionBuilder.parseDouble() retains the field-specific diagnostics.

New code review:

  • SemanticXmlRoutesBuilderLoader: XXE protection is complete (disallow-doctype-decl, FEATURE_SECURE_PROCESSING, empty ACCESS_EXTERNAL_* on both DocumentBuilderFactory and TransformerFactory). Test confirms DTD rejection. Namespace validation covers empty, semantic, xml-io, and spring namespaces. Error paths are clean — failed parsing does not replace previous definitions.

  • SemanticQuestionsBuilder / DeclarationsLifecycle: The WeakHashMap-backed registered set is accessed only under synchronized(questions) (same SemanticQuestions monitor in both register() and afterConfigure()). Thread-safe.

  • SemanticQuestions.isEmpty(): Not synchronized, but only called from TransformTools.requireSeparateDeclarations() on a throwaway per-call context with no concurrent access. Fine.

  • Test coverage: 488-line SemanticDeclarationDslTest covers Java/XML/namespace variants, reload/rename/delete lifecycle, failed batch recovery, cross-resource ordering, placeholder resolution, and 13 parameterized invalid-input scenarios. Solid.

No new issues.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

Automatically install XML declaration support through the existing context
service plugin. Detect declarations or their namespace, delegate ordinary
XML to the standard loader, and retain application loader precedence.
Preserve resource ownership and manage loader startup, shutdown and restart.

Use ordinary XML filenames throughout the documentation and cover both
layouts, Camel Main, custom registries, namespace handling and reloads.

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Luigi De Masi <ldemasi@redhat.com>

@davsclaus davsclaus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moving this out of core is a good change, thanks. I re-reviewed the new design:

  1. Routes in *.semantic.xml lose their source location and get wrong line numbers. SemanticXmlRoutesBuilderLoader.parse() removes the <semantic> element from the DOM, re-serialises the document and parses it with new ModelParser(new StringReader(xml), namespace). With the Reader constructor the resource is null, so no location is set, and line numbers point into the re-serialised text, shifted by the removed block. This breaks the debugger, the dev console route source and XmlPullParserLocationException messages when sourceLocationEnabled is on. Suggestion: parse the original Resource with a ModelParser subclass whose ignoreUnexpectedElement skips semantic under routes. That also removes the DOM → Transformer round trip.
  2. DefaultRoutesLoader takes everything after the first dot as the extension, so my.tickets.semantic.xml finds no loader. Please document that the base name must not contain dots (or handle it).
  3. camel-jbang-mcp gets a new compile dependency on camel-semantic only for SemanticQuestions.isEmpty() in requireSeparateDeclarations. Is the clearer error worth the dependency?

Claude Code on behalf of davsclaus

@gnodet-bot gnodet-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of 438d870 — automatic XML discovery is a clean follow-up.

davsclaus's 3 findings status:

  1. Source location/line numbers (Transformer re-serialization in SemanticXmlRoutesBuilderLoader.parse()): Still present when routes sit alongside <semantic> blocks — new ModelParser(new StringReader(xml.toString()), namespace) loses the resource URI. However, the new auto-discovery wrapper (SemanticXmlLoader) routes ordinary XML files without declarations through the standard loader, preserving full location tracking for the common case. Only files with explicit <semantic> blocks lose source location — a narrowed scope compared to the previous *.semantic.xml-only design.

  2. Dot-in-filename (my.tickets.semantic.xml fails with DefaultRoutesLoader): Effectively addressed — users now use ordinary *.xml files. The *.semantic.xml extension remains as a registered fallback via @RoutesLoader("semantic.xml") but is no longer the documented path.

  3. MCP dependency on camel-semantic: Still present — camel-jbang-mcp depends on camel-semantic for SemanticQuestions.isEmpty() in requireSeparateDeclarations(). This is a conscious design choice: MCP needs to detect and reject lossy conversions with declarations.

New code review:

  • SemanticXmlLoader: Clean wrapper pattern. hasDeclarations() uses StAX for lightweight scanning with proper XXE protection (SUPPORT_DTD=false, IS_SUPPORTING_EXTERNAL_ENTITIES=false). Stream is in try-with-resources, reader is closed in finally. CachedResource snapshot shares bytes within a single call without persistent caching.

  • isSupportedExtension(): Correctly yields to application-registered XML loaders by checking the registry. The this identity check avoids self-matching.

  • delegate(): Synchronized lazy init resolves the real XML loader via BootstrapFactoryFinder to avoid circular lookup through RoutesLoader. Delegate lifecycle is managed in doStop().

  • SemanticReloadPlugin.installXmlLoader(): Defensive installation — skips when ModelParser class is absent, when the loader is already registered, or when another XML-capable loader exists. The onContextInitializing lifecycle callback handles applications that replace the registry after the eager build phase.

  • afterConfigure interceptor on delegated XML builders: Correctly removes declarations from the semantic registry when a file that previously had declarations is reloaded without them. Keyed by resource.getLocation() (original, not snapshot).

  • Test coverage: SemanticXmlAutoDiscoveryTest (181 lines) covers Camel Main integration, delegate beans/route-configuration/lifecycle, custom loader precedence (both naming conventions), missing XML support graceful fallback, application registry contexts, and context restart/reinstall. Existing SemanticDeclarationDslTest updated to use ordinary .xml filenames with a semantic-extension variant preserving backward-compat coverage.

No new issues.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

Parse routes from their original resource bytes while skipping the validated
semantic declaration subtree. Accept dotted XML filenames through the
component loader and retain custom loader precedence.

Discover the optional semantic registry at runtime before generic MCP
exports, preserving rejection of lossy conversions without a production
dependency on camel-semantic. Cover original locations and diagnostics,
dotted filenames, and empty registries; document the XML filename support.

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Luigi De Masi <ldemasi@redhat.com>
@luigidemasi

Copy link
Copy Markdown
Contributor Author

Addressed your review in dc1a33c6385a.

  1. Route parsing now uses the original XML bytes and resource location. A component-owned ModelParser subclass consumes only the already-validated top-level declaration subtree, eliminating the DOM-to-Transformer round trip. Regression tests check route and processor line numbers, source locations and parser diagnostics; misplaced nested declarations still fail.
  2. The automatic loader accepts dotted XML filenames, including my.tickets.xml and my.tickets.semantic.xml. Camel Main tests cover standalone declarations and declarations alongside routes, and a custom loader for a dotted extension retains precedence. Documentation now explicitly allows dotted filenames.
  3. camel-semantic is now a test dependency only in MCP. The converter discovers the optional registry at runtime and still rejects exports containing declarations. Inspection failures propagate as conversion errors. Tests cover populated and empty registries; another run physically excludes the component from the runtime classpath and verifies ordinary conversions.

Validation: 567 Java 17 module tests, 11 checks without the semantic dependency, and the full 695-module build with tests skipped passed.

Generated by Codex via /oss-address-review on behalf of luigidemasi.

@gnodet-bot gnodet-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of dc1a33c — all three davsclaus findings addressed.

davsclaus finding #1 (Source location/line numbers): Addressed — the Transformer re-serialization is completely removed. SemanticModelParser extends ModelParser, sets this.resource = resource in the constructor (preserving the resource URI), and overrides handleUnexpectedElement to skip <semantic> blocks at depth 2 via parser.skipSubTree(). Routes are parsed from the original bytes with correct line numbers. declarationsPreserveOriginalRouteSourceLocations verifies route line 8, input line 9, filter line 10, log line 12 across four filename variants including dotted names. nestedSemanticElementIsRejectedWithOriginalSourceLocation confirms that a misplaced <semantic/> inside a filter produces a XmlPullParserLocationException with the correct resource name and line number.

davsclaus finding #2 (Dot-in-filename): Addressed — isSupportedExtension now uses extension.endsWith(".xml") instead of "camel.xml".equals(extension). Application loaders for specific extensions retain precedence via the registry check. applicationLoaderForDottedExtensionTakesPrecedence verifies a custom "tickets.xml" loader is resolved ahead of the wrapper. Parameterized tests cover my.tickets.xml and my.tickets.semantic.xml.

davsclaus finding #3 (MCP compile dependency): Addressed — camel-semantic moved from compile to test scope in camel-jbang-mcp. The requireSeparateDeclarations guard now uses context.getClassResolver().resolveClass(...) + reflection to discover the optional registry at runtime. When the class is absent, the guard returns cleanly. emptySemanticRegistryDoesNotPreventConversion covers the empty-registry path.

No new issues.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@davsclaus

Copy link
Copy Markdown
Contributor

Follow-up: my previous review was written against 4e03971 and crossed with 438d870 / dc1a33c, which already address all three points. Thanks:

  1. Source positions: routes are now parsed from the original resource stream with a ModelParser subclass that skips the validated <semantic> subtree, so location and line numbers are kept.
  2. Dotted file names: declarations are detected in ordinary *.xml resources, so my.tickets.xml works.
  3. MCP: camel-semantic is now test scope only, and the guard resolves SemanticQuestions reflectively.

Remaining small points on the new design:

  • Upgrade guide: camel-semantic is new in 4.23 (firstVersion 4.23.0), so nobody upgrades from an older behaviour. The upgrade guide is for migration only; please drop the "camel-semantic - automatic XML route loading" section. The semantic-language.adoc update already documents it.
  • With camel-semantic on the classpath, SemanticXmlLoader now fronts all XML route loading: every XML resource is cached and gets an extra StAX pass in hasDeclarations(), which reads to the end of the document when there are no declarations. Consider skipping each depth-2 route subtree, or stopping once you are past the <routes> children. Minor.
  • SemanticXmlLoader.isSupportedExtension() does a findByType(RoutesBuilderLoader.class) registry lookup on every call. Minor.

Claude Code on behalf of davsclaus

Limit declaration detection to top-level elements, skip route subtrees and
stop at the closing routes tag while allowing declarations after routes.
Reuse lazily discovered application loaders and refresh discovery at
startup, reload and registry replacement instead of scanning on each check.

Remove the migration-guide note for the new component and retain its
component documentation. Cover discovery reuse, refresh and late XML
declarations with regression tests.

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Luigi De Masi <ldemasi@redhat.com>
@luigidemasi

Copy link
Copy Markdown
Contributor Author

Addressed the three remaining points in your follow-up in a52e1b078481.

  1. Removed the upgrade-guide subsection. The feature remains documented in semantic-language.adoc and its catalog mirror; there is no migration from a previous release.
  2. The XML probe now skips non-declaration subtrees and returns at the closing routes tag. It continues checking following siblings, so declarations after routes still work. It still consumes XML structurally; this change avoids the extra declaration-detection branches inside subtrees without adding a persistent resource cache.
  3. Other loaders are discovered lazily and reused across extension checks. Discovery is refreshed at context initialization and reload, discarded on stop, and repeated if the registry changes. Tests assert one registry lookup across repeated checks, refresh after registration/removal, and custom-loader precedence after startup.

Validation: 611 Java 17 tests passed, followed by the full 695-module clean install -DskipTests build. No core or standard XML loader changes.

Generated by Codex via /oss-address-review on behalf of luigidemasi.

@gnodet-bot gnodet-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of a52e1b0 — all 3 davsclaus follow-up points addressed.

davsclaus finding #1 (upgrade guide): Addressed — the camel-semantic - automatic XML route loading section is removed from camel-4x-upgrade-guide-4_23.adoc. Feature is new in 4.23, no migration needed.

davsclaus finding #2 (StAX probe scans full document): Addressed — hasDeclarations() now skips non-declaration subtrees via skipSubtree() and returns false at the closing </routes> tag (--depth == 0). Only direct children of <routes> are inspected by name; nested route content is consumed structurally without checking element names or namespaces. nestedRoutesDoNotHideFollowingDeclarations test confirms that <semantic/> inside CDATA is not falsely detected, while a real <semantic> block after routes is still found.

davsclaus finding #3 (per-call registry lookup in isSupportedExtension): Addressed — otherLoaders() caches discovered loaders keyed by registry identity, reused across repeated isSupportedExtension calls. Cache is invalidated on context initialization (onContextInitializing), route reload (onReload), and stop (doStop) via resetLoaderDiscovery(). SemanticReloadPlugin.xmlLoader field narrowed to SemanticXmlLoader for the resetLoaderDiscovery() call. loaderDiscoveryIsReusedAndRefreshedOnReload test asserts exactly 1 registry lookup across 5 extension checks, refresh to 2 after custom loader registration, and back to 3 after removal.

No new issues in the follow-up commit.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

The service restart changes on main track services in a ConcurrentHashMap.
The downloader test proxy returned null for hashCode, so all nine properties
function tests failed during service registration before their assertions.

Give the recording proxy identity-based hashCode and equals implementations
and a useful toString without changing the downloader assertions.

Failed run: https://github.com/apache/camel/actions/runs/36707716997

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Luigi De Masi <ldemasi@redhat.com>

@gnodet-bot gnodet-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of f57b10f — test proxy identity fix.

The recording DependencyDownloader proxy lacked hashCode/equals/toString, which broke when CAMEL-25070 service-restart changes on main use the proxy as a map key. The fix is textbook-correct: identity-based hashCode via System.identityHashCode, reference equality for equals, and a descriptive toString. Test-only, no production code touched.

All previous findings (davsclaus: upgrade guide, StAX probe scope, loader discovery caching; gnodet-bot: threshold parsing, TOCTOU, XXE protection) remain addressed from prior commits.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@luigidemasi
luigidemasi merged commit d5ff834 into apache:main Sep 30, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants