You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2026/09/16 08:07:38 UTC
(camel) branch feature/CAMEL-24710-catalog-checks created (now 045d7166dcd7)
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a change to branch feature/CAMEL-24710-catalog-checks
in repository https://gitbox.apache.org/repos/asf/camel.git
at 045d7166dcd7 CAMEL-24710: docs - the YAML examples pass the catalog checks too, guarded by CatalogDocExamplesTest in camel-jbang-core: the checks read a comment after a value, a block scalar and a nested map in parameters:, a dotted or prefixed header name and name: only under setHeader; the catalog validator accepts option.key for a Map option; crypto, solr and box declare the headers their examples use; xmlSecurity, metrics, fop and openai examples corrected
This branch includes the following new commits:
new 045d7166dcd7 CAMEL-24710: docs - the YAML examples pass the catalog checks too, guarded by CatalogDocExamplesTest in camel-jbang-core: the checks read a comment after a value, a block scalar and a nested map in parameters:, a dotted or prefixed header name and name: only under setHeader; the catalog validator accepts option.key for a Map option; crypto, solr and box declare the headers their examples use; xmlSecurity, metrics, fop and openai examples corrected
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(camel) 01/01: CAMEL-24710: docs - the YAML examples pass the catalog checks too, guarded by CatalogDocExamplesTest in camel-jbang-core: the checks read a comment after a value, a block scalar and a nested map in parameters:, a dotted or prefixed header name and name: only under setHeader; the catalog validator accepts option.key for a Map option; crypto, solr and box declare the headers their examples use; xmlSecurity, metrics, fop and openai examples corrected
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch feature/CAMEL-24710-catalog-checks
in repository https://gitbox.apache.org/repos/asf/camel.git
commit 045d7166dcd7ecf83088eebf7984aed13aab7ca6
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Wed Sep 16 10:07:25 2026 +0200
CAMEL-24710: docs - the YAML examples pass the catalog checks too, guarded by CatalogDocExamplesTest in camel-jbang-core: the checks read a comment after a value, a block scalar and a nested map in parameters:, a dotted or prefixed header name and name: only under setHeader; the catalog validator accepts option.key for a Map option; crypto, solr and box declare the headers their examples use; xmlSecurity, metrics, fop and openai examples corrected
Co-Authored-By: Claude Opus 5 (1M context) <no...@anthropic.com>
Signed-off-by: Claus Ibsen <cl...@gmail.com>
---
.../org/apache/camel/catalog/components/box.json | 3 +
.../apache/camel/catalog/components/crypto.json | 5 +-
.../org/apache/camel/catalog/components/solr.json | 4 +-
.../apache/camel/catalog/docs/fop-component.adoc | 8 +-
.../camel/catalog/docs/metrics-component.adoc | 8 +-
.../camel/catalog/docs/openai-component.adoc | 16 +--
.../camel/catalog/docs/xmlSecurity-dataformat.adoc | 20 ++--
.../org/apache/camel/catalog/CamelCatalogTest.java | 13 +++
.../src/main/docs/openai-component.adoc | 16 +--
.../org/apache/camel/component/box/box.json | 3 +
.../apache/camel/component/box/BoxEndpoint.java | 3 +-
.../camel/component/box/internal/BoxConstants.java | 7 +-
.../org/apache/camel/component/crypto/crypto.json | 5 +-
.../crypto/DigitalSignatureConstants.java | 3 +
.../camel-fop/src/main/docs/fop-component.adoc | 8 +-
.../src/main/docs/metrics-component.adoc | 8 +-
.../org/apache/camel/component/solr/solr.json | 4 +-
.../apache/camel/component/solr/SolrConstants.java | 6 +
.../src/main/docs/xmlSecurity-dataformat.adoc | 20 ++--
.../camel/catalog/impl/AbstractCamelCatalog.java | 18 ++-
.../dsl/jbang/core/commands/ai/EndpointChecks.java | 30 ++++-
.../dsl/jbang/core/commands/ai/HeaderChecks.java | 54 ++++++++-
.../dsl/jbang/core/commands/ai/YamlLines.java | 27 +++++
.../core/commands/ai/CatalogDocExamplesTest.java | 128 +++++++++++++++++++++
.../commands/ai/SourceValidatorEndpointTest.java | 58 ++++++++++
.../dsl/yaml/validator/EipDocExamplesTest.java | 11 +-
26 files changed, 411 insertions(+), 75 deletions(-)
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/box.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/box.json
index 7327874b58e2..f17f0102eb31 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/box.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/box.json
@@ -45,6 +45,9 @@
"userName": { "index": 17, "kind": "property", "displayName": "User Name", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.box.BoxConfiguration", "configurationField": "configuration", "description": "Box user name, MUST be provided" },
"userPassword": { "index": 18, "kind": "property", "displayName": "User Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.box.BoxConfiguration", "configurationField": "configuration", "description": "Box user password, MUST be provided if authSecureStorage is not set, or returns null on [...]
},
+ "headers": {
+ "CamelBox.": { "index": 0, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "Object", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The prefix of the headers that carry the parameters of the API method: the header CamelBox.fileName holds the fileName parameter.", "constantName": "org.apache.camel.component.box.internal.BoxConstants#PROPERTY_PREFIX" }
+ },
"properties": {
"apiName": { "index": 0, "kind": "path", "displayName": "Api Name", "group": "common", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.box.internal.BoxApiName", "enum": [ "COLLABORATIONS", "COMMENTS", "EVENT_LOGS", "FILES", "FOLDERS", "GROUPS", "EVENTS", "SEARCH", "TASKS", "USERS" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.box.BoxConfiguration", "configura [...]
"methodName": { "index": 1, "kind": "path", "displayName": "Method Name", "group": "common", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.box.BoxConfiguration", "configurationField": "configuration", "description": "What sub operation to use for the selected operation" },
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/crypto.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/crypto.json
index 17f736dc943c..9f14e382ba45 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/crypto.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/crypto.json
@@ -49,8 +49,9 @@
"headers": {
"CamelSignaturePrivateKey": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.security.PrivateKey", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The PrivateKey that should be used to sign the message", "constantName": "org.apache.camel.component.crypto.DigitalSignatureConstants#SIGNATURE_PRIVATE_KEY" },
"CamelSignaturePublicKeyOrCert": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Certificate or PublicKey", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The Certificate or PublicKey that should be used to verify the signature", "constantName": "org.apache.camel.component.crypto.DigitalSignatureConstants#SIGNATURE_PUBLIC_KEY_OR_CERT" },
- "CamelSignatureKeyStoreAlias": { "index": 2, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The alias used to query the KeyStore for keys and Certificates to be used in signing and verifying exchanges", "constantName": "org.apache.camel.component.crypto.DigitalSignatureConstants#KEYSTORE_ALIAS" },
- "CamelSignatureKeyStorePassword": { "index": 3, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "char[]", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The password used to access an aliased PrivateKey in the KeyStore.", "constantName": "org.apache.camel.component.crypto.DigitalSignatureConstants#KEYSTORE_PASSWORD" }
+ "CamelDigitalSignature": { "index": 2, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "byte[] (Base64 encoded)", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The signature of the message, set by the sign operation and read by the verify operation (the signatureHeaderName option changes the header name)", "constantName": "org.apache.camel.component.crypto.DigitalSignatureConstant [...]
+ "CamelSignatureKeyStoreAlias": { "index": 3, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The alias used to query the KeyStore for keys and Certificates to be used in signing and verifying exchanges", "constantName": "org.apache.camel.component.crypto.DigitalSignatureConstants#KEYSTORE_ALIAS" },
+ "CamelSignatureKeyStorePassword": { "index": 4, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "char[]", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The password used to access an aliased PrivateKey in the KeyStore.", "constantName": "org.apache.camel.component.crypto.DigitalSignatureConstants#KEYSTORE_PASSWORD" }
},
"properties": {
"cryptoOperation": { "index": 0, "kind": "path", "displayName": "Crypto Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.crypto.CryptoOperation", "enum": [ "sign", "verify" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.crypto.DigitalSignatureConfiguration", "configurationField": "configuration", "description": "Set the Crypto o [...]
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/solr.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/solr.json
index 97c66fc517d0..299fa0fe908c 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/solr.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/solr.json
@@ -47,7 +47,9 @@
"CamelSolrFrom": { "index": 5, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The starting index of the response.", "constantName": "org.apache.camel.component.solr.SolrConstants#PARAM_FROM" },
"CamelSolrParams": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "org.apache.solr.common.params.SolrParams", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The solr parameters to use for the request.", "constantName": "org.apache.camel.component.solr.SolrConstants#PARAM_SOLR_PARAMS" },
"CamelSolrDeleteByQuery": { "index": 7, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "false", "description": "For the delete instruction, interpret body as query\/queries instead of id\/ids.", "constantName": "org.apache.camel.component.solr.SolrConstants#PARAM_DELETE_BY_QUERY" },
- "Content-Type": { "index": 8, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content type is used to identify the type when inserting files.", "constantName": "org.apache.camel.component.solr.SolrConstants#PARAM_CONTENT_TYPE" }
+ "Content-Type": { "index": 8, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content type is used to identify the type when inserting files.", "constantName": "org.apache.camel.component.solr.SolrConstants#PARAM_CONTENT_TYPE" },
+ "CamelSolrField.": { "index": 9, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Object", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The prefix of the headers that carry the fields of the document to insert: the header CamelSolrField.id holds the id field.", "constantName": "org.apache.camel.component.solr.SolrConstants#HEADER_FIELD_PREFIX" },
+ "CamelSolrParam.": { "index": 10, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The prefix of the headers that carry extra request parameters: the header CamelSolrParam.commit holds the commit parameter.", "constantName": "org.apache.camel.component.solr.SolrConstants#HEADER_PARAM_PREFIX" }
},
"properties": {
"host": { "index": 0, "kind": "path", "displayName": "Host", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.solr.SolrConfiguration", "configurationField": "configuration", "description": "The solr instance host name (set to 'default' to use the host name defined on component level)" },
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/fop-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/fop-component.adoc
index 4b8140c6875c..d71438459ecb 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/fop-component.adoc
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/fop-component.adoc
@@ -72,6 +72,8 @@ supported:
The complete list of valid output formats can be found in the `MimeConstants.java` source file.
+The endpoint URI takes either the name in lower case (`fop:pdf`) or the output format (`fop:application/pdf`).
+
// component options: START
include::partial$component-configure-options.adoc[]
@@ -137,7 +139,7 @@ Java::
----
from("file:source/data/xml")
.to("xslt:xslt/template.xsl")
- .to("fop:application/pdf")
+ .to("fop:pdf")
.to("file:target/data");
----
@@ -148,7 +150,7 @@ XML::
<route>
<from uri="file:source/data/xml"/>
<to uri="xslt:xslt/template.xsl"/>
- <to uri="fop:application/pdf"/>
+ <to uri="fop:pdf"/>
<to uri="file:target/data"/>
</route>
----
@@ -164,7 +166,7 @@ YAML::
- to:
uri: xslt:xslt/template.xsl
- to:
- uri: fop:application/pdf
+ uri: fop:pdf
- to:
uri: file:target/data
----
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/metrics-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/metrics-component.adoc
index a70cb8c85cbc..e84bfe571880 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/metrics-component.adoc
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/metrics-component.adoc
@@ -543,9 +543,9 @@ Java::
+
[source,java]
----
-// marks simple.meter without value
+// marks the simple.meter meter without value
from("direct:in")
- .to("metrics:simple.meter")
+ .to("metrics:meter:simple.meter")
.to("direct:out");
----
@@ -555,7 +555,7 @@ XML::
----
<route>
<from uri="direct:in"/>
- <to uri="metrics:simple.meter"/>
+ <to uri="metrics:meter:simple.meter"/>
<to uri="direct:out"/>
</route>
----
@@ -569,7 +569,7 @@ YAML::
uri: direct:in
steps:
- to:
- uri: metrics:simple.meter
+ uri: metrics:meter:simple.meter
- to:
uri: direct:out
----
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openai-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openai-component.adoc
index 79bd9c3949fa..039ce1f67b1b 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openai-component.adoc
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openai-component.adoc
@@ -1046,9 +1046,9 @@ Java::
----
from("direct:chat")
.to("openai:chat-completion?model=qwen3"
- + "&additionalResponseHeader.reasoning_content=CamelMyReasoning"
- + "&additionalResponseHeader.custom_field=CamelMyCustomField")
- .log("Custom reasoning: ${header.CamelMyReasoning}");
+ + "&additionalResponseHeader.reasoning_content=MyReasoning"
+ + "&additionalResponseHeader.custom_field=MyCustomField")
+ .log("Custom reasoning: ${header.MyReasoning}");
----
XML::
@@ -1057,8 +1057,8 @@ XML::
----
<route>
<from uri="direct:chat"/>
- <to uri="openai:chat-completion?model=qwen3&additionalResponseHeader.reasoning_content=CamelMyReasoning&additionalResponseHeader.custom_field=CamelMyCustomField"/>
- <log message="Custom reasoning: ${header.CamelMyReasoning}"/>
+ <to uri="openai:chat-completion?model=qwen3&additionalResponseHeader.reasoning_content=MyReasoning&additionalResponseHeader.custom_field=MyCustomField"/>
+ <log message="Custom reasoning: ${header.MyReasoning}"/>
</route>
----
@@ -1074,10 +1074,10 @@ YAML::
uri: openai:chat-completion
parameters:
model: qwen3
- additionalResponseHeader.reasoning_content: CamelMyReasoning
- additionalResponseHeader.custom_field: CamelMyCustomField
+ additionalResponseHeader.reasoning_content: MyReasoning
+ additionalResponseHeader.custom_field: MyCustomField
- log:
- message: "Custom reasoning: ${header.CamelMyReasoning}"
+ message: "Custom reasoning: ${header.MyReasoning}"
----
====
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/xmlSecurity-dataformat.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/xmlSecurity-dataformat.adoc
index eaf5a86d81c7..ced411c85754 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/xmlSecurity-dataformat.adoc
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/xmlSecurity-dataformat.adoc
@@ -208,7 +208,7 @@ Java::
----
from("direct:start")
.marshal().xmlSecurity("//cheese:cheesesites/italy", namespaces, true,
- "recipient", XMLCipher.AES_128_CBC, XMLCipher.RSA_v1dot5, trustStoreParams)
+ "recipient", XMLCipher.AES_128, XMLCipher.RSA_v1dot5, trustStoreParams)
.to("...");
----
@@ -227,8 +227,8 @@ XML::
<marshal>
<xmlSecurity secureTag="//cheese:cheesesites/italy"
secureTagContents="true"
- xmlCipherAlgorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"
- keyCipherAlgorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"
+ xmlCipherAlgorithm="AES_128"
+ keyCipherAlgorithm="RSA_v1dot5"
recipientKeyAlias="recipient"
keyOrTrustStoreParameters="trustStoreParams"/>
</marshal>
@@ -247,8 +247,8 @@ YAML::
xmlSecurity:
secureTag: "//cheese:cheesesites/italy"
secureTagContents: true
- xmlCipherAlgorithm: "http://www.w3.org/2001/04/xmlenc#aes128-cbc"
- keyCipherAlgorithm: "http://www.w3.org/2001/04/xmlenc#rsa-1_5"
+ xmlCipherAlgorithm: AES_128
+ keyCipherAlgorithm: RSA_v1dot5
recipientKeyAlias: recipient
keyOrTrustStoreParameters: "#trustStoreParams"
----
@@ -265,7 +265,7 @@ Java::
----
from("direct:encrypted")
.unmarshal().xmlSecurity("//cheese:cheesesites/italy", namespaces, true,
- "recipient", XMLCipher.AES_128_CBC, XMLCipher.RSA_v1dot5, keyStoreParams)
+ "recipient", XMLCipher.AES_128, XMLCipher.RSA_v1dot5, keyStoreParams)
.to("...");
----
@@ -284,8 +284,8 @@ XML::
<unmarshal>
<xmlSecurity secureTag="//cheese:cheesesites/italy"
secureTagContents="true"
- xmlCipherAlgorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"
- keyCipherAlgorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"
+ xmlCipherAlgorithm="AES_128"
+ keyCipherAlgorithm="RSA_v1dot5"
recipientKeyAlias="recipient"
keyOrTrustStoreParameters="keyStoreParams"
keyPassword="privateKeyPassword" />
@@ -305,8 +305,8 @@ YAML::
xmlSecurity:
secureTag: "//cheese:cheesesites/italy"
secureTagContents: true
- xmlCipherAlgorithm: "http://www.w3.org/2001/04/xmlenc#aes128-cbc"
- keyCipherAlgorithm: "http://www.w3.org/2001/04/xmlenc#rsa-1_5"
+ xmlCipherAlgorithm: AES_128
+ keyCipherAlgorithm: RSA_v1dot5
recipientKeyAlias: recipient
keyOrTrustStoreParameters: "#keyStoreParams"
keyPassword: privateKeyPassword
diff --git a/catalog/camel-catalog/src/test/java/org/apache/camel/catalog/CamelCatalogTest.java b/catalog/camel-catalog/src/test/java/org/apache/camel/catalog/CamelCatalogTest.java
index 4fd62a61fc57..e5a7079f4a03 100644
--- a/catalog/camel-catalog/src/test/java/org/apache/camel/catalog/CamelCatalogTest.java
+++ b/catalog/camel-catalog/src/test/java/org/apache/camel/catalog/CamelCatalogTest.java
@@ -593,6 +593,19 @@ public class CamelCatalogTest {
assertEquals("beer", map.get("myPrefix.baz"));
}
+ @Test
+ public void validateMapOptionEntries() {
+ // userMetadata is a Map option: userMetadata.key=value fills an entry of the map, as property binding does
+ EndpointValidationResult result = catalog.validateEndpointProperties(
+ "spring-ai-chat:assistant?chatModel=#myModel&userMetadata.messageId=abc&userMetadata.priority=high");
+ assertTrue(result.isSuccess(), result.summaryErrorMessage(false));
+
+ // the option before the dot must be a Map
+ result = catalog.validateEndpointProperties("spring-ai-chat:assistant?chatModel=#myModel&chatModel.foo=abc");
+ assertFalse(result.isSuccess());
+ assertTrue(result.getUnknown().contains("chatModel.foo"));
+ }
+
@Test
public void testEndpointPropertiesPlaceholders() throws Exception {
Map<String, String> map = catalog.endpointProperties("timer:foo?period={{howoften}}&repeatCount=5");
diff --git a/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc b/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc
index 79bd9c3949fa..039ce1f67b1b 100644
--- a/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc
+++ b/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc
@@ -1046,9 +1046,9 @@ Java::
----
from("direct:chat")
.to("openai:chat-completion?model=qwen3"
- + "&additionalResponseHeader.reasoning_content=CamelMyReasoning"
- + "&additionalResponseHeader.custom_field=CamelMyCustomField")
- .log("Custom reasoning: ${header.CamelMyReasoning}");
+ + "&additionalResponseHeader.reasoning_content=MyReasoning"
+ + "&additionalResponseHeader.custom_field=MyCustomField")
+ .log("Custom reasoning: ${header.MyReasoning}");
----
XML::
@@ -1057,8 +1057,8 @@ XML::
----
<route>
<from uri="direct:chat"/>
- <to uri="openai:chat-completion?model=qwen3&additionalResponseHeader.reasoning_content=CamelMyReasoning&additionalResponseHeader.custom_field=CamelMyCustomField"/>
- <log message="Custom reasoning: ${header.CamelMyReasoning}"/>
+ <to uri="openai:chat-completion?model=qwen3&additionalResponseHeader.reasoning_content=MyReasoning&additionalResponseHeader.custom_field=MyCustomField"/>
+ <log message="Custom reasoning: ${header.MyReasoning}"/>
</route>
----
@@ -1074,10 +1074,10 @@ YAML::
uri: openai:chat-completion
parameters:
model: qwen3
- additionalResponseHeader.reasoning_content: CamelMyReasoning
- additionalResponseHeader.custom_field: CamelMyCustomField
+ additionalResponseHeader.reasoning_content: MyReasoning
+ additionalResponseHeader.custom_field: MyCustomField
- log:
- message: "Custom reasoning: ${header.CamelMyReasoning}"
+ message: "Custom reasoning: ${header.MyReasoning}"
----
====
diff --git a/components/camel-box/camel-box-component/src/generated/resources/META-INF/org/apache/camel/component/box/box.json b/components/camel-box/camel-box-component/src/generated/resources/META-INF/org/apache/camel/component/box/box.json
index 7327874b58e2..f17f0102eb31 100644
--- a/components/camel-box/camel-box-component/src/generated/resources/META-INF/org/apache/camel/component/box/box.json
+++ b/components/camel-box/camel-box-component/src/generated/resources/META-INF/org/apache/camel/component/box/box.json
@@ -45,6 +45,9 @@
"userName": { "index": 17, "kind": "property", "displayName": "User Name", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.box.BoxConfiguration", "configurationField": "configuration", "description": "Box user name, MUST be provided" },
"userPassword": { "index": 18, "kind": "property", "displayName": "User Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.box.BoxConfiguration", "configurationField": "configuration", "description": "Box user password, MUST be provided if authSecureStorage is not set, or returns null on [...]
},
+ "headers": {
+ "CamelBox.": { "index": 0, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "Object", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The prefix of the headers that carry the parameters of the API method: the header CamelBox.fileName holds the fileName parameter.", "constantName": "org.apache.camel.component.box.internal.BoxConstants#PROPERTY_PREFIX" }
+ },
"properties": {
"apiName": { "index": 0, "kind": "path", "displayName": "Api Name", "group": "common", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.box.internal.BoxApiName", "enum": [ "COLLABORATIONS", "COMMENTS", "EVENT_LOGS", "FILES", "FOLDERS", "GROUPS", "EVENTS", "SEARCH", "TASKS", "USERS" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.box.BoxConfiguration", "configura [...]
"methodName": { "index": 1, "kind": "path", "displayName": "Method Name", "group": "common", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.box.BoxConfiguration", "configurationField": "configuration", "description": "What sub operation to use for the selected operation" },
diff --git a/components/camel-box/camel-box-component/src/main/java/org/apache/camel/component/box/BoxEndpoint.java b/components/camel-box/camel-box-component/src/main/java/org/apache/camel/component/box/BoxEndpoint.java
index c533c6c67efb..66e9231ef9f2 100644
--- a/components/camel-box/camel-box-component/src/main/java/org/apache/camel/component/box/BoxEndpoint.java
+++ b/components/camel-box/camel-box-component/src/main/java/org/apache/camel/component/box/BoxEndpoint.java
@@ -50,7 +50,8 @@ import org.apache.camel.support.component.ApiMethodPropertiesHelper;
*/
@UriEndpoint(firstVersion = "2.14.0", scheme = "box", title = "Box", syntax = "box:apiName/methodName",
apiSyntax = "apiName/methodName",
- consumerPrefix = "consumer", category = { Category.CLOUD, Category.FILE, Category.API }, lenientProperties = true)
+ consumerPrefix = "consumer", category = { Category.CLOUD, Category.FILE, Category.API }, lenientProperties = true,
+ headersClass = BoxConstants.class)
@Metadata(excludeProperties = "startScheduler,initialDelay,delay,timeUnit,useFixedDelay,pollStrategy,runLoggingLevel,sendEmptyMessageWhenIdle"
+ ",greedy,scheduler,schedulerProperties,scheduledExecutorService,backoffMultiplier,backoffIdleThreshold,backoffErrorThreshold,repeatCount,bridgeErrorHandler")
public class BoxEndpoint extends AbstractApiEndpoint<BoxApiName, BoxConfiguration> {
diff --git a/components/camel-box/camel-box-component/src/main/java/org/apache/camel/component/box/internal/BoxConstants.java b/components/camel-box/camel-box-component/src/main/java/org/apache/camel/component/box/internal/BoxConstants.java
index 61223d730729..71653b33851a 100644
--- a/components/camel-box/camel-box-component/src/main/java/org/apache/camel/component/box/internal/BoxConstants.java
+++ b/components/camel-box/camel-box-component/src/main/java/org/apache/camel/component/box/internal/BoxConstants.java
@@ -16,12 +16,17 @@
*/
package org.apache.camel.component.box.internal;
+import org.apache.camel.spi.Metadata;
+
/**
* Constants for Box component.
*/
public interface BoxConstants {
- // suffix for parameters when passed as exchange header properties
+ // prefix for parameters when passed as exchange header properties
+ @Metadata(description = "The prefix of the headers that carry the parameters of the API method: the header"
+ + " CamelBox.fileName holds the fileName parameter.",
+ javaType = "Object")
String PROPERTY_PREFIX = "CamelBox.";
// thread profile name for this component
diff --git a/components/camel-crypto/src/generated/resources/META-INF/org/apache/camel/component/crypto/crypto.json b/components/camel-crypto/src/generated/resources/META-INF/org/apache/camel/component/crypto/crypto.json
index 17f736dc943c..9f14e382ba45 100644
--- a/components/camel-crypto/src/generated/resources/META-INF/org/apache/camel/component/crypto/crypto.json
+++ b/components/camel-crypto/src/generated/resources/META-INF/org/apache/camel/component/crypto/crypto.json
@@ -49,8 +49,9 @@
"headers": {
"CamelSignaturePrivateKey": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.security.PrivateKey", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The PrivateKey that should be used to sign the message", "constantName": "org.apache.camel.component.crypto.DigitalSignatureConstants#SIGNATURE_PRIVATE_KEY" },
"CamelSignaturePublicKeyOrCert": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Certificate or PublicKey", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The Certificate or PublicKey that should be used to verify the signature", "constantName": "org.apache.camel.component.crypto.DigitalSignatureConstants#SIGNATURE_PUBLIC_KEY_OR_CERT" },
- "CamelSignatureKeyStoreAlias": { "index": 2, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The alias used to query the KeyStore for keys and Certificates to be used in signing and verifying exchanges", "constantName": "org.apache.camel.component.crypto.DigitalSignatureConstants#KEYSTORE_ALIAS" },
- "CamelSignatureKeyStorePassword": { "index": 3, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "char[]", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The password used to access an aliased PrivateKey in the KeyStore.", "constantName": "org.apache.camel.component.crypto.DigitalSignatureConstants#KEYSTORE_PASSWORD" }
+ "CamelDigitalSignature": { "index": 2, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "byte[] (Base64 encoded)", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The signature of the message, set by the sign operation and read by the verify operation (the signatureHeaderName option changes the header name)", "constantName": "org.apache.camel.component.crypto.DigitalSignatureConstant [...]
+ "CamelSignatureKeyStoreAlias": { "index": 3, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The alias used to query the KeyStore for keys and Certificates to be used in signing and verifying exchanges", "constantName": "org.apache.camel.component.crypto.DigitalSignatureConstants#KEYSTORE_ALIAS" },
+ "CamelSignatureKeyStorePassword": { "index": 4, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "char[]", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The password used to access an aliased PrivateKey in the KeyStore.", "constantName": "org.apache.camel.component.crypto.DigitalSignatureConstants#KEYSTORE_PASSWORD" }
},
"properties": {
"cryptoOperation": { "index": 0, "kind": "path", "displayName": "Crypto Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.crypto.CryptoOperation", "enum": [ "sign", "verify" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.crypto.DigitalSignatureConfiguration", "configurationField": "configuration", "description": "Set the Crypto o [...]
diff --git a/components/camel-crypto/src/main/java/org/apache/camel/component/crypto/DigitalSignatureConstants.java b/components/camel-crypto/src/main/java/org/apache/camel/component/crypto/DigitalSignatureConstants.java
index d94677d737b4..8dea9fda1d68 100644
--- a/components/camel-crypto/src/main/java/org/apache/camel/component/crypto/DigitalSignatureConstants.java
+++ b/components/camel-crypto/src/main/java/org/apache/camel/component/crypto/DigitalSignatureConstants.java
@@ -27,6 +27,9 @@ public final class DigitalSignatureConstants {
@Metadata(description = "The Certificate or PublicKey that should be used to verify the signature",
javaType = "Certificate or PublicKey")
public static final String SIGNATURE_PUBLIC_KEY_OR_CERT = "CamelSignaturePublicKeyOrCert";
+ @Metadata(description = "The signature of the message, set by the sign operation and read by the verify operation"
+ + " (the signatureHeaderName option changes the header name)",
+ javaType = "byte[] (Base64 encoded)")
public static final String SIGNATURE = "CamelDigitalSignature";
@Metadata(description = "The alias used to query the KeyStore for keys and Certificates to be\n" +
" used in signing and verifying exchanges",
diff --git a/components/camel-fop/src/main/docs/fop-component.adoc b/components/camel-fop/src/main/docs/fop-component.adoc
index 4b8140c6875c..d71438459ecb 100644
--- a/components/camel-fop/src/main/docs/fop-component.adoc
+++ b/components/camel-fop/src/main/docs/fop-component.adoc
@@ -72,6 +72,8 @@ supported:
The complete list of valid output formats can be found in the `MimeConstants.java` source file.
+The endpoint URI takes either the name in lower case (`fop:pdf`) or the output format (`fop:application/pdf`).
+
// component options: START
include::partial$component-configure-options.adoc[]
@@ -137,7 +139,7 @@ Java::
----
from("file:source/data/xml")
.to("xslt:xslt/template.xsl")
- .to("fop:application/pdf")
+ .to("fop:pdf")
.to("file:target/data");
----
@@ -148,7 +150,7 @@ XML::
<route>
<from uri="file:source/data/xml"/>
<to uri="xslt:xslt/template.xsl"/>
- <to uri="fop:application/pdf"/>
+ <to uri="fop:pdf"/>
<to uri="file:target/data"/>
</route>
----
@@ -164,7 +166,7 @@ YAML::
- to:
uri: xslt:xslt/template.xsl
- to:
- uri: fop:application/pdf
+ uri: fop:pdf
- to:
uri: file:target/data
----
diff --git a/components/camel-metrics/src/main/docs/metrics-component.adoc b/components/camel-metrics/src/main/docs/metrics-component.adoc
index a70cb8c85cbc..e84bfe571880 100644
--- a/components/camel-metrics/src/main/docs/metrics-component.adoc
+++ b/components/camel-metrics/src/main/docs/metrics-component.adoc
@@ -543,9 +543,9 @@ Java::
+
[source,java]
----
-// marks simple.meter without value
+// marks the simple.meter meter without value
from("direct:in")
- .to("metrics:simple.meter")
+ .to("metrics:meter:simple.meter")
.to("direct:out");
----
@@ -555,7 +555,7 @@ XML::
----
<route>
<from uri="direct:in"/>
- <to uri="metrics:simple.meter"/>
+ <to uri="metrics:meter:simple.meter"/>
<to uri="direct:out"/>
</route>
----
@@ -569,7 +569,7 @@ YAML::
uri: direct:in
steps:
- to:
- uri: metrics:simple.meter
+ uri: metrics:meter:simple.meter
- to:
uri: direct:out
----
diff --git a/components/camel-solr/src/generated/resources/META-INF/org/apache/camel/component/solr/solr.json b/components/camel-solr/src/generated/resources/META-INF/org/apache/camel/component/solr/solr.json
index 97c66fc517d0..299fa0fe908c 100644
--- a/components/camel-solr/src/generated/resources/META-INF/org/apache/camel/component/solr/solr.json
+++ b/components/camel-solr/src/generated/resources/META-INF/org/apache/camel/component/solr/solr.json
@@ -47,7 +47,9 @@
"CamelSolrFrom": { "index": 5, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The starting index of the response.", "constantName": "org.apache.camel.component.solr.SolrConstants#PARAM_FROM" },
"CamelSolrParams": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "org.apache.solr.common.params.SolrParams", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The solr parameters to use for the request.", "constantName": "org.apache.camel.component.solr.SolrConstants#PARAM_SOLR_PARAMS" },
"CamelSolrDeleteByQuery": { "index": 7, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "false", "description": "For the delete instruction, interpret body as query\/queries instead of id\/ids.", "constantName": "org.apache.camel.component.solr.SolrConstants#PARAM_DELETE_BY_QUERY" },
- "Content-Type": { "index": 8, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content type is used to identify the type when inserting files.", "constantName": "org.apache.camel.component.solr.SolrConstants#PARAM_CONTENT_TYPE" }
+ "Content-Type": { "index": 8, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content type is used to identify the type when inserting files.", "constantName": "org.apache.camel.component.solr.SolrConstants#PARAM_CONTENT_TYPE" },
+ "CamelSolrField.": { "index": 9, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Object", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The prefix of the headers that carry the fields of the document to insert: the header CamelSolrField.id holds the id field.", "constantName": "org.apache.camel.component.solr.SolrConstants#HEADER_FIELD_PREFIX" },
+ "CamelSolrParam.": { "index": 10, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The prefix of the headers that carry extra request parameters: the header CamelSolrParam.commit holds the commit parameter.", "constantName": "org.apache.camel.component.solr.SolrConstants#HEADER_PARAM_PREFIX" }
},
"properties": {
"host": { "index": 0, "kind": "path", "displayName": "Host", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.solr.SolrConfiguration", "configurationField": "configuration", "description": "The solr instance host name (set to 'default' to use the host name defined on component level)" },
diff --git a/components/camel-solr/src/main/java/org/apache/camel/component/solr/SolrConstants.java b/components/camel-solr/src/main/java/org/apache/camel/component/solr/SolrConstants.java
index d5bfc3073005..1a786de7f013 100644
--- a/components/camel-solr/src/main/java/org/apache/camel/component/solr/SolrConstants.java
+++ b/components/camel-solr/src/main/java/org/apache/camel/component/solr/SolrConstants.java
@@ -42,7 +42,13 @@ public interface SolrConstants {
@Metadata(description = "The content type is used to identify the type when inserting files.", javaType = "String")
String PARAM_CONTENT_TYPE = Exchange.CONTENT_TYPE;
+ @Metadata(description = "The prefix of the headers that carry the fields of the document to insert: the header"
+ + " CamelSolrField.id holds the id field.",
+ javaType = "Object")
String HEADER_FIELD_PREFIX = "CamelSolrField.";
+ @Metadata(description = "The prefix of the headers that carry extra request parameters: the header"
+ + " CamelSolrParam.commit holds the commit parameter.",
+ javaType = "String")
String HEADER_PARAM_PREFIX = "CamelSolrParam.";
String PROPERTY_ACTION_CONTEXT = "SolrActionContext";
diff --git a/components/camel-xmlsecurity/src/main/docs/xmlSecurity-dataformat.adoc b/components/camel-xmlsecurity/src/main/docs/xmlSecurity-dataformat.adoc
index eaf5a86d81c7..ced411c85754 100644
--- a/components/camel-xmlsecurity/src/main/docs/xmlSecurity-dataformat.adoc
+++ b/components/camel-xmlsecurity/src/main/docs/xmlSecurity-dataformat.adoc
@@ -208,7 +208,7 @@ Java::
----
from("direct:start")
.marshal().xmlSecurity("//cheese:cheesesites/italy", namespaces, true,
- "recipient", XMLCipher.AES_128_CBC, XMLCipher.RSA_v1dot5, trustStoreParams)
+ "recipient", XMLCipher.AES_128, XMLCipher.RSA_v1dot5, trustStoreParams)
.to("...");
----
@@ -227,8 +227,8 @@ XML::
<marshal>
<xmlSecurity secureTag="//cheese:cheesesites/italy"
secureTagContents="true"
- xmlCipherAlgorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"
- keyCipherAlgorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"
+ xmlCipherAlgorithm="AES_128"
+ keyCipherAlgorithm="RSA_v1dot5"
recipientKeyAlias="recipient"
keyOrTrustStoreParameters="trustStoreParams"/>
</marshal>
@@ -247,8 +247,8 @@ YAML::
xmlSecurity:
secureTag: "//cheese:cheesesites/italy"
secureTagContents: true
- xmlCipherAlgorithm: "http://www.w3.org/2001/04/xmlenc#aes128-cbc"
- keyCipherAlgorithm: "http://www.w3.org/2001/04/xmlenc#rsa-1_5"
+ xmlCipherAlgorithm: AES_128
+ keyCipherAlgorithm: RSA_v1dot5
recipientKeyAlias: recipient
keyOrTrustStoreParameters: "#trustStoreParams"
----
@@ -265,7 +265,7 @@ Java::
----
from("direct:encrypted")
.unmarshal().xmlSecurity("//cheese:cheesesites/italy", namespaces, true,
- "recipient", XMLCipher.AES_128_CBC, XMLCipher.RSA_v1dot5, keyStoreParams)
+ "recipient", XMLCipher.AES_128, XMLCipher.RSA_v1dot5, keyStoreParams)
.to("...");
----
@@ -284,8 +284,8 @@ XML::
<unmarshal>
<xmlSecurity secureTag="//cheese:cheesesites/italy"
secureTagContents="true"
- xmlCipherAlgorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"
- keyCipherAlgorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"
+ xmlCipherAlgorithm="AES_128"
+ keyCipherAlgorithm="RSA_v1dot5"
recipientKeyAlias="recipient"
keyOrTrustStoreParameters="keyStoreParams"
keyPassword="privateKeyPassword" />
@@ -305,8 +305,8 @@ YAML::
xmlSecurity:
secureTag: "//cheese:cheesesites/italy"
secureTagContents: true
- xmlCipherAlgorithm: "http://www.w3.org/2001/04/xmlenc#aes128-cbc"
- keyCipherAlgorithm: "http://www.w3.org/2001/04/xmlenc#rsa-1_5"
+ xmlCipherAlgorithm: AES_128
+ keyCipherAlgorithm: RSA_v1dot5
recipientKeyAlias: recipient
keyOrTrustStoreParameters: "#keyStoreParams"
keyPassword: privateKeyPassword
diff --git a/core/camel-core-catalog/src/main/java/org/apache/camel/catalog/impl/AbstractCamelCatalog.java b/core/camel-core-catalog/src/main/java/org/apache/camel/catalog/impl/AbstractCamelCatalog.java
index 2f44c34ceb89..957c6e06c0a9 100644
--- a/core/camel-core-catalog/src/main/java/org/apache/camel/catalog/impl/AbstractCamelCatalog.java
+++ b/core/camel-core-catalog/src/main/java/org/apache/camel/catalog/impl/AbstractCamelCatalog.java
@@ -305,7 +305,7 @@ public abstract class AbstractCamelCatalog {
boolean lookup = value.startsWith("#") && value.length() > 1;
// we cannot evaluate multi values as strict as the others, as we don't know their expected types
boolean multiValue = prefix != null && originalName.startsWith(prefix)
- && row.isMultiValue();
+ && row.isMultiValue() || isMapEntry(row, originalName);
// default value
Object defaultValue = row.getDefaultValue();
@@ -1844,9 +1844,25 @@ public abstract class AbstractCamelCatalog {
return row.getName();
}
}
+ int dot = name.indexOf('.');
+ if (dot > 0) {
+ BaseOptionModel row = rows.get(name.substring(0, dot));
+ if (row != null && isMapEntry(row, name)) {
+ return row.getName();
+ }
+ }
return null;
}
+ /**
+ * Whether the name sets an entry of a Map option (userMetadata.messageId=x fills the userMetadata map), as property
+ * binding does.
+ */
+ private static boolean isMapEntry(BaseOptionModel row, String name) {
+ String javaType = row.getJavaType();
+ return javaType != null && javaType.startsWith("java.util.Map") && name.startsWith(row.getName() + ".");
+ }
+
/**
* Converts the string from dash format into camel case (hello-great-world -> helloGreatWorld)
*
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/EndpointChecks.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/EndpointChecks.java
index f992dd13de86..3d93405485e4 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/EndpointChecks.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/EndpointChecks.java
@@ -32,6 +32,7 @@ import static org.apache.camel.dsl.jbang.core.commands.ai.YamlLines.YAML_URI_PAT
import static org.apache.camel.dsl.jbang.core.commands.ai.YamlLines.countLeadingSpaces;
import static org.apache.camel.dsl.jbang.core.commands.ai.YamlLines.extractEipFromLine;
import static org.apache.camel.dsl.jbang.core.commands.ai.YamlLines.findParentEip;
+import static org.apache.camel.dsl.jbang.core.commands.ai.YamlLines.stripComment;
import static org.apache.camel.dsl.jbang.core.commands.ai.YamlLines.unquote;
/**
@@ -148,6 +149,9 @@ final class EndpointChecks {
String nextTrimmed = next.trim();
if (nextIndent == lineIndent && nextTrimmed.startsWith("parameters:")) {
int paramBlockIndent = nextIndent;
+ int blockScalarIndent = -1;
+ String mapKey = null;
+ int mapIndent = -1;
for (int k = j + 1; k < lines.length; k++) {
String paramLine = lines[k];
if (paramLine.isBlank()) {
@@ -157,11 +161,33 @@ final class EndpointChecks {
if (paramIndent <= paramBlockIndent) {
break;
}
+ if (blockScalarIndent >= 0 && paramIndent > blockScalarIndent) {
+ // the lines of a block scalar (argSchema: | followed by JSON) are its value, not options
+ continue;
+ }
+ blockScalarIndent = -1;
+ if (mapKey != null && paramIndent <= mapIndent) {
+ mapKey = null;
+ }
String paramTrimmed = paramLine.trim();
int colonPos = paramTrimmed.indexOf(':');
if (colonPos > 0) {
- String key = paramTrimmed.substring(0, colonPos).trim();
- String val = unquote(paramTrimmed.substring(colonPos + 1).trim());
+ String key = unquote(paramTrimmed.substring(0, colonPos).trim());
+ String val = unquote(stripComment(paramTrimmed.substring(colonPos + 1).trim()));
+ if (mapKey != null) {
+ // headers: with foo: bar under it is the entry foo of the headers map option
+ key = mapKey + "." + key;
+ } else if (val.isEmpty() && k + 1 < lines.length
+ && countLeadingSpaces(lines[k + 1]) > paramIndent) {
+ mapKey = key;
+ mapIndent = paramIndent;
+ continue;
+ }
+ if (YamlLines.isBlockScalarIndicator(val)) {
+ // the option is checked by name; its value is the block that follows
+ blockScalarIndent = paramIndent;
+ val = "";
+ }
char sep = hasParams ? '&' : '?';
uriBuilder.append(sep).append(key).append('=').append(val);
hasParams = true;
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/HeaderChecks.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/HeaderChecks.java
index 15d785f8aebf..23fd298f82d2 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/HeaderChecks.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/HeaderChecks.java
@@ -40,7 +40,7 @@ final class HeaderChecks {
}
static final Pattern CAMEL_HEADER_REF_PATTERN = Pattern.compile(
- "(?:\\$\\{headers?\\.|headers\\.|headers\\[['\"]|header\\(['\"]|name:\\s*['\"]?)(Camel[A-Z][A-Za-z0-9]*)");
+ "(?:\\$\\{headers?\\.|headers\\.|headers\\[['\"]|header\\(['\"]|(name):\\s*['\"]?)(Camel[A-Z][A-Za-z0-9]*(?:\\.[A-Za-z0-9_]+)*)");
static final Pattern SCHEME_IN_URI_PATTERN = Pattern.compile("uri:\\s*\"?([a-zA-Z][a-zA-Z0-9+.-]*):");
@@ -100,8 +100,12 @@ final class HeaderChecks {
for (int i = 0; i < lines.length; i++) {
Matcher m = CAMEL_HEADER_REF_PATTERN.matcher(lines[i]);
while (m.find()) {
- String name = m.group(1);
- if (common.contains(name) || reported.contains(name)) {
+ if (m.group(1) != null && !isHeaderName(lines, i)) {
+ // name: of a setProperty, setVariable, bean...: not a header
+ continue;
+ }
+ String name = m.group(2);
+ if (common.contains(name) || reported.contains(name) || isKnown(name, known)) {
continue;
}
String propertyOwner = null;
@@ -117,7 +121,7 @@ final class HeaderChecks {
+ ", not a header (the header would be null): write ${exchangeProperty." + name + "}");
continue;
}
- if (known.contains(name) || !reported.add(name)) {
+ if (!reported.add(name)) {
continue;
}
String best = closestName(name, new ArrayList<>(known));
@@ -142,4 +146,46 @@ final class HeaderChecks {
return msgs;
}
+ /**
+ * A header the metadata lists: as is (CamelBox.fileName), by a prefix the component documents with a trailing dot
+ * (CamelSolrField. for CamelSolrField.id), or as the head of an OGNL path (CamelFileName.length()).
+ */
+ static boolean isKnown(String name, Set<String> known) {
+ String candidate = name;
+ while (true) {
+ if (known.contains(candidate)) {
+ return true;
+ }
+ for (String k : known) {
+ if (k.endsWith(".") && candidate.startsWith(k)) {
+ return true;
+ }
+ }
+ int dot = candidate.lastIndexOf('.');
+ if (dot < 0) {
+ return false;
+ }
+ candidate = candidate.substring(0, dot);
+ }
+ }
+
+ /** Whether the name: on this line belongs to a setHeader or removeHeader step, not a setProperty, a bean... */
+ static boolean isHeaderName(String[] lines, int index) {
+ int indent = YamlLines.countLeadingSpaces(lines[index]);
+ for (int j = index - 1; j >= 0; j--) {
+ String line = lines[j];
+ if (line.isBlank() || line.trim().startsWith("#")) {
+ continue;
+ }
+ if (YamlLines.countLeadingSpaces(line) < indent) {
+ String step = line.trim();
+ if (step.startsWith("- ")) {
+ step = step.substring(2).trim();
+ }
+ return step.startsWith("setHeader:") || step.startsWith("removeHeader:");
+ }
+ }
+ return false;
+ }
+
}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/YamlLines.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/YamlLines.java
index 0220b2ee7b86..0f245b07c3d7 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/YamlLines.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/YamlLines.java
@@ -130,6 +130,33 @@ final class YamlLines {
return unquote(trimmed.substring(prefix.length()).trim());
}
+ /**
+ * A scalar without the YAML comment that may follow it: {@code 30000 # the default is 300000} is 30000, and a
+ * quoted scalar keeps a # inside its quotes.
+ */
+ static String stripComment(String val) {
+ if (val.isEmpty()) {
+ return val;
+ }
+ char quote = val.charAt(0);
+ if (quote == '"' || quote == '\'') {
+ int close = val.indexOf(quote, 1);
+ return close > 0 ? val.substring(0, close + 1) : val;
+ }
+ for (int i = 1; i < val.length(); i++) {
+ if (val.charAt(i) == '#' && Character.isWhitespace(val.charAt(i - 1))) {
+ return val.substring(0, i).stripTrailing();
+ }
+ }
+ return val;
+ }
+
+ /** Whether a value is the indicator of a block scalar: |, >, and the variants with a chomping or indent hint. */
+ static boolean isBlockScalarIndicator(String val) {
+ return !val.isEmpty() && (val.charAt(0) == '|' || val.charAt(0) == '>') && val.length() <= 3
+ && val.substring(1).chars().allMatch(c -> c == '-' || c == '+' || Character.isDigit(c));
+ }
+
static String unquote(String val) {
if (val.length() >= 2 && val.startsWith("\"") && val.endsWith("\"")) {
return val.substring(1, val.length() - 1);
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocExamplesTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocExamplesTest.java
new file mode 100644
index 000000000000..609a2682d77b
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocExamplesTest.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.ai;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.camel.catalog.CamelCatalog;
+import org.apache.camel.catalog.DefaultCamelCatalog;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * CAMEL-24710: every YAML route example of the documentation passes the checks {@code camel validate yaml} and the
+ * authoring tools run after the schema: endpoint URIs and their options, Simple expressions and Camel headers against
+ * the catalog. The schema itself is guarded by EipDocExamplesTest in camel-yaml-dsl-validator.
+ */
+class CatalogDocExamplesTest {
+
+ private static final Pattern YAML_BLOCK = Pattern.compile("\\[source,yaml\\]\\n-{4}\\n(.*?)\\n-{4}", Pattern.DOTALL);
+
+ /**
+ * Pages whose examples are right for the runtime but fail the catalog because the component metadata disagrees with
+ * the component (CAMEL-24748 and the notes on it); checked again when the metadata is fixed.
+ */
+ private static final Map<String, String> PAGES_SKIPPED = Map.ofEntries(
+ Map.entry("azure-storage-datalake-component", "CAMEL-24748: the operation enum lists 2 of 15 values"),
+ Map.entry("google-secret-manager-component", "CAMEL-24748: the operation enum lists 1 of 4 values"),
+ Map.entry("google-storage-component", "CAMEL-24748: objectName is labelled producer, the consumer uses it"),
+ Map.entry("web3j-component", "CAMEL-24748: operation is labelled producer, the consumer uses it"),
+ Map.entry("grpc-component", "CAMEL-24748: forwardOnError and forwardOnCompleted are labelled consumer"),
+ Map.entry("netty-http-component", "CAMEL-24748: bootstrapConfiguration is not in the metadata"),
+ Map.entry("jt400-component", "CAMEL-24748: the output options have no @UriParam; the syntax has literal"
+ + " segments (QSYS.LIB, objectPath.type) the catalog reads as options"),
+ Map.entry("olingo2-component", "CAMEL-24748: the syntax is apiName/methodName, the runtime reads"
+ + " methodName/resourcePath with the api name implicit"),
+ Map.entry("olingo4-component", "CAMEL-24748: the syntax is apiName/methodName, the runtime reads"
+ + " methodName/resourcePath with the api name implicit"),
+ Map.entry("xmpp-component", "CAMEL-24748: port is required in the metadata, the runtime defaults it and"
+ + " takes a user@ prefix on the host"),
+ Map.entry("huggingface-component", "CAMEL-24748: task is an enum in the metadata, the runtime takes any"
+ + " name with predictorBean"),
+ Map.entry("salesforce-rest-api", "CAMEL-24748: approval.* has no @UriParam"));
+
+ /** Examples that show what only the runtime knows, by page and a text found in the example. */
+ private static final Map<String, String> EXAMPLES_SKIPPED = Map.of(
+ "simple-advanced", "${foo", // a custom simple function registered at runtime
+ "yaml-dsl", "myStep:"); // a step contributed by a custom YAML deserializer
+
+ private static CamelCatalog catalog;
+
+ private record DocExamples(int examples, List<String> failures) {
+ }
+
+ @BeforeAll
+ static void setup() {
+ catalog = new DefaultCamelCatalog();
+ }
+
+ @Test
+ void everyYamlExampleOfTheDocumentationPassesTheCatalogChecks() {
+ List<String> pages = new ArrayList<>();
+ for (String name : catalog.findDocNames()) {
+ if (!PAGES_SKIPPED.containsKey(name)) {
+ pages.add(name);
+ }
+ }
+ DocExamples result = validate(pages);
+ assertThat(result.examples()).as("YAML route examples found in the documentation").isGreaterThan(1700);
+ assertThat(result.failures()).as("Documentation examples that fail the catalog checks").isEmpty();
+ }
+
+ @Test
+ void theSkippedPagesStillExist() {
+ for (String page : PAGES_SKIPPED.keySet()) {
+ assertThat(catalog.asciiDoc(page)).as("the skipped page %s (drop the entry when it is gone)", page).isNotNull();
+ }
+ }
+
+ private static DocExamples validate(List<String> pages) {
+ int examples = 0;
+ List<String> failures = new ArrayList<>();
+ for (String page : pages) {
+ String doc = catalog.asciiDoc(page);
+ if (doc == null) {
+ continue;
+ }
+ int n = 0;
+ Matcher m = YAML_BLOCK.matcher(doc);
+ while (m.find()) {
+ String yaml = m.group(1).stripTrailing() + "\n";
+ if (!yaml.stripLeading().startsWith("- ")) {
+ // a fragment (an option list, a snippet), not a route file
+ continue;
+ }
+ n++;
+ String skipped = EXAMPLES_SKIPPED.get(page);
+ if (skipped != null && yaml.contains(skipped)) {
+ continue;
+ }
+ examples++;
+ for (String msg : SourceValidator.validateCamelYaml(yaml, catalog)) {
+ failures.add(page + " example " + n + ": " + msg);
+ }
+ }
+ }
+ return new DocExamples(examples, failures);
+ }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointTest.java
index 911d3a3c7fa3..a9694ee8908b 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointTest.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointTest.java
@@ -398,6 +398,64 @@ class SourceValidatorEndpointTest {
.contains("${exchangeProperty.CamelTimerCounter}");
}
+ @Test
+ void aPropertyOrBeanNameIsNotAHeader() {
+ // CAMEL-24710: name: counts as a header only under setHeader/removeHeader; a dotted name is looked up as is,
+ // by the prefix the component documents (CamelSolrField.), and as the head of an OGNL path
+ List<String> msgs = SourceValidator.validateKnownHeaders("""
+ - beans:
+ - name: CamelMyBean
+ type: "#class:com.example.MyBean"
+ - from:
+ uri: "file:in?noop=true"
+ steps:
+ - setProperty:
+ name: CamelAwsSqsDeleteFiltered
+ constant: "true"
+ - setHeader:
+ name: CamelSolrField.id
+ simple: "${body}"
+ - log: "${header.CamelFileName.length()} ${header.CamelFileNam.length()}"
+ - to:
+ uri: solr:localhost:8983/mycollection
+ """, catalog);
+ assertThat(msgs).hasSize(1);
+ assertThat(msgs.get(0)).contains("header CamelFileNam.length is not set by").contains("did you mean CamelFileLength");
+ }
+
+ @Test
+ void aCommentABlockScalarAndANestedMapInTheParametersAreRead() {
+ // CAMEL-24710: a comment after a value is not the value; the body of a block scalar is not options; the keys
+ // under a Map option are its entries
+ List<String> msgs = SourceValidator.validateYamlEndpoints("""
+ - from:
+ uri: ai-tool:createOrder
+ parameters:
+ description: "Create an order"
+ argSchema: |
+ {
+ "type": "object",
+ "properties": { "id": { "type": "string" } }
+ }
+ steps:
+ - to:
+ uri: log:out
+ parameters:
+ groupSize: 30000 # the default is none
+ showAll: true
+ - to:
+ uri: once
+ parameters:
+ name: hello
+ headers:
+ foo: foolish
+ bar: 456
+ unknownOne: x
+ """, catalog);
+ assertThat(msgs).hasSize(1);
+ assertThat(msgs.get(0)).contains("Unknown option 'unknownOne'");
+ }
+
@Test
void aProducerOnlyComponentInFromIsNamed() {
List<String> msgs = SourceValidator.validateYamlEndpoints("""
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/EipDocExamplesTest.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/EipDocExamplesTest.java
index 505ec1386e95..248ea4363421 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/EipDocExamplesTest.java
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/EipDocExamplesTest.java
@@ -19,7 +19,6 @@ package org.apache.camel.dsl.yaml.validator;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -48,13 +47,6 @@ class EipDocExamplesTest {
"point-to-point-channel", "publish-subscribe-channel", "return-address", "scatter-gather",
"selective-consumer", "service-activator", "transactional-client");
- /**
- * The xmlSecurity examples write the cipher algorithms as the URIs the data format hands to XMLCipher, while the
- * model's enums list the XMLCipher constant names, which XMLCipher rejects (CAMEL-24716). The page is checked again
- * once the model accepts what the data format does.
- */
- private static final Set<String> DATA_FORMAT_PAGES_SKIPPED = Set.of("xmlSecurity-dataformat");
-
private static final Pattern YAML_BLOCK = Pattern.compile("\\[source,yaml\\]\\n-{4}\\n(.*?)\\n-{4}", Pattern.DOTALL);
/**
@@ -100,8 +92,7 @@ class EipDocExamplesTest {
@Test
void everyYamlExampleOfTheDataFormatDocumentationValidates() throws Exception {
- List<String> pages
- = docNames(name -> name.endsWith("-dataformat") && !DATA_FORMAT_PAGES_SKIPPED.contains(name));
+ List<String> pages = docNames(name -> name.endsWith("-dataformat"));
DocExamples result = validate(pages);