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/10/02 09:08:13 UTC
(camel) 01/01: CAMEL-25261: camel-jbang - bundle the examples of the ladder that run offline (resync of the example catalog)
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch chore/CAMEL-25261-sync-bundled-examples
in repository https://gitbox.apache.org/repos/asf/camel.git
commit 42bacbd46d6d2177b23d639fd54ffcfb21198efa
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Fri Oct 2 11:07:59 2026 +0200
CAMEL-25261: camel-jbang - bundle the examples of the ladder that run offline (resync of the example catalog)
Co-Authored-By: Claude Opus 5.5 (1M context) <no...@anthropic.com>
Signed-off-by: Claus Ibsen <cl...@gmail.com>
---
.../examples/camel-jbang-example-catalog.json | 56 +++---
.../examples/connect/file-processing/README.md | 78 ++++++++
.../connect/file-processing/application.properties | 16 ++
.../file-processing/file-processing.camel.yaml | 56 ++++++
.../file-processing/samples/invoice-2001.json | 1 +
.../file-processing/samples/invoice-2002.json | 1 +
.../file-processing/samples/invoice-2003.json | 1 +
.../file-processing/samples/invoice-2004.json | 1 +
.../connect/file-processing/samples/note-2005.txt | 1 +
.../examples/connect/http-client/README.md | 77 ++++++++
.../connect/http-client/application.properties | 18 ++
.../connect/http-client/http-client.camel.yaml | 117 ++++++++++++
.../connect/http-client/orders/order-1001.json | 2 +
.../connect/http-client/orders/order-1002.json | 2 +
.../connect/http-client/orders/order-1003.json | 2 +
.../examples/connect/http-client/stock.json | 5 +
.../resources/examples/connect/stock-api/README.md | 80 ++++++++
.../connect/stock-api/application.properties | 18 ++
.../connect/stock-api/stock-api.camel.yaml | 73 ++++++++
.../examples/connect/stock-api/stock.json | 5 +
.../examples/contracts/openapi-client/README.md | 86 +++++++++
.../openapi-client/application.properties | 18 ++
.../openapi-client/openapi-client.camel.yaml | 64 +++++++
.../openapi-client/orders/order-1001.json | 2 +
.../openapi-client/orders/order-1002.json | 2 +
.../openapi-client/orders/order-1003.json | 2 +
.../contracts/openapi-client/stock-api.json | 201 +++++++++++++++++++++
.../examples/contracts/openapi-server/README.md | 91 ++++++++++
.../openapi-server/application.properties | 18 ++
.../openapi-server/openapi-server.camel.yaml | 162 +++++++++++++++++
.../contracts/openapi-server/stock-api.json | 201 +++++++++++++++++++++
.../examples/contracts/openapi-server/stock.json | 5 +
.../examples/fail-well/circuit-breaker/README.md | 3 +-
.../examples/fail-well/error-handling/README.md | 82 +++++++++
.../error-handling/application.properties | 16 ++
.../error-handling/error-handling.camel.yaml | 84 +++++++++
.../error-handling/orders/order-1001.json | 2 +
.../error-handling/orders/order-1002.json | 2 +
.../error-handling/orders/order-1003.json | 2 +
.../examples/quick-start/rest-api/README.md | 75 +++++++-
.../quick-start/rest-api/application.properties | 17 ++
.../examples/quick-start/routes/Greeter.java | 40 ++--
.../examples/quick-start/routes/README.md | 85 ++++-----
.../quick-start/routes/application.properties | 23 ++-
.../examples/quick-start/routes/beans.yaml | 8 +-
.../examples/quick-start/routes/routes.camel.yaml | 12 +-
.../examples/quick-start/splitter/README.md | 74 +++++---
.../examples/quick-start/timer-log/README.md | 70 ++++++-
.../quick-start/timer-log/application.properties | 17 ++
.../resources/examples/route/aggregator/README.md | 73 ++++++++
.../route/aggregator/aggregator.camel.yaml | 52 ++++++
.../route/aggregator/application.properties | 16 ++
.../route/aggregator/orders/order-1001.json | 2 +
.../route/aggregator/orders/order-1002.json | 2 +
.../route/aggregator/orders/order-1003.json | 2 +
.../examples/route/content-based-router/README.md | 67 +++++++
.../content-based-router/application.properties | 16 ++
.../content-based-router.camel.yaml | 33 ++++
.../content-based-router/orders/order-1001.json | 2 +
.../content-based-router/orders/order-1002.json | 2 +
.../content-based-router/orders/order-1003.json | 2 +
.../examples/route/filter-and-multicast/README.md | 69 +++++++
.../filter-and-multicast/application.properties | 16 ++
.../filter-and-multicast.camel.yaml | 50 +++++
.../filter-and-multicast/orders/order-1001.json | 2 +
.../filter-and-multicast/orders/order-1002.json | 2 +
.../filter-and-multicast/orders/order-1003.json | 2 +
.../resources/examples/route/order-lines/README.md | 72 ++++++++
.../route/order-lines/application.properties | 16 ++
.../route/order-lines/order-lines.camel.yaml | 30 +++
.../route/order-lines/orders/order-1001.json | 2 +
.../route/order-lines/orders/order-1002.json | 2 +
.../route/order-lines/orders/order-1003.json | 2 +
.../examples/run/nightly-report/README.md | 64 +++++++
.../run/nightly-report/application.properties | 24 +++
.../run/nightly-report/nightly-report.camel.yaml | 16 ++
.../examples/run/order-generator/OrderNumber.java | 20 ++
.../examples/run/order-generator/README.md | 74 ++++++++
.../run/order-generator/application.properties | 21 +++
.../examples/run/order-generator/beans.yaml | 5 +
.../run/order-generator/order-generator.camel.yaml | 24 +++
.../examples/run/properties-and-profiles/README.md | 74 ++++++++
.../application-prod.properties | 21 +++
.../properties-and-profiles/application.properties | 21 +++
.../properties-and-profiles.camel.yaml | 16 ++
.../examples/transform/csv-to-json/README.md | 69 +++++++
.../transform/csv-to-json/application.properties | 16 ++
.../transform/csv-to-json/csv-to-json.camel.yaml | 35 ++++
.../transform/csv-to-json/inbox/invoices.csv | 4 +
.../examples/transform/data-mapping/README.md | 65 +++++++
.../transform/data-mapping/application.properties | 16 ++
.../transform/data-mapping/data-mapping.camel.yaml | 28 +++
.../examples/transform/data-mapping/order.json | 3 +
.../transform/data-mapping/shipment-mapping.groovy | 12 ++
.../resources/examples/transform/groovy/README.md | 67 +++++++
.../transform/groovy/application.properties | 19 ++
.../examples/transform/groovy/groovy.camel.yaml | 33 ++++
.../examples/transform/groovy/order-bad-email.json | 2 +
.../resources/examples/transform/groovy/order.json | 2 +
.../examples/transform/json-transform/README.md | 68 +++++++
.../json-transform/application.properties | 16 ++
.../json-transform/json-transform.camel.yaml | 33 ++++
.../examples/transform/json-transform/order.json | 3 +
.../examples/transform/xml-to-json/README.md | 67 +++++++
.../transform/xml-to-json/application.properties | 16 ++
.../transform/xml-to-json/inbox/supplier-order.xml | 7 +
.../transform/xml-to-json/xml-to-json.camel.yaml | 21 +++
.../resources/examples/transform/xslt/README.md | 66 +++++++
.../examples/transform/xslt/application.properties | 16 ++
.../transform/xslt/inbox/supplier-order.xml | 7 +
.../examples/transform/xslt/packing-slip.xsl | 15 ++
.../examples/transform/xslt/xslt.camel.yaml | 17 ++
.../src/test/java/camel/example/Greeter.java | 31 ++--
.../src/test/java/camel/example/OrderNumber.java | 40 ++++
114 files changed, 3637 insertions(+), 145 deletions(-)
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-jbang-example-catalog.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-jbang-example-catalog.json
index 0434afa3861f..dbabdbe6816a 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-jbang-example-catalog.json
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-jbang-example-catalog.json
@@ -2,7 +2,7 @@
{
"name": "ai/docling-langchain4j-rag",
"title": "Document Analysis with Docling and LangChain4j RAG",
- "description": "Documents dropped in a directory are converted by a running Docling service, chunked and summarised by a local Ollama model through langchain4j-chat, and written to an output directory; an HTTP endpoint answers questions against the converted documents.",
+ "description": "Documents dropped in a directory are converted to Markdown by a Docling service, analysed by a local Ollama model through langchain4j-chat, and written as a report to an output directory; an HTTP endpoint answers questions against the latest document.",
"level": "ai",
"teaches": {
"components": [
@@ -42,6 +42,8 @@
"docling",
"ollama"
],
+ "needs": "a local model",
+ "ciSkip": true,
"order": 4
},
{
@@ -88,6 +90,7 @@
"infraServices": [
"ollama"
],
+ "needs": "a local model",
"ciSkip": true,
"order": 1
},
@@ -164,6 +167,8 @@
"pii-redaction.camel.yaml",
"pii.schema.json"
],
+ "needs": "an OpenAI-compatible API",
+ "ciSkip": true,
"order": 3
},
{
@@ -504,7 +509,7 @@
"inbox",
"archive"
],
- "bundled": false,
+ "bundled": true,
"requiresDocker": false,
"hasCitrusTests": true,
"files": [
@@ -555,7 +560,7 @@
"rest",
"client"
],
- "bundled": false,
+ "bundled": true,
"requiresDocker": false,
"hasCitrusTests": true,
"files": [
@@ -601,7 +606,7 @@
"http",
"platform-http"
],
- "bundled": false,
+ "bundled": true,
"requiresDocker": false,
"hasCitrusTests": true,
"files": [
@@ -686,7 +691,7 @@
"client",
"contract"
],
- "bundled": false,
+ "bundled": true,
"requiresDocker": false,
"hasCitrusTests": true,
"files": [
@@ -736,7 +741,7 @@
"rest",
"contract"
],
- "bundled": false,
+ "bundled": true,
"requiresDocker": false,
"hasCitrusTests": true,
"files": [
@@ -818,7 +823,7 @@
"retry",
"dead-letter"
],
- "bundled": false,
+ "bundled": true,
"requiresDocker": false,
"hasCitrusTests": true,
"files": [
@@ -827,8 +832,7 @@
"error-handling.camel.yaml",
"orders/order-1001.json",
"orders/order-1002.json",
- "orders/order-1003.json",
- "parked/order-1003.json"
+ "orders/order-1003.json"
],
"order": 1
},
@@ -856,7 +860,7 @@
],
"bundled": true,
"requiresDocker": false,
- "hasCitrusTests": false,
+ "hasCitrusTests": true,
"files": [
"README.md",
"application.properties",
@@ -885,7 +889,7 @@
],
"bundled": true,
"requiresDocker": false,
- "hasCitrusTests": false,
+ "hasCitrusTests": true,
"files": [
"Greeter.java",
"README.md",
@@ -920,7 +924,7 @@
],
"bundled": true,
"requiresDocker": false,
- "hasCitrusTests": false,
+ "hasCitrusTests": true,
"files": [
"README.md",
"splitter.camel.yaml"
@@ -948,7 +952,7 @@
],
"bundled": true,
"requiresDocker": false,
- "hasCitrusTests": false,
+ "hasCitrusTests": true,
"files": [
"README.md",
"application.properties",
@@ -987,7 +991,7 @@
"aggregator",
"batch"
],
- "bundled": false,
+ "bundled": true,
"requiresDocker": false,
"hasCitrusTests": true,
"files": [
@@ -1027,7 +1031,7 @@
"choice",
"routing"
],
- "bundled": false,
+ "bundled": true,
"requiresDocker": false,
"hasCitrusTests": true,
"files": [
@@ -1069,7 +1073,7 @@
"filter",
"multicast"
],
- "bundled": false,
+ "bundled": true,
"requiresDocker": false,
"hasCitrusTests": true,
"files": [
@@ -1110,7 +1114,7 @@
"splitter",
"split"
],
- "bundled": false,
+ "bundled": true,
"requiresDocker": false,
"hasCitrusTests": true,
"files": [
@@ -1145,7 +1149,7 @@
"cron",
"schedule"
],
- "bundled": false,
+ "bundled": true,
"requiresDocker": false,
"hasCitrusTests": true,
"files": [
@@ -1180,7 +1184,7 @@
"bean",
"json"
],
- "bundled": false,
+ "bundled": true,
"requiresDocker": false,
"hasCitrusTests": true,
"files": [
@@ -1215,7 +1219,7 @@
"profiles",
"configuration"
],
- "bundled": false,
+ "bundled": true,
"requiresDocker": false,
"hasCitrusTests": true,
"files": [
@@ -1407,7 +1411,7 @@
"file",
"transformation"
],
- "bundled": false,
+ "bundled": true,
"requiresDocker": false,
"hasCitrusTests": true,
"files": [
@@ -1448,7 +1452,7 @@
"json",
"transformation"
],
- "bundled": false,
+ "bundled": true,
"requiresDocker": false,
"hasCitrusTests": true,
"files": [
@@ -1489,7 +1493,7 @@
"validation",
"dependencies"
],
- "bundled": false,
+ "bundled": true,
"requiresDocker": false,
"hasCitrusTests": true,
"files": [
@@ -1530,7 +1534,7 @@
"jsonpath",
"transformation"
],
- "bundled": false,
+ "bundled": true,
"requiresDocker": false,
"hasCitrusTests": true,
"files": [
@@ -1567,7 +1571,7 @@
"jackson",
"transformation"
],
- "bundled": false,
+ "bundled": true,
"requiresDocker": false,
"hasCitrusTests": true,
"files": [
@@ -1599,7 +1603,7 @@
"xslt",
"transformation"
],
- "bundled": false,
+ "bundled": true,
"requiresDocker": false,
"hasCitrusTests": true,
"files": [
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/README.md
new file mode 100644
index 000000000000..3444ea6f50f3
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/README.md
@@ -0,0 +1,78 @@
+# File processing
+
+The courier's billing system drops invoices into an inbox directory. Each invoice is checked, archived under a
+month directory and moved to `done`; a bad one goes to `failed`; a file that is not an invoice is left alone.
+The first route plays the courier: it copies the five files in `samples/` into `inbox/`.
+
+## What you will see
+
+```text
+INFO ... file-processing.camel.yaml:47 : Invoice INV-2001 for ORD-1001: 49.4 EUR, archived as 2026-09/invoice-2001.json
+INFO ... file-processing.camel.yaml:47 : Invoice INV-2002 for ORD-1002: 28.5 EUR, archived as 2026-09/invoice-2002.json
+WARN ... file-processing.camel.yaml:11 : Invoice invoice-2003.json rejected: Validation failed for Predicate[bodyOgnl([amount]) > 0]. ...
+ERROR ... DefaultErrorHandler : Failed delivery for (MessageId: ...). Exhausted after delivery attempt: 1 caught: ...PredicateValidationException: ...
+WARN ... GenericFileOnCompletion : Rollback file strategy: ...GenericFileRenameProcessStrategy
+INFO ... file-processing.camel.yaml:47 : Invoice INV-2004 for ORD-1003: 53.45 EUR, archived as 2026-09/invoice-2004.json
+```
+
+## Install Camel CLI
+
+Install [JBang](https://www.jbang.dev/download/) and the Camel CLI as described in the
+[root README](../../README.md#install-the-camel-cli); `camel --version` confirms the install.
+
+## Run it
+
+```shell
+camel run *
+```
+
+Afterwards the directories tell the story: `inbox/done/` holds the three good invoices, `inbox/failed/` the
+rejected one, `archive/<year-month>/` copies of the good ones, and `inbox/note-2005.txt` is still where it was.
+Delete `inbox/` and `archive/` to run it again.
+
+## How it works
+
+- The `courier` route is a `file` consumer on `samples/` with `noop: true` writing to `file:inbox`, a copy.
+- The `invoices` route is the lesson: a `file` consumer on `inbox/` with `include: .*\.json`, so the driver's
+ note is never picked up; `move: done` for files that went through, `moveFailed: failed` for files whose
+ exchange failed. Both are directories relative to the inbox.
+- `validate` throws a `PredicateValidationException` when the amount is not positive; the `onException` logs a
+ warning without a stack trace and lets the exception through, which is what makes the consumer use `moveFailed`.
+ Had it set `handled: true`, the file would have been moved to `done` as if it had succeeded.
+- The archive copy uses a `fileName` expression with `${date:now:yyyy-MM}` for the directory and `${file:name}`
+ for the name; the file component creates directories as needed.
+
+## Build it step by step
+
+1. A `file` route on `inbox` that logs `${file:name}`; drop a file in and see it logged, then note the
+ `.camel` directory the consumer moved it to by default.
+2. Add `move: done` and `include: .*\.json`, drop the note in and see it ignored.
+3. Add the `courier` route so the samples arrive by themselves, and `unmarshal` and log the invoice.
+4. Add `validate` and `moveFailed: failed`, then the `onException` to tidy the log.
+
+## Try changing
+
+- Add `idempotent: true` with `idempotentKey: ${file:name}` and drop the same invoice twice.
+- Replace `move: done` with `delete: true`.
+- Point `archive` at a `sftp:` endpoint; the `ftp` example on the next rung shows how.
+
+## Integration testing
+
+The example comes with a test in the [Citrus](https://citrusframework.org/) YAML DSL,
+`test/file-processing.citrus.it.yaml`, which the Camel CLI runs:
+
+```shell
+camel test run test/file-processing.citrus.it.yaml
+```
+
+The test starts the routes and verifies the archived and rejected lines.
+
+## Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/application.properties b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/application.properties
new file mode 100644
index 000000000000..13a83393a912
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/application.properties
@@ -0,0 +1,16 @@
+# 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.
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/file-processing.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/file-processing.camel.yaml
new file mode 100644
index 000000000000..fd9446c3ce86
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/file-processing.camel.yaml
@@ -0,0 +1,56 @@
+# The courier's billing system drops invoices into the inbox. Each invoice
+# is checked, archived by month and moved to done; a bad one goes to failed.
+# The courier route plays the courier: it copies the samples into the inbox.
+- onException:
+ exception:
+ - org.apache.camel.support.processor.PredicateValidationException
+ redeliveryPolicy:
+ logStackTrace: false
+ logExhaustedMessageHistory: false
+ steps:
+ - log:
+ loggingLevel: WARN
+ message: "Invoice ${file:name} rejected: ${exception.message}"
+
+- route:
+ id: courier
+ from:
+ uri: file
+ parameters:
+ directoryName: samples
+ noop: true
+ sortBy: "file:name"
+ steps:
+ - to:
+ uri: file
+ parameters:
+ directoryName: inbox
+
+- route:
+ id: invoices
+ from:
+ uri: file
+ parameters:
+ directoryName: inbox
+ include: ".*\\.json"
+ move: done
+ moveFailed: failed
+ sortBy: "file:name"
+ steps:
+ - unmarshal:
+ json:
+ library: Jackson
+ - validate:
+ expression:
+ simple:
+ expression: "${body[amount]} > 0"
+ - log:
+ message: "Invoice ${body[invoiceId]} for ${body[orderId]}: ${body[amount]} EUR, archived as ${date:now:yyyy-MM}/${file:name}"
+ - marshal:
+ json:
+ library: Jackson
+ - to:
+ uri: file
+ parameters:
+ directoryName: archive
+ fileName: "${date:now:yyyy-MM}/${file:name}"
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/samples/invoice-2001.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/samples/invoice-2001.json
new file mode 100644
index 000000000000..51d79d522de0
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/samples/invoice-2001.json
@@ -0,0 +1 @@
+{"invoiceId": "INV-2001", "orderId": "ORD-1001", "amount": 49.40, "currency": "EUR"}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/samples/invoice-2002.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/samples/invoice-2002.json
new file mode 100644
index 000000000000..094523603805
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/samples/invoice-2002.json
@@ -0,0 +1 @@
+{"invoiceId": "INV-2002", "orderId": "ORD-1002", "amount": 28.50, "currency": "EUR"}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/samples/invoice-2003.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/samples/invoice-2003.json
new file mode 100644
index 000000000000..a084e66f4dd1
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/samples/invoice-2003.json
@@ -0,0 +1 @@
+{"invoiceId": "INV-2003", "orderId": "ORD-1003", "amount": -5.00, "currency": "EUR"}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/samples/invoice-2004.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/samples/invoice-2004.json
new file mode 100644
index 000000000000..761c24828f0a
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/samples/invoice-2004.json
@@ -0,0 +1 @@
+{"invoiceId": "INV-2004", "orderId": "ORD-1003", "amount": 53.45, "currency": "EUR"}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/samples/note-2005.txt b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/samples/note-2005.txt
new file mode 100644
index 000000000000..e82a2d850509
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/samples/note-2005.txt
@@ -0,0 +1 @@
+Handwritten note from the driver: parcel for ORD-1002 left with the neighbour.
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/http-client/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/http-client/README.md
new file mode 100644
index 000000000000..f03565365e6f
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/http-client/README.md
@@ -0,0 +1,77 @@
+# HTTP client
+
+Before an order goes to picking, every line is checked against the stock service over HTTP. The stock service
+runs inside this same example, so there is nothing else to start: the first two routes are a trimmed copy of
+`stock-api`, the third is the client.
+
+## What you will see
+
+```text
+INFO ... http-client.camel.yaml:112 : ORD-1001: CAMEL-TSHIRT x 2, 120 in stock, ok
+INFO ... http-client.camel.yaml:112 : ORD-1001: CAMEL-MUG x 1, 42 in stock, ok
+INFO ... http-client.camel.yaml:112 : ORD-1002: CAMEL-MUG x 3, 42 in stock, ok
+INFO ... http-client.camel.yaml:112 : ORD-1003: CAMEL-TSHIRT x 1, 120 in stock, ok
+INFO ... http-client.camel.yaml:116 : ORD-1003: CAMEL-CAP x 1, only 0 in stock, back-order
+INFO ... http-client.camel.yaml:112 : ORD-1003: CAMEL-MUG x 2, 42 in stock, ok
+```
+
+## Install Camel CLI
+
+Install [JBang](https://www.jbang.dev/download/) and the Camel CLI as described in the
+[root README](../../README.md#install-the-camel-cli); `camel --version` confirms the install.
+
+## Run it
+
+```shell
+camel run *
+```
+
+The three orders in `orders/` are read once each (`noop: true` leaves the files in place), so the run can be
+repeated. The stock levels are in `stock.json`.
+
+## How it works
+
+- The client splits each order into lines, as `order-lines` does, and calls `toD` with
+ `http://localhost:8080/stock/${exchangeProperty.sku}`: `toD` because the URI is built per message.
+- The SKU, the quantity and the order id are kept in exchange properties, not headers: headers travel on the
+ HTTP request, and a header named `sku` would collide with the `{sku}` path parameter on the server side.
+ Properties stay in the route.
+- The body is set to null before the call: a GET has no body, and the order line would otherwise be sent. The
+ response replaces the body, which is why the line was saved in properties first.
+- `throwExceptionOnFailure=false` turns a 404 into a normal response with `CamelHttpResponseCode` set, so the
+ `choice` can log it instead of the error handler.
+
+## Build it step by step
+
+1. Start from `stock-api` and add a `file` route on `orders` that logs each order.
+2. Split the lines and call `http://localhost:8080/stock/CAMEL-MUG` with a fixed SKU; log the response body.
+3. Build the URI from the line with `toD` and a property; notice the body is gone after the call.
+4. Add `throwExceptionOnFailure=false` and the `choice` on the response code and the quantity.
+
+## Try changing
+
+- Remove `throwExceptionOnFailure=false` and add a SKU that does not exist to an order: the 404 becomes an
+ `HttpOperationFailedException` handled by the error handler.
+- Call a public API instead, `https://api.github.com/repos/apache/camel` for example, and log `${body[stargazers_count]}`.
+- Set `camel.server.port` to another port and update the URI to match.
+
+## Integration testing
+
+The example comes with a test in the [Citrus](https://citrusframework.org/) YAML DSL,
+`test/http-client.citrus.it.yaml`, which the Camel CLI runs:
+
+```shell
+camel test run test/http-client.citrus.it.yaml
+```
+
+The test starts the routes and verifies the ok and back-order lines.
+
+## Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/http-client/application.properties b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/http-client/application.properties
new file mode 100644
index 000000000000..5d54c44f16dc
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/http-client/application.properties
@@ -0,0 +1,18 @@
+# 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.
+
+camel.server.port=8080
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/http-client/http-client.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/http-client/http-client.camel.yaml
new file mode 100644
index 000000000000..79f40db5b295
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/http-client/http-client.camel.yaml
@@ -0,0 +1,117 @@
+# Before an order goes to picking, every line is checked against the stock
+# service over HTTP. The stock service runs in this same example, so there
+# is nothing else to start.
+- rest:
+ path: /stock
+ get:
+ - path: "/{sku}"
+ to:
+ uri: direct:one-sku
+
+- route:
+ id: stock-service
+ from:
+ uri: direct
+ parameters:
+ name: one-sku
+ steps:
+ - setBody:
+ expression:
+ constant:
+ expression: resource:file:stock.json
+ - setBody:
+ expression:
+ jsonpath:
+ expression: "$[?(@.sku == '${header.sku}')]"
+ resultType: java.util.List
+ - choice:
+ when:
+ - expression:
+ simple:
+ expression: "${body.size()} == 0"
+ steps:
+ - setHeader:
+ name: CamelHttpResponseCode
+ expression:
+ constant:
+ expression: "404"
+ - setBody:
+ expression:
+ simple:
+ expression: '{"error": "unknown sku ${header.sku}"}'
+ otherwise:
+ steps:
+ - setBody:
+ expression:
+ simple:
+ expression: "${body[0]}"
+ - marshal:
+ json:
+ library: Jackson
+ - setHeader:
+ name: Content-Type
+ expression:
+ constant:
+ expression: application/json
+
+- route:
+ id: check-stock
+ from:
+ uri: file
+ parameters:
+ directoryName: orders
+ noop: true
+ sortBy: "file:name"
+ steps:
+ - unmarshal:
+ json:
+ library: Jackson
+ # exchange properties stay in the route; headers would travel on the HTTP request
+ - setProperty:
+ name: orderId
+ expression:
+ simple:
+ expression: "${body[orderId]}"
+ - split:
+ expression:
+ simple:
+ expression: "${body[lines]}"
+ steps:
+ - setProperty:
+ name: sku
+ expression:
+ simple:
+ expression: "${body[sku]}"
+ - setProperty:
+ name: needed
+ expression:
+ simple:
+ expression: "${body[qty]}"
+ # a GET has no body, and the response replaces whatever the body was
+ - setBody:
+ expression:
+ simple:
+ expression: "${null}"
+ - toD:
+ uri: "http://localhost:8080/stock/${exchangeProperty.sku}?throwExceptionOnFailure=false"
+ - unmarshal:
+ json:
+ library: Jackson
+ - choice:
+ when:
+ - expression:
+ simple:
+ expression: "${header.CamelHttpResponseCode} != 200"
+ steps:
+ - log:
+ message: "${exchangeProperty.orderId}: ${body[error]} (HTTP ${header.CamelHttpResponseCode})"
+ - expression:
+ simple:
+ expression: "${body[qty]} >= ${exchangeProperty.needed}"
+ steps:
+ - log:
+ message: "${exchangeProperty.orderId}: ${exchangeProperty.sku} x ${exchangeProperty.needed}, ${body[qty]} in stock, ok"
+ otherwise:
+ steps:
+ - log:
+ message: "${exchangeProperty.orderId}: ${exchangeProperty.sku} x ${exchangeProperty.needed}, only ${body[qty]} in stock, back-order"
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/http-client/orders/order-1001.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/http-client/orders/order-1001.json
new file mode 100644
index 000000000000..3eb5f038d636
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/http-client/orders/order-1001.json
@@ -0,0 +1,2 @@
+{"orderId": "ORD-1001", "customer": "C-482", "country": "DK", "status": "paid",
+ "lines": [{"sku": "CAMEL-TSHIRT", "qty": 2, "price": 19.95}, {"sku": "CAMEL-MUG", "qty": 1, "price": 9.50}]}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/http-client/orders/order-1002.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/http-client/orders/order-1002.json
new file mode 100644
index 000000000000..bb7aa0e9ff1c
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/http-client/orders/order-1002.json
@@ -0,0 +1,2 @@
+{"orderId": "ORD-1002", "customer": "C-207", "country": "DE", "status": "paid",
+ "lines": [{"sku": "CAMEL-MUG", "qty": 3, "price": 9.50}]}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/http-client/orders/order-1003.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/http-client/orders/order-1003.json
new file mode 100644
index 000000000000..9edab768cbd7
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/http-client/orders/order-1003.json
@@ -0,0 +1,2 @@
+{"orderId": "ORD-1003", "customer": "C-134", "country": "US", "status": "pending",
+ "lines": [{"sku": "CAMEL-TSHIRT", "qty": 1, "price": 19.95}, {"sku": "CAMEL-CAP", "qty": 1, "price": 14.00}, {"sku": "CAMEL-MUG", "qty": 2, "price": 9.50}]}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/http-client/stock.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/http-client/stock.json
new file mode 100644
index 000000000000..efab5de3aa66
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/http-client/stock.json
@@ -0,0 +1,5 @@
+[
+ {"sku": "CAMEL-TSHIRT", "qty": 120},
+ {"sku": "CAMEL-MUG", "qty": 42},
+ {"sku": "CAMEL-CAP", "qty": 0}
+]
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/stock-api/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/stock-api/README.md
new file mode 100644
index 000000000000..10cbd834a40f
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/stock-api/README.md
@@ -0,0 +1,80 @@
+# Stock API
+
+The shop's stock service: what the warehouse has on the shelf, per SKU. A REST API with two operations, served by
+the HTTP server built into the Camel CLI, answering from a JSON file.
+
+## What you will see
+
+```text
+$ curl localhost:8080/stock/CAMEL-MUG
+{"sku":"CAMEL-MUG","qty":42}
+
+$ curl -i localhost:8080/stock/CAMEL-SOCKS
+HTTP/1.1 404 Not Found
+{"error": "unknown sku CAMEL-SOCKS"}
+
+$ curl localhost:8080/stock
+[
+ {"sku": "CAMEL-TSHIRT", "qty": 120},
+ {"sku": "CAMEL-MUG", "qty": 42},
+ {"sku": "CAMEL-CAP", "qty": 0}
+]
+```
+
+## Install Camel CLI
+
+Install [JBang](https://www.jbang.dev/download/) and the Camel CLI as described in the
+[root README](../../README.md#install-the-camel-cli); `camel --version` confirms the install.
+
+## Run it
+
+```shell
+camel run *
+```
+
+Then, in another terminal, call it with `curl` as above. Stop it with `ctrl` + `c`.
+
+## How it works
+
+- `rest` with `path: /stock` declares the API: a `get` with no path for the list, a `get` with `/{sku}` for one
+ item. Each operation hands over to a `direct` route. The CLI starts the HTTP server on port 8080 because the
+ file uses the REST DSL; `application.properties` sets the port.
+- The list route sets the body to `resource:file:stock.json`, the file next to the route, and the content type.
+- The item route loads the same file and picks the item with a `jsonpath` filter, `$[?(@.sku == '...')]`, in which
+ the `{sku}` path parameter arrives as the header `sku`. The result is a list: empty means 404 with a small error
+ body, otherwise the first element is marshalled back to JSON.
+- `CamelHttpResponseCode` is the header that sets the status code; 200 is the default.
+
+## Build it step by step
+
+1. A `rest` with one `get` that answers `resource:file:stock.json`; run it and `curl localhost:8080/stock`.
+2. Add the `/{sku}` operation and log `${header.sku}` to see the path parameter arrive.
+3. Filter the file with `jsonpath` and return the first element; `curl` a known SKU.
+4. Add the `choice` for the empty result and the 404.
+
+## Try changing
+
+- Add `POST /stock/{sku}` that logs the body: `curl -X POST -d '{"qty": 5}' localhost:8080/stock/CAMEL-CAP`.
+- Change the port in `application.properties` and see the CLI pick it up.
+- The `http-client` example calls this service from another route.
+
+## Integration testing
+
+The example comes with a test in the [Citrus](https://citrusframework.org/) YAML DSL,
+`test/stock-api.citrus.it.yaml`, which the Camel CLI runs:
+
+```shell
+camel test run test/stock-api.citrus.it.yaml
+```
+
+The test starts the service and calls both operations over HTTP, checking the JSON and the 404.
+
+## Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/stock-api/application.properties b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/stock-api/application.properties
new file mode 100644
index 000000000000..5d54c44f16dc
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/stock-api/application.properties
@@ -0,0 +1,18 @@
+# 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.
+
+camel.server.port=8080
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/stock-api/stock-api.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/stock-api/stock-api.camel.yaml
new file mode 100644
index 000000000000..b5464b803204
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/stock-api/stock-api.camel.yaml
@@ -0,0 +1,73 @@
+# The shop's stock service: what the warehouse has on the shelf, per SKU.
+# GET /stock lists everything, GET /stock/{sku} answers for one SKU or 404.
+- rest:
+ path: /stock
+ get:
+ - to:
+ uri: direct:all-stock
+ - path: "/{sku}"
+ to:
+ uri: direct:one-sku
+
+- route:
+ id: all-stock
+ from:
+ uri: direct
+ parameters:
+ name: all-stock
+ steps:
+ - setBody:
+ expression:
+ constant:
+ expression: resource:file:stock.json
+ - setHeader:
+ name: Content-Type
+ expression:
+ constant:
+ expression: application/json
+
+- route:
+ id: one-sku
+ from:
+ uri: direct
+ parameters:
+ name: one-sku
+ steps:
+ - setBody:
+ expression:
+ constant:
+ expression: resource:file:stock.json
+ - setBody:
+ expression:
+ jsonpath:
+ expression: "$[?(@.sku == '${header.sku}')]"
+ resultType: java.util.List
+ - choice:
+ when:
+ - expression:
+ simple:
+ expression: "${body.size()} == 0"
+ steps:
+ - setHeader:
+ name: CamelHttpResponseCode
+ expression:
+ constant:
+ expression: "404"
+ - setBody:
+ expression:
+ simple:
+ expression: '{"error": "unknown sku ${header.sku}"}'
+ otherwise:
+ steps:
+ - setBody:
+ expression:
+ simple:
+ expression: "${body[0]}"
+ - marshal:
+ json:
+ library: Jackson
+ - setHeader:
+ name: Content-Type
+ expression:
+ constant:
+ expression: application/json
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/stock-api/stock.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/stock-api/stock.json
new file mode 100644
index 000000000000..efab5de3aa66
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/stock-api/stock.json
@@ -0,0 +1,5 @@
+[
+ {"sku": "CAMEL-TSHIRT", "qty": 120},
+ {"sku": "CAMEL-MUG", "qty": 42},
+ {"sku": "CAMEL-CAP", "qty": 0}
+]
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-client/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-client/README.md
new file mode 100644
index 000000000000..bee062478acc
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-client/README.md
@@ -0,0 +1,86 @@
+# OpenAPI client
+
+The picking desk reserves the stock for every order line by calling the stock API by contract: `stock-api.json`
+says which operations exist and how to call them, and the `rest-openapi` component turns an `operationId` into
+the HTTP request. The server is the `openapi-server` example, running in another terminal.
+
+## What you will see
+
+```text
+INFO ... openapi-client.camel.yaml:63 : ORD-1001: reserved 2 x CAMEL-TSHIRT, 120 left on the shelf
+INFO ... openapi-client.camel.yaml:63 : ORD-1001: reserved 1 x CAMEL-MUG, 42 left on the shelf
+INFO ... openapi-client.camel.yaml:63 : ORD-1002: reserved 3 x CAMEL-MUG, 42 left on the shelf
+INFO ... openapi-client.camel.yaml:63 : ORD-1003: reserved 1 x CAMEL-TSHIRT, 120 left on the shelf
+WARN ... openapi-client.camel.yaml:13 : ORD-1003: CAMEL-CAP not reserved, the stock API answered 409: {"error": "only 0 CAMEL-CAP in stock, 1 wanted for ORD-1003"}
+INFO ... openapi-client.camel.yaml:63 : ORD-1003: reserved 2 x CAMEL-MUG, 42 left on the shelf
+```
+
+## Install Camel CLI
+
+Install [JBang](https://www.jbang.dev/download/) and the Camel CLI as described in the
+[root README](../../README.md#install-the-camel-cli); `camel --version` confirms the install.
+
+## Run it
+
+Start the server first, in the `openapi-server` directory:
+
+```shell
+camel run *
+```
+
+Then, in this directory:
+
+```shell
+camel run *
+```
+
+The three orders in `orders/` are read once each (`noop: true` leaves the files in place), so the run
+can be repeated. `stock.api.url` in `application.properties` says where the server is.
+
+## How it works
+
+- `rest-openapi` with `specificationUri: stock-api.json` and `operationId: reserveStock` is the call: the
+ component reads the verb, the path and the parameters from the contract, so the route never spells out
+ `POST /api/stock/{sku}/reserve`. `host` is the server; `componentName: http` picks the HTTP client to use.
+- The path parameter `sku` is taken from the header of the same name; the request body is the message body,
+ built here as the JSON the contract's `Reservation` schema describes.
+- The order id and SKU are also kept in exchange properties for the log, since the response replaces the body
+ and headers are sent on the wire, as the `http-client` example explains.
+- A 409 or 400 from the server is an `HttpOperationFailedException`; the `onException` logs its status code
+ and response body and continues with the next line.
+
+## Build it step by step
+
+1. A timer route that calls `rest-openapi` with `operationId: getStock` and a fixed `sku` header; log the body.
+2. Read the orders, split the lines, and call `reserveStock` with the reservation as the body.
+3. Keep the order id and SKU in properties and log the result.
+4. Add the `onException` and see the cap's 409 handled instead of failing the order.
+
+## Try changing
+
+- Set `requestValidationEnabled: true` on the endpoint and send a reservation without `qty`: the client
+ refuses it before any HTTP call.
+- Point `stock.api.url` at a server that is not running and watch the connection error reach the error handler.
+- Replace the contract's server with a public one, `https://petstore3.swagger.io/api/v3/openapi.json`, and
+ call `getPetById`.
+
+## Integration testing
+
+The example comes with a test in the [Citrus](https://citrusframework.org/) YAML DSL,
+`test/openapi-client.citrus.it.yaml`, which the Camel CLI runs:
+
+```shell
+camel test run test/openapi-client.citrus.it.yaml
+```
+
+The test starts the `openapi-server` example and this client, and verifies the reservations and the 409.
+
+## Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-client/application.properties b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-client/application.properties
new file mode 100644
index 000000000000..cfc23ac075c7
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-client/application.properties
@@ -0,0 +1,18 @@
+# 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.
+
+stock.api.url=http://localhost:8080
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-client/openapi-client.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-client/openapi-client.camel.yaml
new file mode 100644
index 000000000000..f45644fce446
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-client/openapi-client.camel.yaml
@@ -0,0 +1,64 @@
+# The picking desk reserves the stock for every order line by calling the
+# stock API by contract: stock-api.json says which operations exist and how
+# to call them, and rest-openapi turns an operationId into the HTTP call.
+# The server is the openapi-server example, running in another terminal.
+- onException:
+ exception:
+ - org.apache.camel.http.base.HttpOperationFailedException
+ handled:
+ constant:
+ expression: "true"
+ steps:
+ - log:
+ loggingLevel: WARN
+ message: "${exchangeProperty.orderId}: ${exchangeProperty.sku} not reserved, the stock API answered ${exception.statusCode}: ${exception.responseBody}"
+
+- route:
+ id: reserve-order-lines
+ from:
+ uri: file
+ parameters:
+ directoryName: orders
+ noop: true
+ sortBy: "file:name"
+ steps:
+ - unmarshal:
+ json:
+ library: Jackson
+ - setProperty:
+ name: orderId
+ expression:
+ simple:
+ expression: "${body[orderId]}"
+ - split:
+ expression:
+ simple:
+ expression: "${body[lines]}"
+ steps:
+ - setProperty:
+ name: sku
+ expression:
+ simple:
+ expression: "${body[sku]}"
+ # the path parameter comes from the header of the same name
+ - setHeader:
+ name: sku
+ expression:
+ simple:
+ expression: "${body[sku]}"
+ - setBody:
+ expression:
+ simple:
+ expression: '{"orderId": "${exchangeProperty.orderId}", "qty": ${body[qty]}}'
+ - to:
+ uri: rest-openapi
+ parameters:
+ specificationUri: stock-api.json
+ operationId: reserveStock
+ host: "{{stock.api.url}}"
+ componentName: http
+ - unmarshal:
+ json:
+ library: Jackson
+ - log:
+ message: "${exchangeProperty.orderId}: reserved ${body[reserved]} x ${body[sku]}, ${body[remaining]} left on the shelf"
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-client/orders/order-1001.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-client/orders/order-1001.json
new file mode 100644
index 000000000000..3eb5f038d636
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-client/orders/order-1001.json
@@ -0,0 +1,2 @@
+{"orderId": "ORD-1001", "customer": "C-482", "country": "DK", "status": "paid",
+ "lines": [{"sku": "CAMEL-TSHIRT", "qty": 2, "price": 19.95}, {"sku": "CAMEL-MUG", "qty": 1, "price": 9.50}]}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-client/orders/order-1002.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-client/orders/order-1002.json
new file mode 100644
index 000000000000..bb7aa0e9ff1c
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-client/orders/order-1002.json
@@ -0,0 +1,2 @@
+{"orderId": "ORD-1002", "customer": "C-207", "country": "DE", "status": "paid",
+ "lines": [{"sku": "CAMEL-MUG", "qty": 3, "price": 9.50}]}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-client/orders/order-1003.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-client/orders/order-1003.json
new file mode 100644
index 000000000000..9edab768cbd7
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-client/orders/order-1003.json
@@ -0,0 +1,2 @@
+{"orderId": "ORD-1003", "customer": "C-134", "country": "US", "status": "pending",
+ "lines": [{"sku": "CAMEL-TSHIRT", "qty": 1, "price": 19.95}, {"sku": "CAMEL-CAP", "qty": 1, "price": 14.00}, {"sku": "CAMEL-MUG", "qty": 2, "price": 9.50}]}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-client/stock-api.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-client/stock-api.json
new file mode 100644
index 000000000000..279b68124da7
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-client/stock-api.json
@@ -0,0 +1,201 @@
+{
+ "openapi": "3.0.3",
+ "info": {
+ "title": "Stock API",
+ "description": "What the warehouse has on the shelf, per SKU, and reservations against it.",
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost:8080/api"
+ }
+ ],
+ "paths": {
+ "/stock": {
+ "get": {
+ "operationId": "listStock",
+ "summary": "All stock levels",
+ "responses": {
+ "200": {
+ "description": "The stock levels",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/StockItem"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/stock/{sku}": {
+ "get": {
+ "operationId": "getStock",
+ "summary": "Stock level of one SKU",
+ "parameters": [
+ {
+ "name": "sku",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "The stock level",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/StockItem"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Unknown SKU",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/stock/{sku}/reserve": {
+ "post": {
+ "operationId": "reserveStock",
+ "summary": "Reserve a quantity of one SKU for an order",
+ "parameters": [
+ {
+ "name": "sku",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Reservation"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Reserved",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ReservationResult"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "The request does not match the contract"
+ },
+ "404": {
+ "description": "Unknown SKU",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ }
+ }
+ }
+ },
+ "409": {
+ "description": "Not enough stock",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "StockItem": {
+ "type": "object",
+ "required": [
+ "sku",
+ "qty"
+ ],
+ "properties": {
+ "sku": {
+ "type": "string"
+ },
+ "qty": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ },
+ "Reservation": {
+ "type": "object",
+ "required": [
+ "orderId",
+ "qty"
+ ],
+ "properties": {
+ "orderId": {
+ "type": "string"
+ },
+ "qty": {
+ "type": "integer",
+ "minimum": 1
+ }
+ }
+ },
+ "ReservationResult": {
+ "type": "object",
+ "required": [
+ "sku",
+ "reserved",
+ "remaining"
+ ],
+ "properties": {
+ "sku": {
+ "type": "string"
+ },
+ "reserved": {
+ "type": "integer"
+ },
+ "remaining": {
+ "type": "integer"
+ }
+ }
+ },
+ "Error": {
+ "type": "object",
+ "required": [
+ "error"
+ ],
+ "properties": {
+ "error": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-server/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-server/README.md
new file mode 100644
index 000000000000..04fbb1092666
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-server/README.md
@@ -0,0 +1,91 @@
+# OpenAPI server
+
+The stock API, contract first. `stock-api.json` is the OpenAPI contract: three operations, their parameters,
+request and response schemas and status codes. The REST DSL serves every operation in it, validates requests
+against it and hands each operation to a `direct` route named after its `operationId`.
+
+## What you will see
+
+```text
+$ curl localhost:8080/api/stock/CAMEL-MUG
+{"sku":"CAMEL-MUG","qty":42}
+
+$ curl -X POST -H 'Content-Type: application/json' -d '{"orderId": "ORD-1001", "qty": 2}' localhost:8080/api/stock/CAMEL-MUG/reserve
+{"sku": "CAMEL-MUG", "reserved": 2, "remaining": 42}
+
+$ curl -i -X POST -H 'Content-Type: application/json' -d '{"orderId": "ORD-1003", "qty": 1}' localhost:8080/api/stock/CAMEL-CAP/reserve
+HTTP/1.1 409 Conflict
+{"error": "only 0 CAMEL-CAP in stock, 1 wanted for ORD-1003"}
+
+$ curl -i -X POST -H 'Content-Type: application/json' localhost:8080/api/stock/CAMEL-MUG/reserve
+HTTP/1.1 400 Bad Request
+
+INFO ... openapi-server.camel.yaml:123 : Reserved 2 x CAMEL-MUG for ORD-1001
+```
+
+## Install Camel CLI
+
+Install [JBang](https://www.jbang.dev/download/) and the Camel CLI as described in the
+[root README](../../README.md#install-the-camel-cli); `camel --version` confirms the install.
+
+## Run it
+
+```shell
+camel run *
+```
+
+The contract's `servers` entry gives the base path, `/api`, so the operations are under http://localhost:8080/api/stock
+and the contract itself is served at http://localhost:8080/openapi. Call the
+operations with `curl` as above from another terminal, or run the `openapi-client` example against it.
+Stop it with `ctrl` + `c`.
+
+## How it works
+
+- `rest` with `openApi: specification: stock-api.json` is the whole API definition: paths, verbs and parameters
+ come from the contract, not from the route file, and the base path `/api` from its `servers` entry, which
+ must have a path. `restConfiguration` switches on `clientRequestValidation`,
+ which rejects a request without the body or content type the contract requires with 400, and
+ `apiContextPath: openapi` serves the contract.
+- Each operation is a `direct` route named after its `operationId`: `listStock`, `getStock`, `reserveStock`.
+ Path parameters arrive as headers, `sku` here; the request body is the message body.
+- `lookup` is a helper route the two SKU operations share: it filters the stock file with `jsonpath` and
+ leaves the item, or null, as the body.
+- The contract check does not look at the values, so `reserveStock` runs `validate` on them and an
+ `onException` turns the failed predicate into a 400 with an error body, the status the contract promises.
+- `CamelHttpResponseCode` sets 404 and 409; the bodies are the JSON the contract's `Error` schema describes.
+
+## Build it step by step
+
+1. Write `stock-api.json` with only `listStock`, add the `rest` block and a `listStock` route that returns
+ `resource:file:stock.json`; run it and open http://localhost:8080/openapi.
+2. Add `getStock` to the contract and the route, with the `lookup` helper and the 404.
+3. Add `reserveStock` with its request schema; POST without a body and see the 400 from the contract check.
+4. Add `validate` and the `onException` for the values, then the 409 for short stock.
+
+## Try changing
+
+- Add a fourth operation to the contract and start the example: the missing route is reported at startup.
+- Set `missingOperation: mock` in `restConfiguration` and put an example response under `examples/`: the
+ operation is served from the example without a route.
+- Change a response schema in the contract and see what the client example makes of it.
+
+## Integration testing
+
+The example comes with a test in the [Citrus](https://citrusframework.org/) YAML DSL,
+`test/openapi-server.citrus.it.yaml`, which the Camel CLI runs:
+
+```shell
+camel test run test/openapi-server.citrus.it.yaml
+```
+
+The test starts the API and calls the three operations over HTTP, checking the 200, 409 and 400 answers.
+
+## Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-server/application.properties b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-server/application.properties
new file mode 100644
index 000000000000..5d54c44f16dc
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-server/application.properties
@@ -0,0 +1,18 @@
+# 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.
+
+camel.server.port=8080
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-server/openapi-server.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-server/openapi-server.camel.yaml
new file mode 100644
index 000000000000..23d364e09707
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-server/openapi-server.camel.yaml
@@ -0,0 +1,162 @@
+# The stock API, contract first: stock-api.json is the OpenAPI contract, the
+# REST DSL serves every operation in it, requests are validated against it,
+# and each operation is a direct route named after its operationId.
+- restConfiguration:
+ clientRequestValidation: true
+ apiContextPath: openapi
+
+# The contract check rejects a missing body or a wrong content type; the values are the route's job
+- onException:
+ exception:
+ - org.apache.camel.support.processor.PredicateValidationException
+ handled:
+ constant:
+ expression: "true"
+ steps:
+ - setHeader:
+ name: CamelHttpResponseCode
+ expression:
+ constant:
+ expression: "400"
+ - setBody:
+ expression:
+ simple:
+ expression: '{"error": "orderId and a positive qty are required"}'
+
+- rest:
+ openApi:
+ specification: stock-api.json
+
+- route:
+ id: listStock
+ from:
+ uri: direct
+ parameters:
+ name: listStock
+ steps:
+ - setBody:
+ expression:
+ constant:
+ expression: resource:file:stock.json
+
+- route:
+ id: getStock
+ from:
+ uri: direct
+ parameters:
+ name: getStock
+ steps:
+ - to:
+ uri: direct:lookup
+ - choice:
+ when:
+ - expression:
+ simple:
+ expression: "${body} == null"
+ steps:
+ - setHeader:
+ name: CamelHttpResponseCode
+ expression:
+ constant:
+ expression: "404"
+ - setBody:
+ expression:
+ simple:
+ expression: '{"error": "unknown sku ${header.sku}"}'
+ otherwise:
+ steps:
+ - marshal:
+ json:
+ library: Jackson
+
+- route:
+ id: reserveStock
+ from:
+ uri: direct
+ parameters:
+ name: reserveStock
+ steps:
+ - unmarshal:
+ json:
+ library: Jackson
+ - validate:
+ expression:
+ simple:
+ expression: "${body[orderId]} != null && ${body[qty]} > 0"
+ - setProperty:
+ name: reservation
+ expression:
+ simple:
+ expression: "${body}"
+ - to:
+ uri: direct:lookup
+ - choice:
+ when:
+ - expression:
+ simple:
+ expression: "${body} == null"
+ steps:
+ - setHeader:
+ name: CamelHttpResponseCode
+ expression:
+ constant:
+ expression: "404"
+ - setBody:
+ expression:
+ simple:
+ expression: '{"error": "unknown sku ${header.sku}"}'
+ - expression:
+ simple:
+ expression: "${body[qty]} < ${exchangeProperty.reservation[qty]}"
+ steps:
+ - setHeader:
+ name: CamelHttpResponseCode
+ expression:
+ constant:
+ expression: "409"
+ - setBody:
+ expression:
+ simple:
+ expression: '{"error": "only ${body[qty]} ${header.sku} in stock, ${exchangeProperty.reservation[qty]} wanted for ${exchangeProperty.reservation[orderId]}"}'
+ otherwise:
+ steps:
+ - log:
+ message: "Reserved ${exchangeProperty.reservation[qty]} x ${header.sku} for ${exchangeProperty.reservation[orderId]}"
+ - setBody:
+ expression:
+ simple:
+ expression: '{"sku": "${header.sku}", "reserved": ${exchangeProperty.reservation[qty]}, "remaining": ${body[qty]}}'
+
+# Finds one SKU in the stock file: the item as a map, or null
+- route:
+ id: lookup
+ from:
+ uri: direct
+ parameters:
+ name: lookup
+ steps:
+ - setBody:
+ expression:
+ constant:
+ expression: resource:file:stock.json
+ - setBody:
+ expression:
+ jsonpath:
+ expression: "$[?(@.sku == '${header.sku}')]"
+ resultType: java.util.List
+ - choice:
+ when:
+ - expression:
+ simple:
+ expression: "${body.size()} == 0"
+ steps:
+ - setBody:
+ expression:
+ simple:
+ expression: "${null}"
+ otherwise:
+ steps:
+ - setBody:
+ expression:
+ simple:
+ expression: "${body[0]}"
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-server/stock-api.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-server/stock-api.json
new file mode 100644
index 000000000000..279b68124da7
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-server/stock-api.json
@@ -0,0 +1,201 @@
+{
+ "openapi": "3.0.3",
+ "info": {
+ "title": "Stock API",
+ "description": "What the warehouse has on the shelf, per SKU, and reservations against it.",
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost:8080/api"
+ }
+ ],
+ "paths": {
+ "/stock": {
+ "get": {
+ "operationId": "listStock",
+ "summary": "All stock levels",
+ "responses": {
+ "200": {
+ "description": "The stock levels",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/StockItem"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/stock/{sku}": {
+ "get": {
+ "operationId": "getStock",
+ "summary": "Stock level of one SKU",
+ "parameters": [
+ {
+ "name": "sku",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "The stock level",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/StockItem"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Unknown SKU",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/stock/{sku}/reserve": {
+ "post": {
+ "operationId": "reserveStock",
+ "summary": "Reserve a quantity of one SKU for an order",
+ "parameters": [
+ {
+ "name": "sku",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Reservation"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Reserved",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ReservationResult"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "The request does not match the contract"
+ },
+ "404": {
+ "description": "Unknown SKU",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ }
+ }
+ }
+ },
+ "409": {
+ "description": "Not enough stock",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "StockItem": {
+ "type": "object",
+ "required": [
+ "sku",
+ "qty"
+ ],
+ "properties": {
+ "sku": {
+ "type": "string"
+ },
+ "qty": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ },
+ "Reservation": {
+ "type": "object",
+ "required": [
+ "orderId",
+ "qty"
+ ],
+ "properties": {
+ "orderId": {
+ "type": "string"
+ },
+ "qty": {
+ "type": "integer",
+ "minimum": 1
+ }
+ }
+ },
+ "ReservationResult": {
+ "type": "object",
+ "required": [
+ "sku",
+ "reserved",
+ "remaining"
+ ],
+ "properties": {
+ "sku": {
+ "type": "string"
+ },
+ "reserved": {
+ "type": "integer"
+ },
+ "remaining": {
+ "type": "integer"
+ }
+ }
+ },
+ "Error": {
+ "type": "object",
+ "required": [
+ "error"
+ ],
+ "properties": {
+ "error": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-server/stock.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-server/stock.json
new file mode 100644
index 000000000000..efab5de3aa66
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-server/stock.json
@@ -0,0 +1,5 @@
+[
+ {"sku": "CAMEL-TSHIRT", "qty": 120},
+ {"sku": "CAMEL-MUG", "qty": 42},
+ {"sku": "CAMEL-CAP", "qty": 0}
+]
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/circuit-breaker/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/circuit-breaker/README.md
index f2a99ddcb55d..c38d1596fe7e 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/circuit-breaker/README.md
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/circuit-breaker/README.md
@@ -19,7 +19,8 @@ INFO ... circuit-breaker.camel.yaml:30 : Stock check 15 (breaker CLOSED): CAMEL-
## Install Camel CLI
-<!-- see installation instructions in ../../install.adoc -->
+Install [JBang](https://www.jbang.dev/download/) and the Camel CLI as described in the
+[root README](../../README.md#install-the-camel-cli); `camel --version` confirms the install.
## Run it
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/error-handling/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/error-handling/README.md
new file mode 100644
index 000000000000..00b24b2a213f
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/error-handling/README.md
@@ -0,0 +1,82 @@
+# Error handling
+
+Three orders go to the payment provider. One is charged at once. For one the provider does not answer, twice,
+and the error handler retries until it does. One card is declined, which no retry will fix, so the order is
+parked as a file for someone to look at.
+
+## What you will see
+
+```text
+INFO ... error-handling.camel.yaml:40 : Payment for ORD-1001 charged, 2 line(s) to the warehouse
+WARN ... DeadLetterChannel : Failed delivery for (...). On delivery attempt: 0 caught: java.net.ConnectException: payment provider did not answer
+WARN ... DeadLetterChannel : Failed delivery for (...). On delivery attempt: 1 caught: java.net.ConnectException: payment provider did not answer
+INFO ... error-handling.camel.yaml:40 : Payment for ORD-1002 charged, 1 line(s) to the warehouse
+INFO ... error-handling.camel.yaml:21 : Payment for ORD-1003 declined: card declined
+INFO ... error-handling.camel.yaml:75 : Order ORD-1003 parked for manual review: card declined
+```
+
+## Install Camel CLI
+
+Install [JBang](https://www.jbang.dev/download/) and the Camel CLI as described in the
+[root README](../../README.md#install-the-camel-cli); `camel --version` confirms the install.
+
+## Run it
+
+```shell
+camel run *
+```
+
+The three orders in `orders/` are read once each (`noop: true` leaves the files in place). The declined order
+ends up in `parked/order-1003.json`.
+
+## How it works
+
+- `errorHandler` with `deadLetterChannel` is the default for every route in the file: a failure is retried twice
+ with a second in between (`redeliveryPolicy`), each attempt logged at WARN, and when the retries are used up the
+ message goes to the dead letter uri, `direct:parked`, and counts as handled.
+- `onException` for `IllegalStateException`, the declined card, overrides that for one exception type: no
+ retries, `handled` so the file consumer is done with the order, and its own steps log and park it.
+- The payment provider is its own route with `noErrorHandler`, so its exceptions reach the caller and the retry
+ repeats the whole `to: direct:charge` call, as it would for a remote service. Camel retries the step that
+ failed, and a step that always throws would always fail; the provider answers on the third attempt because it
+ reads `CamelRedeliveryCounter`.
+- The parked route can use `${exception.message}` because the error handler keeps the exception on the exchange.
+
+## Build it step by step
+
+1. A `file` route on `orders` that unmarshals each order, calls `direct:charge` and logs the payment; a second
+ route from `direct:charge` that throws for ORD-1003. Without any error handler the failure is logged with a
+ stack trace and the order is dropped.
+2. Add `errorHandler` with `deadLetterChannel` to `direct:parked` and a route that logs there. The stack
+ trace is gone and the order is parked, after three attempts a second apart.
+3. Add `noErrorHandler` to the provider route and make it throw `ConnectException` for ORD-1002 unless
+ `CamelRedeliveryCounter` is 2: two warnings, then charged.
+4. Add the `onException` for the declined card with no retries.
+
+## Try changing
+
+- Raise `maximumRedeliveries` to 5 with `backOffMultiplier: 2` and `useExponentialBackOff: true` and watch the
+ delays grow.
+- Replace `direct:parked` with a file endpoint straight in `deadLetterUri` and drop the parked route.
+- Make the provider fail for every order and see all three parked.
+
+## Integration testing
+
+The example comes with a test in the [Citrus](https://citrusframework.org/) YAML DSL,
+`test/error-handling.citrus.it.yaml`, which the Camel CLI runs:
+
+```shell
+camel test run test/error-handling.citrus.it.yaml
+```
+
+The test starts the route and verifies the charged, declined and parked lines.
+
+## Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/error-handling/application.properties b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/error-handling/application.properties
new file mode 100644
index 000000000000..13a83393a912
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/error-handling/application.properties
@@ -0,0 +1,16 @@
+# 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.
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/error-handling/error-handling.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/error-handling/error-handling.camel.yaml
new file mode 100644
index 000000000000..8d9f00600110
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/error-handling/error-handling.camel.yaml
@@ -0,0 +1,84 @@
+# Payment for the web shop orders. The payment provider is flaky for one order
+# and declines another; the error handler retries the first and parks both.
+- errorHandler:
+ deadLetterChannel:
+ deadLetterUri: direct:parked
+ redeliveryPolicy:
+ maximumRedeliveries: 2
+ redeliveryDelay: 1000
+ retryAttemptedLogLevel: WARN
+
+# A declined card is final: no retry, park it at once
+- onException:
+ exception:
+ - java.lang.IllegalStateException
+ redeliveryPolicy:
+ maximumRedeliveries: 0
+ handled:
+ constant:
+ expression: "true"
+ steps:
+ - log:
+ message: "Payment for ${body[orderId]} declined: ${exception.message}"
+ - to:
+ uri: direct:parked
+
+- route:
+ id: checkout
+ from:
+ uri: file
+ parameters:
+ directoryName: orders
+ noop: true
+ sortBy: "file:name"
+ steps:
+ - unmarshal:
+ json:
+ library: Jackson
+ - to:
+ uri: direct:charge
+ - log:
+ message: "Payment for ${body[orderId]} charged, ${body[lines].size()} line(s) to the warehouse"
+
+# The payment provider. No error handler here, so its failures reach the caller,
+# which retries the whole call as it would with a remote service.
+- route:
+ id: payment-provider
+ errorHandler:
+ noErrorHandler: {}
+ from:
+ uri: direct:charge
+ steps:
+ - choice:
+ when:
+ - expression:
+ simple:
+ expression: "${body[orderId]} == 'ORD-1002' && ${header.CamelRedeliveryCounter} != 2"
+ steps:
+ - throwException:
+ exceptionType: java.net.ConnectException
+ message: "payment provider did not answer"
+ - expression:
+ simple:
+ expression: "${body[orderId]} == 'ORD-1003'"
+ steps:
+ - throwException:
+ exceptionType: java.lang.IllegalStateException
+ message: "card declined"
+
+# Parked orders are written as files for someone to look at
+- route:
+ id: parked
+ from:
+ uri: direct:parked
+ steps:
+ - log:
+ message: "Order ${body[orderId]} parked for manual review: ${exception.message}"
+ - marshal:
+ json:
+ library: Jackson
+ - to:
+ uri: file
+ parameters:
+ directoryName: parked
+ fileName: "${header.CamelFileName}"
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/error-handling/orders/order-1001.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/error-handling/orders/order-1001.json
new file mode 100644
index 000000000000..3eb5f038d636
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/error-handling/orders/order-1001.json
@@ -0,0 +1,2 @@
+{"orderId": "ORD-1001", "customer": "C-482", "country": "DK", "status": "paid",
+ "lines": [{"sku": "CAMEL-TSHIRT", "qty": 2, "price": 19.95}, {"sku": "CAMEL-MUG", "qty": 1, "price": 9.50}]}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/error-handling/orders/order-1002.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/error-handling/orders/order-1002.json
new file mode 100644
index 000000000000..bb7aa0e9ff1c
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/error-handling/orders/order-1002.json
@@ -0,0 +1,2 @@
+{"orderId": "ORD-1002", "customer": "C-207", "country": "DE", "status": "paid",
+ "lines": [{"sku": "CAMEL-MUG", "qty": 3, "price": 9.50}]}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/error-handling/orders/order-1003.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/error-handling/orders/order-1003.json
new file mode 100644
index 000000000000..9edab768cbd7
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/error-handling/orders/order-1003.json
@@ -0,0 +1,2 @@
+{"orderId": "ORD-1003", "customer": "C-134", "country": "US", "status": "pending",
+ "lines": [{"sku": "CAMEL-TSHIRT", "qty": 1, "price": 19.95}, {"sku": "CAMEL-CAP", "qty": 1, "price": 14.00}, {"sku": "CAMEL-MUG", "qty": 2, "price": 9.50}]}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/rest-api/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/rest-api/README.md
index db3dac32b83c..2fc82eea0e84 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/rest-api/README.md
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/rest-api/README.md
@@ -1,12 +1,73 @@
-## REST API
+# REST API
-This example shows a REST API with hello endpoints.
+A REST API on port 8080, served by the HTTP server built into the Camel CLI: `GET /api/hello` answers the
+greeting from `application.properties` and `GET /api/hello/{name}` answers a greeting with the name.
-### How to run
+## What you will see
- camel run rest-api.camel.yaml
+```text
+$ curl localhost:8080/api/hello
+Hello from Camel REST API!
-### Try it
+$ curl localhost:8080/api/hello/World
+Hello World from Camel REST API!
+```
- curl http://localhost:8080/api/hello
- curl http://localhost:8080/api/hello/World
+## Install Camel CLI
+
+Install [JBang](https://www.jbang.dev/download/) and the Camel CLI as described in the
+[root README](../../README.md#install-the-camel-cli); `camel --version` confirms the install.
+
+## Run it
+
+```shell
+camel run *
+```
+
+Then, in another terminal, call it with `curl` as above. Stop it with `ctrl` + `c`.
+
+## How it works
+
+- `rest-api.camel.yaml` declares the API with the REST DSL: a `rest` with `path: /api` and two `get`
+ operations, `/hello` and `/hello/{name}`. Each operation hands over to a `direct` route with `to`.
+- The two `direct` routes set the body that becomes the HTTP response: a constant with the greeting from
+ `application.properties`, and a simple expression in which the `{name}` path parameter arrives as the
+ header `name`.
+- The CLI starts the HTTP server because the file uses the REST DSL; `camel.server.port` in
+ `application.properties` sets the port.
+
+## Build it step by step
+
+Ask your assistant, or type it yourself, one step at a time, and run after each:
+
+1. A `rest` with `path: /api` and one `get` on `/hello` that goes to a direct route answering "Hello";
+ run it and `curl localhost:8080/api/hello`.
+2. Add the `/hello/{name}` operation and a second direct route that answers with `${header.name}`.
+3. Move the greeting and the port to `application.properties`.
+
+## Try changing
+
+- Add `POST /api/hello` that answers with the request body: `curl -d 'Camel' localhost:8080/api/hello`.
+- Set the header `Content-Type` to `application/json` and answer `{"greeting": "${body}"}`.
+- Run with `--console` and open http://localhost:8080/q/dev to see the routes and their statistics.
+
+## Integration testing
+
+The example comes with a test in the [Citrus](https://citrusframework.org/) YAML DSL,
+`test/rest-api.citrus.it.yaml`, which the Camel CLI runs:
+
+```shell
+camel test run test/rest-api.citrus.it.yaml
+```
+
+The test starts the route and calls both operations over HTTP.
+
+## Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/rest-api/application.properties b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/rest-api/application.properties
index f3ee4076eb17..4abd98e0127a 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/rest-api/application.properties
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/rest-api/application.properties
@@ -1,3 +1,20 @@
+# 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.
+
# HTTP server port
camel.server.port=8080
# Greeting message for the hello endpoint
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/routes/Greeter.java b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/routes/Greeter.java
index bdb9a45ec3a6..605356270bcf 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/routes/Greeter.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/routes/Greeter.java
@@ -1,20 +1,34 @@
+/*
+ * 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 camel.example;
-import org.apache.camel.Exchange;
-import org.apache.camel.Processor;
+/**
+ * Builds the greeting. A plain Java class next to the route, with no Camel API in it: beans.yaml declares it as the
+ * bean greeter and sets its greeting from a property, and the route calls its greet method with the message body.
+ */
+public class Greeter {
-public class Greeter implements Processor {
+ private String greeting;
- private String message;
-
- public void setMessage(String message) {
- this.message = message;
+ public void setGreeting(String greeting) {
+ this.greeting = greeting;
}
- @Override
- public void process(Exchange exchange) throws Exception {
- String body = exchange.getIn().getBody(String.class);
- exchange.getIn().setBody(message + " " + body);
+ public String greet(String name) {
+ return greeting + ", " + name + "!";
}
-
-}
\ No newline at end of file
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/routes/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/routes/README.md
index 8157bcfa24ed..c8e1d74f6af4 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/routes/README.md
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/routes/README.md
@@ -1,67 +1,68 @@
-## Routes
+# Routes
-This example shows how routes are defined in Yaml.
+The first step from YAML into your own code: a timer route in YAML calls a Java bean, `Greeter`, that builds
+the message, and logs what the bean returned.
-### Install JBang
+## What you will see
-First install JBang according to https://www.jbang.dev
-
-When JBang is installed then you should be able to run from a shell:
-
-```sh
-$ jbang --version
-```
-
-This will output the version of JBang.
-
-To run this example you can either install Camel on JBang via:
-
-```sh
-$ jbang app install camel@apache/camel
+```text
+INFO ... routes.camel.yaml:16 : Hello, Camel!
+INFO ... routes.camel.yaml:16 : Hello, Camel!
```
-Which allows to run Camel CLI with `camel` as shown below.
+## Install Camel CLI
-### How to run
+Install [JBang](https://www.jbang.dev/download/) and the Camel CLI as described in the
+[root README](../../README.md#install-the-camel-cli); `camel --version` confirms the install.
-You can run this example using:
+## Run it
-```sh
-$ camel run *
+```shell
+camel run *
```
-Camel will start a route that periodically provides a greeting message.
+Stop it with `ctrl` + `c`, or from another terminal with `camel stop routes`.
-### Live reload
+## How it works
-You can run the example in dev mode which allows you to edit the example,
-and hot-reload when the file is saved.
+- `routes.camel.yaml` is the route: a `timer` fires every second, a `setBody` puts the name `Camel` in the
+ message, a `bean` step calls the `greet` method of the bean `greeter` with the body, and a `log` prints
+ what the method returned, which is now the body.
+- `Greeter.java` is a plain Java class next to the route, with no Camel API in it: a `greeting` property and a
+ `greet(String name)` method. The CLI compiles it when the route starts.
+- `beans.yaml` declares the class as the bean `greeter` and sets its `greeting` from a property.
+- `application.properties` holds the greeting and the timer period.
-```sh
-$ camel run * --dev
-```
+## Build it step by step
-### Run directly from GitHub
+Ask your assistant, or type it yourself, one step at a time, and run after each:
-The example can also be run directly by referring to the GitHub URL as shown:
+1. A route from a timer that sets the body to "Camel" and logs it.
+2. A Java class `Greeter` in the package `camel.example` with a method `greet(String name)` that returns
+ "Hello, " and the name, declared as the bean `greeter` in `beans.yaml`; call it from the route with a
+ `bean` step before the log.
+3. Give the class a `greeting` property, set it in `beans.yaml`, and move its value to
+ `application.properties`.
-```sh
-$ camel run https://github.com/apache/camel-jbang-examples/tree/main/routes
-```
+## Try changing
-### Developer Web Console
+- `greeter.greeting=Hej` in `application.properties`; the Java class and the route do not change.
+- Add a second method `shout(String name)` that returns the greeting in upper case and switch the `method` in
+ the route.
+- Leave out `method: greet` and see that Camel finds the single public method by itself.
-You can enable the developer console via `--console` flag as show:
+## Integration testing
-```sh
-$ camel run * --console
-```
+The example comes with a test in the [Citrus](https://citrusframework.org/) YAML DSL,
+`test/routes.citrus.it.yaml`, which the Camel CLI runs:
-Then you can browse: http://localhost:8080/q/dev to introspect the running Camel Application.
-Under "beans" Camel should display bean `greeter`.
+```shell
+camel test run test/routes.citrus.it.yaml
+```
+The test starts the route, with the bean and the Java class, and verifies the logged greeting.
-### Help and contributions
+## Help and contributions
If you hit any problem using Camel or have some feedback, then please
[let us know](https://camel.apache.org/community/support/).
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/routes/application.properties b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/routes/application.properties
index 9049d38c3ecf..a6616c43986d 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/routes/application.properties
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/routes/application.properties
@@ -1,2 +1,21 @@
-# Greeting message used by the Greeter bean
-greeter.message=Hello!
+# 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.
+
+# The greeting the Greeter bean starts its message with
+greeter.greeting=Hello
+# How often the timer fires, in milliseconds
+greeter.period=1000
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/routes/beans.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/routes/beans.yaml
index a984e7798770..ec4497028a00 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/routes/beans.yaml
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/routes/beans.yaml
@@ -1,5 +1,5 @@
- beans:
- - name: "greeter"
- type: "camel.example.Greeter"
- properties:
- message: '{{greeter.message}}'
+ - name: greeter
+ type: "camel.example.Greeter"
+ properties:
+ greeting: "{{greeter.greeting}}"
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/routes/routes.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/routes/routes.camel.yaml
index a15415f483a1..76ae52820ca4 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/routes/routes.camel.yaml
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/routes/routes.camel.yaml
@@ -1,17 +1,17 @@
- route:
- id: greeting-route
+ id: greeting
from:
uri: timer
parameters:
- timerName: start
- period: 1000
+ timerName: greet
+ period: "{{greeter.period}}"
steps:
- setBody:
expression:
- simple:
- expression: "I'm ${routeId}"
+ constant:
+ expression: "Camel"
- bean:
ref: greeter
+ method: greet
- log:
message: "${body}"
-
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/splitter/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/splitter/README.md
index ea673ccfe3aa..a07be02973d6 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/splitter/README.md
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/splitter/README.md
@@ -1,42 +1,72 @@
-## Splitter
+# Splitter
-This example demonstrates the Splitter EIP.
+A timer creates a comma-separated batch of items, the splitter turns it into one message per item, and each item
+is logged on its own line.
-A batch of comma-separated product names is split into individual messages.
-Each item is logged with its split index.
+## What you will see
-The splitter processes each part of the message independently, which is useful
-for breaking down bulk data into individual records for downstream processing.
+```text
+INFO ... splitter.camel.yaml:14 : Received batch: Laptop,Phone,Tablet,Monitor,Keyboard
+INFO ... splitter.camel.yaml:21 : Processing item 0: Laptop
+INFO ... splitter.camel.yaml:21 : Processing item 1: Phone
+INFO ... splitter.camel.yaml:21 : Processing item 2: Tablet
+INFO ... splitter.camel.yaml:21 : Processing item 3: Monitor
+INFO ... splitter.camel.yaml:21 : Processing item 4: Keyboard
+```
+
+The batch is sent three times, five seconds apart, and then the timer stops.
-### Install JBang
+## Install Camel CLI
-First install JBang according to https://www.jbang.dev
+Install [JBang](https://www.jbang.dev/download/) and the Camel CLI as described in the
+[root README](../../README.md#install-the-camel-cli); `camel --version` confirms the install.
-When JBang is installed then you should be able to run from a shell:
+## Run it
-```sh
-$ jbang --version
+```shell
+camel run *
```
-This will output the version of JBang.
+Stop it with `ctrl` + `c`, or from another terminal with `camel stop splitter`.
-To run this example you can either install Camel on JBang via:
+## How it works
-```sh
-$ jbang app install camel@apache/camel
-```
+- `splitter.camel.yaml` is the route: a `timer` with `repeatCount: 3` fires three times, a `setBody` puts the
+ batch in the message as one string, and a `log` prints it.
+- `split` with a `tokenize` expression on `,` turns the one message into five, one per item. The steps under
+ the `split` run once for each of them: here a `log` with the item.
+- `CamelSplitIndex` is a header the splitter sets on each part, counting from 0; `CamelSplitSize` is the
+ total and `CamelSplitComplete` is true on the last part.
+- After the last part the route continues after the `split` with the original batch as the body; there is
+ nothing after it here.
+
+## Build it step by step
-Which allows to run Camel CLI with `camel` as shown below.
+Ask your assistant, or type it yourself, one step at a time, and run after each:
-### How to run
+1. A route from a timer that sets the body to "Laptop,Phone,Tablet" and logs it.
+2. Split the body on the comma and log each part.
+3. Add the split index to the log line, and `repeatCount: 3` to the timer.
-You can run this example using:
+## Try changing
-```sh
-$ camel run *
+- Log `${header.CamelSplitIndex} of ${header.CamelSplitSize}` on each part.
+- Add a `log` after the `split` and see the original batch come back as the body.
+- Split a JSON array instead: set the body to `["Laptop","Phone"]` and use `jsonpath: {expression: "$[*]"}`
+ as the split expression.
+
+## Integration testing
+
+The example comes with a test in the [Citrus](https://citrusframework.org/) YAML DSL,
+`test/splitter.citrus.it.yaml`, which the Camel CLI runs:
+
+```shell
+camel test run test/splitter.citrus.it.yaml
```
-### Help and contributions
+The test starts the route and verifies the batch and the last item are logged.
+
+## Help and contributions
If you hit any problem using Camel or have some feedback, then please
[let us know](https://camel.apache.org/community/support/).
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/timer-log/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/timer-log/README.md
index f773ce1bad55..5b44f204ef82 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/timer-log/README.md
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/timer-log/README.md
@@ -1,7 +1,69 @@
-## Timer Log
+# Timer Log
-This example shows a simple timer that logs a hello message every second.
+The hello of Camel in one file: a timer fires every second and a log line prints the greeting from
+`application.properties`.
-### How to run
+## What you will see
- camel run timer-log.camel.yaml
+```text
+INFO ... timer-log.camel.yaml:13 : Hello Camel!
+INFO ... timer-log.camel.yaml:13 : Hello Camel!
+INFO ... timer-log.camel.yaml:13 : Hello Camel!
+```
+
+## Install Camel CLI
+
+Install [JBang](https://www.jbang.dev/download/) and the Camel CLI as described in the
+[root README](../../README.md#install-the-camel-cli); `camel --version` confirms the install.
+
+## Run it
+
+```shell
+camel run *
+```
+
+Stop it with `ctrl` + `c`, or from another terminal with `camel stop timer-log`.
+
+## How it works
+
+- `timer-log.camel.yaml` is the route: `from` a `timer` that fires every `timer.period` milliseconds, a
+ `setBody` puts the greeting in the message, and a `log` prints the body.
+- `application.properties` holds the period and the greeting. `{{timer.period}}` and `{{greeting.message}}`
+ in the route are property placeholders, resolved when the route starts.
+- The Camel CLI reads every file in the directory: the route because it ends in `.camel.yaml`, the properties
+ because of their name.
+
+## Build it step by step
+
+Ask your assistant, or type it yourself, one step at a time, and run after each:
+
+1. A route from a timer that logs "Hello Camel!" every second.
+2. Move the greeting to `application.properties` and set the body from the property.
+3. Move the period to a property too.
+
+## Try changing
+
+- `timer.period=5000` for a greeting every five seconds; the route file does not change.
+- Add `repeatCount: 3` to the timer parameters and the timer stops after three greetings.
+- Log `${date:now:HH:mm:ss} ${body}` to see when each line was written.
+
+## Integration testing
+
+The example comes with a test in the [Citrus](https://citrusframework.org/) YAML DSL,
+`test/timer-log.citrus.it.yaml`, which the Camel CLI runs:
+
+```shell
+camel test run test/timer-log.citrus.it.yaml
+```
+
+The test starts the route and verifies the logged greeting.
+
+## Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/timer-log/application.properties b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/timer-log/application.properties
index 370577aacb69..5cc0a54df8e4 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/timer-log/application.properties
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/quick-start/timer-log/application.properties
@@ -1,3 +1,20 @@
+# 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.
+
# Timer period in milliseconds
timer.period=1000
# Greeting message to log
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/aggregator/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/aggregator/README.md
new file mode 100644
index 000000000000..56789ff9d1e1
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/aggregator/README.md
@@ -0,0 +1,73 @@
+# Aggregator
+
+The reverse of `order-lines`: the warehouse reports each picked line on its own, and the aggregator collects the
+lines of one order back into a shipment. Lines are correlated by the order id, and a shipment completes when all
+the lines the order had are in.
+
+## What you will see
+
+```text
+INFO ... aggregator.camel.yaml:26 : Picked 2 x CAMEL-TSHIRT for ORD-1001
+INFO ... aggregator.camel.yaml:26 : Picked 1 x CAMEL-MUG for ORD-1001
+INFO ... aggregator.camel.yaml:26 : Picked 3 x CAMEL-MUG for ORD-1002
+...
+INFO ... aggregator.camel.yaml:51 : Shipment for ORD-1001 complete: [{"sku":"CAMEL-TSHIRT","qty":2,"price":19.95},{"sku":"CAMEL-MUG","qty":1,"price":9.5}]
+INFO ... aggregator.camel.yaml:51 : Shipment for ORD-1002 complete: [{"sku":"CAMEL-MUG","qty":3,"price":9.5}]
+```
+
+## Install Camel CLI
+
+Install [JBang](https://www.jbang.dev/download/) and the Camel CLI as described in the
+[root README](../../README.md#install-the-camel-cli); `camel --version` confirms the install.
+
+## Run it
+
+```shell
+camel run *
+```
+
+The three orders in `orders/` are read once each (`noop: true` leaves the files in place), so the run
+can be repeated.
+
+## How it works
+
+- The first route reads the orders, keeps the order id in a header, splits them into lines, and sends every
+ line to `direct:shipment`, as a warehouse system would report picks one by one.
+- The second route aggregates: `correlationExpression` `${header.orderId}` groups lines of the same order,
+ `completionSizeExpression` `${header.CamelSplitSize}` says how many lines make the order complete, and the
+ `GroupedBodyAggregationStrategy` collects the bodies into a list.
+- When a shipment completes, the list of lines is written as JSON with `marshal` and logged.
+
+## Build it step by step
+
+1. Start from `order-lines` and send each line to `direct:shipment` instead of only logging it.
+2. Add a route from `direct:shipment` with an `aggregate` correlating on `${header.orderId}` and a fixed
+ `completionSize: 2`; only the two-line order completes.
+3. Replace the fixed size by `completionSizeExpression` with `${header.CamelSplitSize}`.
+4. Add `marshal` with `json` and log the shipment.
+
+## Try changing
+
+- Add `completionTimeout: 5000` so a shipment with a missing line still closes after five seconds.
+- Sum the pieces instead of listing the lines: the `data-mapping` example shows the Groovy for it.
+
+## Integration testing
+
+The example comes with a test in the [Citrus](https://citrusframework.org/) YAML DSL,
+`test/aggregator.citrus.it.yaml`, which the Camel CLI runs:
+
+```shell
+camel test run test/aggregator.citrus.it.yaml
+```
+
+The test starts the route and verifies that the shipments for the three orders complete.
+
+## Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/aggregator/aggregator.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/aggregator/aggregator.camel.yaml
new file mode 100644
index 000000000000..5148ba54b6b8
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/aggregator/aggregator.camel.yaml
@@ -0,0 +1,52 @@
+# The reverse of order-lines: the warehouse reports each picked line on its own, and the aggregator
+# collects the lines of one order back into a shipment. Lines are correlated by the order id, and a
+# shipment completes when all the lines the order had are in.
+- route:
+ id: picked-lines
+ from:
+ uri: file
+ parameters:
+ directoryName: orders
+ noop: true
+ sortBy: "file:name"
+ steps:
+ - unmarshal:
+ json:
+ library: Jackson
+ - setHeader:
+ name: orderId
+ expression:
+ simple:
+ expression: "${body[orderId]}"
+ - split:
+ expression:
+ simple:
+ expression: "${body[lines]}"
+ steps:
+ - log:
+ message: "Picked ${body[qty]} x ${body[sku]} for ${header.orderId}"
+ - to:
+ uri: direct
+ parameters:
+ name: shipment
+- route:
+ id: aggregator
+ from:
+ uri: direct
+ parameters:
+ name: shipment
+ steps:
+ - aggregate:
+ aggregationStrategy: "#class:org.apache.camel.processor.aggregate.GroupedBodyAggregationStrategy"
+ correlationExpression:
+ simple:
+ expression: "${header.orderId}"
+ completionSizeExpression:
+ simple:
+ expression: "${header.CamelSplitSize}"
+ steps:
+ - marshal:
+ json:
+ library: Jackson
+ - log:
+ message: "Shipment for ${header.orderId} complete: ${body}"
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/aggregator/application.properties b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/aggregator/application.properties
new file mode 100644
index 000000000000..13a83393a912
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/aggregator/application.properties
@@ -0,0 +1,16 @@
+# 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.
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/aggregator/orders/order-1001.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/aggregator/orders/order-1001.json
new file mode 100644
index 000000000000..3eb5f038d636
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/aggregator/orders/order-1001.json
@@ -0,0 +1,2 @@
+{"orderId": "ORD-1001", "customer": "C-482", "country": "DK", "status": "paid",
+ "lines": [{"sku": "CAMEL-TSHIRT", "qty": 2, "price": 19.95}, {"sku": "CAMEL-MUG", "qty": 1, "price": 9.50}]}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/aggregator/orders/order-1002.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/aggregator/orders/order-1002.json
new file mode 100644
index 000000000000..bb7aa0e9ff1c
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/aggregator/orders/order-1002.json
@@ -0,0 +1,2 @@
+{"orderId": "ORD-1002", "customer": "C-207", "country": "DE", "status": "paid",
+ "lines": [{"sku": "CAMEL-MUG", "qty": 3, "price": 9.50}]}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/aggregator/orders/order-1003.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/aggregator/orders/order-1003.json
new file mode 100644
index 000000000000..9edab768cbd7
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/aggregator/orders/order-1003.json
@@ -0,0 +1,2 @@
+{"orderId": "ORD-1003", "customer": "C-134", "country": "US", "status": "pending",
+ "lines": [{"sku": "CAMEL-TSHIRT", "qty": 1, "price": 19.95}, {"sku": "CAMEL-CAP", "qty": 1, "price": 14.00}, {"sku": "CAMEL-MUG", "qty": 2, "price": 9.50}]}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/content-based-router/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/content-based-router/README.md
new file mode 100644
index 000000000000..4bbb6390c7ff
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/content-based-router/README.md
@@ -0,0 +1,67 @@
+# Content-based router
+
+Three orders from three countries. A `choice` routes each by its country: the Danish order to local delivery,
+EU orders to shipping without customs, everything else to export with a customs declaration.
+
+## What you will see
+
+```text
+INFO ... content-based-router.camel.yaml:22 : Order ORD-1001 from DK: local delivery from the Copenhagen warehouse
+INFO ... content-based-router.camel.yaml:28 : Order ORD-1002 from DE: EU shipping, no customs
+INFO ... content-based-router.camel.yaml:32 : Order ORD-1003 from US: export, customs declaration needed
+```
+
+## Install Camel CLI
+
+Install [JBang](https://www.jbang.dev/download/) and the Camel CLI as described in the
+[root README](../../README.md#install-the-camel-cli); `camel --version` confirms the install.
+
+## Run it
+
+```shell
+camel run *
+```
+
+The three orders in `orders/` are read once each (`noop: true` leaves the files in place), so the run
+can be repeated.
+
+## How it works
+
+- The `file` consumer reads the three orders from `orders/` in name order; `unmarshal` with `json` parses each,
+ so `${body[country]}` reads a field.
+- `choice` tests the country: `== 'DK'` first, then `in 'DE,SE,NL,FR'`, the simple language's list test, and
+ `otherwise` takes the rest. The first matching `when` wins.
+- Each branch only logs here; in a real shop each `to:` a different route or system.
+
+## Build it step by step
+
+1. A `file` route on `orders` that unmarshals each order and logs `${body[orderId]}` and `${body[country]}`.
+2. Add a `choice` with one `when` for `DK` and an `otherwise`, each logging a different line.
+3. Add a second `when` for the EU countries with `in 'DE,SE,NL,FR'`.
+4. Add a fourth order file for a Swedish customer and see it take the EU branch.
+
+## Try changing
+
+- Route on the order value instead: `${body[lines].size()} > 2` for big orders.
+- Replace the logs with `to: direct:...` routes, one per branch, as `filter-and-multicast` does.
+
+## Integration testing
+
+The example comes with a test in the [Citrus](https://citrusframework.org/) YAML DSL,
+`test/content-based-router.citrus.it.yaml`, which the Camel CLI runs:
+
+```shell
+camel test run test/content-based-router.citrus.it.yaml
+```
+
+The test starts the route and verifies that each of the three orders is logged from its branch.
+
+## Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/content-based-router/application.properties b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/content-based-router/application.properties
new file mode 100644
index 000000000000..13a83393a912
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/content-based-router/application.properties
@@ -0,0 +1,16 @@
+# 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.
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/content-based-router/content-based-router.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/content-based-router/content-based-router.camel.yaml
new file mode 100644
index 000000000000..017afeea9279
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/content-based-router/content-based-router.camel.yaml
@@ -0,0 +1,33 @@
+# Three orders from three countries. A choice routes each by its country: Danish orders go to the
+# local warehouse, EU orders ship without customs, everything else is an export with a customs
+# declaration. Each branch logs what it did.
+- route:
+ id: content-based-router
+ from:
+ uri: file
+ parameters:
+ directoryName: orders
+ noop: true
+ sortBy: "file:name"
+ steps:
+ - unmarshal:
+ json:
+ library: Jackson
+ - choice:
+ when:
+ - expression:
+ simple:
+ expression: "${body[country]} == 'DK'"
+ steps:
+ - log:
+ message: "Order ${body[orderId]} from ${body[country]}: local delivery from the Copenhagen warehouse"
+ - expression:
+ simple:
+ expression: "${body[country]} in 'DE,SE,NL,FR'"
+ steps:
+ - log:
+ message: "Order ${body[orderId]} from ${body[country]}: EU shipping, no customs"
+ otherwise:
+ steps:
+ - log:
+ message: "Order ${body[orderId]} from ${body[country]}: export, customs declaration needed"
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/content-based-router/orders/order-1001.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/content-based-router/orders/order-1001.json
new file mode 100644
index 000000000000..3eb5f038d636
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/content-based-router/orders/order-1001.json
@@ -0,0 +1,2 @@
+{"orderId": "ORD-1001", "customer": "C-482", "country": "DK", "status": "paid",
+ "lines": [{"sku": "CAMEL-TSHIRT", "qty": 2, "price": 19.95}, {"sku": "CAMEL-MUG", "qty": 1, "price": 9.50}]}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/content-based-router/orders/order-1002.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/content-based-router/orders/order-1002.json
new file mode 100644
index 000000000000..bb7aa0e9ff1c
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/content-based-router/orders/order-1002.json
@@ -0,0 +1,2 @@
+{"orderId": "ORD-1002", "customer": "C-207", "country": "DE", "status": "paid",
+ "lines": [{"sku": "CAMEL-MUG", "qty": 3, "price": 9.50}]}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/content-based-router/orders/order-1003.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/content-based-router/orders/order-1003.json
new file mode 100644
index 000000000000..9edab768cbd7
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/content-based-router/orders/order-1003.json
@@ -0,0 +1,2 @@
+{"orderId": "ORD-1003", "customer": "C-134", "country": "US", "status": "pending",
+ "lines": [{"sku": "CAMEL-TSHIRT", "qty": 1, "price": 19.95}, {"sku": "CAMEL-CAP", "qty": 1, "price": 14.00}, {"sku": "CAMEL-MUG", "qty": 2, "price": 9.50}]}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/filter-and-multicast/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/filter-and-multicast/README.md
new file mode 100644
index 000000000000..fd45f5348091
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/filter-and-multicast/README.md
@@ -0,0 +1,69 @@
+# Filter and multicast
+
+Only paid orders go further, and a paid order goes to two places at once: the warehouse to pick it and invoicing
+to bill it. The `filter` drops the unpaid order; the `multicast` sends the same message to both routes.
+
+## What you will see
+
+```text
+INFO ... filter-and-multicast.camel.yaml:16 : Order ORD-1001 received, status paid
+INFO ... filter-and-multicast.camel.yaml:40 : Warehouse: pick 2 line(s) for ORD-1001
+INFO ... filter-and-multicast.camel.yaml:49 : Invoicing: bill customer C-482 for ORD-1001
+INFO ... filter-and-multicast.camel.yaml:16 : Order ORD-1002 received, status paid
+...
+INFO ... filter-and-multicast.camel.yaml:16 : Order ORD-1003 received, status pending
+```
+
+## Install Camel CLI
+
+Install [JBang](https://www.jbang.dev/download/) and the Camel CLI as described in the
+[root README](../../README.md#install-the-camel-cli); `camel --version` confirms the install.
+
+## Run it
+
+```shell
+camel run *
+```
+
+The three orders in `orders/` are read once each (`noop: true` leaves the files in place), so the run
+can be repeated.
+
+## How it works
+
+- `filter` with `${body[status]} == 'paid'` lets paid orders into its steps; the pending order is logged as
+ received and goes no further, which is the difference from `choice`: a filter has no otherwise.
+- `multicast` sends a copy of the message to each `to:` in turn, `direct:warehouse` then `direct:invoicing`.
+- The two `direct` routes are the two departments; each logs its part of the work.
+
+## Build it step by step
+
+1. A `file` route on `orders` that unmarshals each order and logs its id and status.
+2. Add a `filter` on the status and log inside it; the pending order disappears from the inner log.
+3. Add `direct:warehouse` and `direct:invoicing` routes and a `multicast` to both inside the filter.
+4. Add `parallelProcessing: true` to the multicast and see both departments log at the same moment.
+
+## Try changing
+
+- Add a third department, `direct:analytics`, that logs the order total.
+- Use `recipientList` with a header listing the departments instead of a fixed `multicast`.
+
+## Integration testing
+
+The example comes with a test in the [Citrus](https://citrusframework.org/) YAML DSL,
+`test/filter-and-multicast.citrus.it.yaml`, which the Camel CLI runs:
+
+```shell
+camel test run test/filter-and-multicast.citrus.it.yaml
+```
+
+The test starts the route and verifies that the warehouse and invoicing logs appear for the paid orders.
+
+## Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/filter-and-multicast/application.properties b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/filter-and-multicast/application.properties
new file mode 100644
index 000000000000..13a83393a912
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/filter-and-multicast/application.properties
@@ -0,0 +1,16 @@
+# 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.
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/filter-and-multicast/filter-and-multicast.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/filter-and-multicast/filter-and-multicast.camel.yaml
new file mode 100644
index 000000000000..1559e2bb1333
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/filter-and-multicast/filter-and-multicast.camel.yaml
@@ -0,0 +1,50 @@
+# Only paid orders go further, and a paid order goes to two places at once: the warehouse to pick
+# it and invoicing to bill it. The filter drops the unpaid order, the multicast sends the same
+# message to both.
+- route:
+ id: filter-and-multicast
+ from:
+ uri: file
+ parameters:
+ directoryName: orders
+ noop: true
+ sortBy: "file:name"
+ steps:
+ - unmarshal:
+ json:
+ library: Jackson
+ - log:
+ message: "Order ${body[orderId]} received, status ${body[status]}"
+ - filter:
+ expression:
+ simple:
+ expression: "${body[status]} == 'paid'"
+ steps:
+ - multicast:
+ steps:
+ - to:
+ uri: direct
+ parameters:
+ name: warehouse
+ - to:
+ uri: direct
+ parameters:
+ name: invoicing
+- route:
+ id: warehouse
+ from:
+ uri: direct
+ parameters:
+ name: warehouse
+ steps:
+ - log:
+ message: "Warehouse: pick ${body[lines].size()} line(s) for ${body[orderId]}"
+- route:
+ id: invoicing
+ from:
+ uri: direct
+ parameters:
+ name: invoicing
+ steps:
+ - log:
+ message: "Invoicing: bill customer ${body[customer]} for ${body[orderId]}"
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/filter-and-multicast/orders/order-1001.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/filter-and-multicast/orders/order-1001.json
new file mode 100644
index 000000000000..3eb5f038d636
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/filter-and-multicast/orders/order-1001.json
@@ -0,0 +1,2 @@
+{"orderId": "ORD-1001", "customer": "C-482", "country": "DK", "status": "paid",
+ "lines": [{"sku": "CAMEL-TSHIRT", "qty": 2, "price": 19.95}, {"sku": "CAMEL-MUG", "qty": 1, "price": 9.50}]}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/filter-and-multicast/orders/order-1002.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/filter-and-multicast/orders/order-1002.json
new file mode 100644
index 000000000000..bb7aa0e9ff1c
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/filter-and-multicast/orders/order-1002.json
@@ -0,0 +1,2 @@
+{"orderId": "ORD-1002", "customer": "C-207", "country": "DE", "status": "paid",
+ "lines": [{"sku": "CAMEL-MUG", "qty": 3, "price": 9.50}]}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/filter-and-multicast/orders/order-1003.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/filter-and-multicast/orders/order-1003.json
new file mode 100644
index 000000000000..9edab768cbd7
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/filter-and-multicast/orders/order-1003.json
@@ -0,0 +1,2 @@
+{"orderId": "ORD-1003", "customer": "C-134", "country": "US", "status": "pending",
+ "lines": [{"sku": "CAMEL-TSHIRT", "qty": 1, "price": 19.95}, {"sku": "CAMEL-CAP", "qty": 1, "price": 14.00}, {"sku": "CAMEL-MUG", "qty": 2, "price": 9.50}]}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/order-lines/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/order-lines/README.md
new file mode 100644
index 000000000000..2659d8d2ebd3
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/order-lines/README.md
@@ -0,0 +1,72 @@
+# Order lines
+
+An order is one message with several lines; the warehouse picks one line at a time. The splitter turns the order
+into one message per line, and the order id travels along in a header.
+
+## What you will see
+
+```text
+INFO ... order-lines.camel.yaml:20 : Order ORD-1001 with 2 line(s)
+INFO ... order-lines.camel.yaml:27 : pick 2 x CAMEL-TSHIRT for ORD-1001
+INFO ... order-lines.camel.yaml:27 : pick 1 x CAMEL-MUG for ORD-1001
+INFO ... order-lines.camel.yaml:29 : Order ORD-1001: all 2 line(s) sent to picking
+INFO ... order-lines.camel.yaml:20 : Order ORD-1002 with 1 line(s)
+...
+```
+
+## Install Camel CLI
+
+Install [JBang](https://www.jbang.dev/download/) and the Camel CLI as described in the
+[root README](../../README.md#install-the-camel-cli); `camel --version` confirms the install.
+
+## Run it
+
+```shell
+camel run *
+```
+
+The three orders in `orders/` are read once each (`noop: true` leaves the files in place), so the run
+can be repeated.
+
+## How it works
+
+- The order is parsed with `unmarshal` and `json`, so `${body[lines]}` is a list and `${body[lines].size()}` its
+ length.
+- `setHeader` keeps the order id before the split: headers are copied to every split message, so each pick line
+ knows its order.
+- `split` with `${body[lines]}` runs its steps once per line with that line as the body; `CamelSplitIndex` and
+ `CamelSplitSize` are available inside if a count is needed.
+- After the split the route continues with the original order, which logs the confirmation.
+
+## Build it step by step
+
+1. A `file` route on `orders` that unmarshals each order and logs the number of lines.
+2. Add `split` over `${body[lines]}` and log `${body[sku]}` inside it.
+3. Keep the order id in a header before the split and use it in the pick line.
+4. Log the confirmation after the split and notice the body is the whole order again.
+
+## Try changing
+
+- Send each line to a `direct:pick` route instead of logging; the `aggregator` example collects them again.
+- Split in parallel with `parallelProcessing: true` and watch the pick lines interleave.
+
+## Integration testing
+
+The example comes with a test in the [Citrus](https://citrusframework.org/) YAML DSL,
+`test/order-lines.citrus.it.yaml`, which the Camel CLI runs:
+
+```shell
+camel test run test/order-lines.citrus.it.yaml
+```
+
+The test starts the route and verifies the pick lines and the confirmation.
+
+## Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/order-lines/application.properties b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/order-lines/application.properties
new file mode 100644
index 000000000000..13a83393a912
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/order-lines/application.properties
@@ -0,0 +1,16 @@
+# 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.
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/order-lines/order-lines.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/order-lines/order-lines.camel.yaml
new file mode 100644
index 000000000000..9f8c2ad5c3d5
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/order-lines/order-lines.camel.yaml
@@ -0,0 +1,30 @@
+# An order is one message with several lines; the warehouse picks one line at a time. The splitter
+# turns the order into one message per line, and the split messages keep the order id in a header.
+- route:
+ id: order-lines
+ from:
+ uri: file
+ parameters:
+ directoryName: orders
+ noop: true
+ sortBy: "file:name"
+ steps:
+ - unmarshal:
+ json:
+ library: Jackson
+ - setHeader:
+ name: orderId
+ expression:
+ simple:
+ expression: "${body[orderId]}"
+ - log:
+ message: "Order ${header.orderId} with ${body[lines].size()} line(s)"
+ - split:
+ expression:
+ simple:
+ expression: "${body[lines]}"
+ steps:
+ - log:
+ message: " pick ${body[qty]} x ${body[sku]} for ${header.orderId}"
+ - log:
+ message: "Order ${header.orderId}: all ${body[lines].size()} line(s) sent to picking"
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/order-lines/orders/order-1001.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/order-lines/orders/order-1001.json
new file mode 100644
index 000000000000..3eb5f038d636
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/order-lines/orders/order-1001.json
@@ -0,0 +1,2 @@
+{"orderId": "ORD-1001", "customer": "C-482", "country": "DK", "status": "paid",
+ "lines": [{"sku": "CAMEL-TSHIRT", "qty": 2, "price": 19.95}, {"sku": "CAMEL-MUG", "qty": 1, "price": 9.50}]}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/order-lines/orders/order-1002.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/order-lines/orders/order-1002.json
new file mode 100644
index 000000000000..bb7aa0e9ff1c
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/order-lines/orders/order-1002.json
@@ -0,0 +1,2 @@
+{"orderId": "ORD-1002", "customer": "C-207", "country": "DE", "status": "paid",
+ "lines": [{"sku": "CAMEL-MUG", "qty": 3, "price": 9.50}]}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/order-lines/orders/order-1003.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/order-lines/orders/order-1003.json
new file mode 100644
index 000000000000..9edab768cbd7
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/order-lines/orders/order-1003.json
@@ -0,0 +1,2 @@
+{"orderId": "ORD-1003", "customer": "C-134", "country": "US", "status": "pending",
+ "lines": [{"sku": "CAMEL-TSHIRT", "qty": 1, "price": 19.95}, {"sku": "CAMEL-CAP", "qty": 1, "price": 14.00}, {"sku": "CAMEL-MUG", "qty": 2, "price": 9.50}]}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/nightly-report/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/nightly-report/README.md
new file mode 100644
index 000000000000..5b0266512e7e
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/nightly-report/README.md
@@ -0,0 +1,64 @@
+# Nightly report
+
+The shop's inventory report on a schedule. A cron expression triggers the route instead of a timer: every
+ten seconds in the demo, nightly at 02:00 with a one-line change in `application.properties`.
+
+## What you will see
+
+```text
+INFO ... nightly-report.camel.yaml:15 : Inventory report 2026-09-18 15:04:10: 120 T-shirts and 45 mugs in stock
+INFO ... nightly-report.camel.yaml:15 : Inventory report 2026-09-18 15:04:20: 120 T-shirts and 45 mugs in stock
+```
+
+## Install Camel CLI
+
+Install [JBang](https://www.jbang.dev/download/) and the Camel CLI as described in the
+[root README](../../README.md#install-the-camel-cli); `camel --version` confirms the install.
+
+## Run it
+
+```shell
+camel run *
+```
+
+## How it works
+
+- The `cron` component fires on the schedule `report.schedule`, a cron expression with seconds first.
+- `setBody` builds the report line with the simple language: the current time with `${date:now:...}`
+ and the stock counts from properties.
+- The Camel CLI adds the scheduler (camel-quartz) the cron component needs on its own.
+
+## Build it step by step
+
+1. Start from a timer route that logs "Inventory report" every ten seconds.
+2. Replace the timer with `cron` and the schedule `0/10 * * * * ?`; the log keeps coming every ten seconds.
+3. Add the current time to the message with `${date:now:yyyy-MM-dd HH:mm:ss}`.
+4. Move the schedule and the stock counts to `application.properties` and use them as `{{...}}`.
+5. Change the schedule to `0 0 2 * * ?` and read it back: at two in the morning, every night.
+
+## Try changing
+
+- `0 * * * * ?` runs at the top of every minute.
+- Log the report at `WARN` level when a count drops below ten: the `content-based-router` example shows
+ the `choice` you need.
+
+## Integration testing
+
+The example comes with a test in the [Citrus](https://citrusframework.org/) YAML DSL,
+`test/nightly-report.citrus.it.yaml`, which the Camel CLI runs:
+
+```shell
+camel test run test/nightly-report.citrus.it.yaml
+```
+
+The test starts the route and verifies the logged line.
+
+## Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/nightly-report/application.properties b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/nightly-report/application.properties
new file mode 100644
index 000000000000..b2f7fada5bd2
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/nightly-report/application.properties
@@ -0,0 +1,24 @@
+# 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.
+
+# When the report runs, as a cron expression (seconds minutes hours day month weekday).
+# Every ten seconds for the demo; a nightly run at 02:00 is: report.schedule=0 0 2 * * ?
+report.schedule=0/10 * * * * ?
+
+# The stock the report counts
+inventory.tshirt=120
+inventory.mug=45
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/nightly-report/nightly-report.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/nightly-report/nightly-report.camel.yaml
new file mode 100644
index 000000000000..73f0cfb27d8e
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/nightly-report/nightly-report.camel.yaml
@@ -0,0 +1,16 @@
+# The shop's inventory report on a schedule. A cron expression instead of a timer: the demo schedule
+# runs every ten seconds so there is something to see; a real nightly run is a one-line change.
+- route:
+ id: nightly-report
+ from:
+ uri: cron
+ parameters:
+ name: report
+ schedule: "{{report.schedule}}"
+ steps:
+ - setBody:
+ expression:
+ simple:
+ expression: "Inventory report ${date:now:yyyy-MM-dd HH:mm:ss}: {{inventory.tshirt}} T-shirts and {{inventory.mug}} mugs in stock"
+ - log:
+ message: "${body}"
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/order-generator/OrderNumber.java b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/order-generator/OrderNumber.java
new file mode 100644
index 000000000000..e1e963c051d6
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/order-generator/OrderNumber.java
@@ -0,0 +1,20 @@
+package camel.example;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Hands out the shop's order numbers, ORD-1001, ORD-1002, ... A plain Java class next to the route, declared as a
+ * bean in beans.yaml and called from the route with a method expression.
+ */
+public class OrderNumber {
+
+ private final AtomicInteger counter = new AtomicInteger();
+
+ public void setStart(int start) {
+ counter.set(start - 1);
+ }
+
+ public String next() {
+ return "ORD-" + counter.incrementAndGet();
+ }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/order-generator/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/order-generator/README.md
new file mode 100644
index 000000000000..18fc09f74ec8
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/order-generator/README.md
@@ -0,0 +1,74 @@
+# Order generator
+
+The shop's order feed. A timer creates an order every five seconds, a Java bean hands out the order
+number, and the order is logged as JSON. Every later example on the ladder starts from this file and
+this order shape.
+
+## What you will see
+
+```text
+INFO ... order-generator.camel.yaml:23 : New order ORD-1001: {"orderId": "ORD-1001", "customer": "C-482", "country": "DK", "lines": [{"sku": "CAMEL-TSHIRT", "qty": 2, "price": 19.95}], "status": "paid"}
+INFO ... order-generator.camel.yaml:23 : New order ORD-1002: {"orderId": "ORD-1002", "customer": "C-207", "country": "DK", "lines": [{"sku": "CAMEL-TSHIRT", "qty": 1, "price": 19.95}], "status": "paid"}
+```
+
+## Install Camel CLI
+
+Install [JBang](https://www.jbang.dev/download/) and the Camel CLI as described in the
+[root README](../../README.md#install-the-camel-cli); `camel --version` confirms the install.
+
+## Run it
+
+```shell
+camel run *
+```
+
+Stop it with `ctrl` + `c`, or from another terminal with `camel stop order-generator`.
+
+## How it works
+
+- `order-generator.camel.yaml` is the route: a `timer` fires every `order.period` milliseconds, a
+ `setHeader` asks the `orderNumber` bean for the next number with a method expression, a `setBody`
+ builds the order as JSON with the simple language, and a `log` prints it.
+- `OrderNumber.java` is a plain Java class next to the route. `beans.yaml` declares it as the bean
+ `orderNumber` and sets its first number from a property.
+- `application.properties` holds the period and the first order number; change them without touching
+ the route.
+
+## Build it step by step
+
+Ask your assistant, or type it yourself, one step at a time, and run after each:
+
+1. A timer route that logs "New order" every five seconds.
+2. Set a header `orderId` to a constant `ORD-1001` and log "New order ${header.orderId}".
+3. Add a Java class `OrderNumber` with a method `next()` that returns `ORD-` and a counter, declare it
+ in `beans.yaml`, and set the header from the bean's method instead of the constant.
+4. Set the body to a JSON order with the order id, a random customer and one line, and log the body.
+5. Move the period and the first order number to `application.properties`.
+
+## Try changing
+
+- `order.period=1000` for an order every second.
+- Add a second line to the order, or pick the sku at random from two products.
+- Log only the order id and the number of lines: `${header.orderId} has ${body.lines.size()} line(s)`
+ needs the body as an object; the `json-transform` example on the next rung shows how.
+
+## Integration testing
+
+The example comes with a test in the [Citrus](https://citrusframework.org/) YAML DSL,
+`test/order-generator.citrus.it.yaml`, which the Camel CLI runs:
+
+```shell
+camel test run test/order-generator.citrus.it.yaml
+```
+
+The test starts the route and verifies the logged line.
+
+## Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/order-generator/application.properties b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/order-generator/application.properties
new file mode 100644
index 000000000000..bf07ed7c84f1
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/order-generator/application.properties
@@ -0,0 +1,21 @@
+# 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.
+
+# How often a new order is created, in milliseconds
+order.period=5000
+# The first order number
+order.first-number=1001
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/order-generator/beans.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/order-generator/beans.yaml
new file mode 100644
index 000000000000..444deab17be6
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/order-generator/beans.yaml
@@ -0,0 +1,5 @@
+- beans:
+ - name: orderNumber
+ type: "camel.example.OrderNumber"
+ properties:
+ start: "{{order.first-number}}"
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/order-generator/order-generator.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/order-generator/order-generator.camel.yaml
new file mode 100644
index 000000000000..514f42b966b8
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/order-generator/order-generator.camel.yaml
@@ -0,0 +1,24 @@
+# The shop's order feed: a timer creates an order every few seconds. Every later example on the
+# ladder starts from this file and this order shape.
+- route:
+ id: order-generator
+ from:
+ uri: timer
+ parameters:
+ timerName: orders
+ period: "{{order.period}}"
+ steps:
+ - setHeader:
+ name: orderId
+ expression:
+ method:
+ ref: orderNumber
+ method: next
+ - setBody:
+ expression:
+ simple:
+ expression: |-
+ {"orderId": "${header.orderId}", "customer": "C-${random(100,999)}", "country": "DK",
+ "lines": [{"sku": "CAMEL-TSHIRT", "qty": ${random(1,3)}, "price": 19.95}], "status": "paid"}
+ - log:
+ message: "New order ${header.orderId}: ${body}"
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/properties-and-profiles/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/properties-and-profiles/README.md
new file mode 100644
index 000000000000..a5461677e08d
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/properties-and-profiles/README.md
@@ -0,0 +1,74 @@
+# Properties and profiles
+
+One route, two configurations. `application.properties` is what `camel run` uses; `application-prod.properties`
+holds the values that differ in production and is chosen with `--profile=prod`.
+
+## What you will see
+
+```text
+camel run *
+INFO ... properties-and-profiles.camel.yaml:15 : Welcome to Camel Shop (development), prices in EUR
+
+camel run * --profile=prod
+INFO ... properties-and-profiles.camel.yaml:15 : Welcome to Camel Shop, prices in USD
+```
+
+## Install Camel CLI
+
+Install [JBang](https://www.jbang.dev/download/) and the Camel CLI as described in the
+[root README](../../README.md#install-the-camel-cli); `camel --version` confirms the install.
+
+## Run it
+
+```shell
+camel run *
+```
+
+and then the production profile:
+
+```shell
+camel run * --profile=prod
+```
+
+## How it works
+
+- `{{shop.name}}` and `{{shop.currency}}` in the route are property placeholders; Camel resolves them
+ from `application.properties` at startup.
+- With `--profile=prod` Camel also reads `application-prod.properties`, and a value there wins over the
+ same key in `application.properties`. Only the differing values need to be in the profile file.
+- `welcome.period` is only in `application.properties`, so both profiles share it.
+
+## Build it step by step
+
+1. A timer route that logs "Welcome to Camel Shop".
+2. Move the shop name to `application.properties` as `shop.name` and use `{{shop.name}}` in the message.
+3. Add `shop.currency` the same way and mention it in the message.
+4. Create `application-prod.properties` with a different name and currency, run with `--profile=prod`,
+ and see the message change.
+5. Move the timer period to a property that only `application.properties` has.
+
+## Try changing
+
+- Override a single value from the command line: `camel run * --prop=shop.currency=DKK`.
+- Add an `application-test.properties` and run `--profile=test`.
+
+## Integration testing
+
+The example comes with a test in the [Citrus](https://citrusframework.org/) YAML DSL,
+`test/properties-and-profiles.citrus.it.yaml`, which the Camel CLI runs:
+
+```shell
+camel test run test/properties-and-profiles.citrus.it.yaml
+```
+
+The test starts the route and verifies the logged line.
+
+## Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/properties-and-profiles/application-prod.properties b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/properties-and-profiles/application-prod.properties
new file mode 100644
index 000000000000..33d3d1cdeb86
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/properties-and-profiles/application-prod.properties
@@ -0,0 +1,21 @@
+# 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.
+
+# The production configuration, used with camel run --profile=prod.
+# Only the values that differ are needed; the rest come from application.properties.
+shop.name=Camel Shop
+shop.currency=USD
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/properties-and-profiles/application.properties b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/properties-and-profiles/application.properties
new file mode 100644
index 000000000000..050a9e5f3e88
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/properties-and-profiles/application.properties
@@ -0,0 +1,21 @@
+# 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.
+
+# The default configuration, used with plain camel run
+shop.name=Camel Shop (development)
+shop.currency=EUR
+welcome.period=3000
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/properties-and-profiles/properties-and-profiles.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/properties-and-profiles/properties-and-profiles.camel.yaml
new file mode 100644
index 000000000000..6882e660a65d
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/properties-and-profiles/properties-and-profiles.camel.yaml
@@ -0,0 +1,16 @@
+# The same route with two configurations: application.properties for development and
+# application-prod.properties for production, chosen with --profile=prod.
+- route:
+ id: welcome
+ from:
+ uri: timer
+ parameters:
+ timerName: welcome
+ period: "{{welcome.period}}"
+ steps:
+ - setBody:
+ expression:
+ simple:
+ expression: "Welcome to {{shop.name}}, prices in {{shop.currency}}"
+ - log:
+ message: "${body}"
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/csv-to-json/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/csv-to-json/README.md
new file mode 100644
index 000000000000..8ed95b6f7301
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/csv-to-json/README.md
@@ -0,0 +1,69 @@
+# CSV to JSON
+
+The accountant drops a CSV of invoices in the inbox. Each line becomes one JSON invoice, logged and written to
+the outbox as its own file.
+
+## What you will see
+
+```text
+INFO ... csv-to-json.camel.yaml:29 : Invoice INV-2001: {"invoiceId":"INV-2001","orderId":"ORD-1001","customer":"C-482","amount":"49.40","currency":"EUR","dueDate":"2026-10-01"}
+INFO ... csv-to-json.camel.yaml:29 : Invoice INV-2002: {"invoiceId":"INV-2002","orderId":"ORD-1002",...}
+INFO ... csv-to-json.camel.yaml:29 : Invoice INV-2003: {"invoiceId":"INV-2003","orderId":"ORD-1003",...}
+
+$ ls outbox
+INV-2001.json INV-2002.json INV-2003.json
+```
+
+## Install Camel CLI
+
+Install [JBang](https://www.jbang.dev/download/) and the Camel CLI as described in the
+[root README](../../README.md#install-the-camel-cli); `camel --version` confirms the install.
+
+## Run it
+
+```shell
+camel run *
+```
+
+## How it works
+
+- `unmarshal` with the `csv` data format reads `inbox/invoices.csv`; `captureHeaderRecord` takes the first line
+ as the field names and `useMaps` makes each following line a map.
+- `split` turns the list of maps into one message per invoice.
+- Inside the split, the invoice id is kept in a header, `marshal` with `json` writes the map as JSON, and the
+ `file` producer names the output file after the invoice.
+- Values are strings, as in the CSV; the `data-mapping` example shows how to compute and type fields.
+
+## Build it step by step
+
+1. A `file` route on `inbox` that logs the CSV as text.
+2. Add `unmarshal` with `csv` and log the body: a list of maps.
+3. Add `split` over `${body}` and log each invoice.
+4. Add `marshal` with `json` inside the split.
+5. Write each invoice to `outbox`, named after its id.
+
+## Try changing
+
+- Aggregate the amounts per customer before writing: the `aggregator` example shows the EIP.
+- Delete or move the CSV after reading by dropping `noop: true` and adding `move: done`.
+
+## Integration testing
+
+The example comes with a test in the [Citrus](https://citrusframework.org/) YAML DSL,
+`test/csv-to-json.citrus.it.yaml`, which the Camel CLI runs:
+
+```shell
+camel test run test/csv-to-json.citrus.it.yaml
+```
+
+The test starts the route and verifies that the three invoices are logged as JSON.
+
+## Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/csv-to-json/application.properties b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/csv-to-json/application.properties
new file mode 100644
index 000000000000..13a83393a912
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/csv-to-json/application.properties
@@ -0,0 +1,16 @@
+# 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.
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/csv-to-json/csv-to-json.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/csv-to-json/csv-to-json.camel.yaml
new file mode 100644
index 000000000000..4fdf03dba037
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/csv-to-json/csv-to-json.camel.yaml
@@ -0,0 +1,35 @@
+# The accountant drops a CSV of invoices in the inbox; each line becomes one JSON invoice, logged and
+# written to the outbox as its own file.
+- route:
+ id: csv-to-json
+ from:
+ uri: file
+ parameters:
+ directoryName: inbox
+ noop: true
+ include: ".*\\.csv"
+ steps:
+ - unmarshal:
+ csv:
+ useMaps: true
+ captureHeaderRecord: true
+ - split:
+ expression:
+ simple:
+ expression: "${body}"
+ steps:
+ - setHeader:
+ name: invoiceId
+ expression:
+ simple:
+ expression: "${body[invoiceId]}"
+ - marshal:
+ json:
+ library: Jackson
+ - log:
+ message: "Invoice ${header.invoiceId}: ${body}"
+ - to:
+ uri: file
+ parameters:
+ directoryName: outbox
+ fileName: "${header.invoiceId}.json"
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/csv-to-json/inbox/invoices.csv b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/csv-to-json/inbox/invoices.csv
new file mode 100644
index 000000000000..8a2891332396
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/csv-to-json/inbox/invoices.csv
@@ -0,0 +1,4 @@
+invoiceId,orderId,customer,amount,currency,dueDate
+INV-2001,ORD-1001,C-482,49.40,EUR,2026-10-01
+INV-2002,ORD-1002,C-207,19.95,EUR,2026-10-01
+INV-2003,ORD-1003,C-134,59.85,EUR,2026-10-02
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/data-mapping/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/data-mapping/README.md
new file mode 100644
index 000000000000..7ed0376e65b9
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/data-mapping/README.md
@@ -0,0 +1,65 @@
+# Data mapping
+
+The case every integration has: the shop's order mapped field by field to the courier's shipment format, which has
+different names, a different structure, and values the order does not carry. The mapping is a Groovy script.
+
+## What you will see
+
+```text
+INFO ... data-mapping.camel.yaml:27 : Shipment for the courier: {"shipmentRef":"SHIP-1001","recipient":{"customerNo":"C-482","countryCode":"DK"},"parcels":[{"article":"CAMEL-TSHIRT","pieces":2},{"article":"CAMEL-MUG","pieces":1}],"totalPieces":3,"service":"domestic","createdAt":"2026-09-18"}
+```
+
+## Install Camel CLI
+
+Install [JBang](https://www.jbang.dev/download/) and the Camel CLI as described in the
+[root README](../../README.md#install-the-camel-cli); `camel --version` confirms the install.
+
+## Run it
+
+```shell
+camel run *
+```
+
+## How it works
+
+- The order in `order.json` is parsed with `unmarshal` and the `json` data format, so the script works on a map,
+ not on text.
+- `setBody` with a `groovy` expression runs `shipment-mapping.groovy`: `resource:file:` loads the script from the
+ file next to the route. The script's last expression, a map with the courier's field names, becomes the body.
+- `marshal` with `json` writes the shipment out. Renamed fields, a nested `recipient`, one `parcels` entry per
+ line, a computed `totalPieces`, a `service` chosen from the country, and a `createdAt` the order never had.
+
+## Build it step by step
+
+1. A one-shot timer route that sets the body from `order.json`, unmarshals it with `json`, and logs
+ `${body[orderId]}`.
+2. Add a `setBody` with an inline `groovy` expression returning `[shipmentRef: body.orderId]` and log the body.
+3. Move the expression into `shipment-mapping.groovy` and load it with `resource:file:`.
+4. Grow the map: the recipient, the parcels with `collect`, the total with `sum`.
+5. Add `marshal` with `json` and log the shipment.
+
+## Try changing
+
+- Map the country to the courier's service codes with a Groovy map: `[DK: 'DOM', SE: 'NORDIC'][order.country]`.
+- Do the same mapping with a Java bean instead of Groovy; the `routes` quick-start shows a bean.
+
+## Integration testing
+
+The example comes with a test in the [Citrus](https://citrusframework.org/) YAML DSL,
+`test/data-mapping.citrus.it.yaml`, which the Camel CLI runs:
+
+```shell
+camel test run test/data-mapping.citrus.it.yaml
+```
+
+The test starts the route and verifies that the shipment is logged with the courier's fields.
+
+## Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/data-mapping/application.properties b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/data-mapping/application.properties
new file mode 100644
index 000000000000..13a83393a912
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/data-mapping/application.properties
@@ -0,0 +1,16 @@
+# 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.
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/data-mapping/data-mapping.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/data-mapping/data-mapping.camel.yaml
new file mode 100644
index 000000000000..57deb54c5dab
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/data-mapping/data-mapping.camel.yaml
@@ -0,0 +1,28 @@
+# The case every integration has: the shop's order mapped field by field to the courier's shipment
+# format, which has different names, a different structure, and values the order does not carry.
+# The order is parsed to a map, the Groovy script shipment-mapping.groovy builds the shipment map,
+# and it is written out as JSON.
+- route:
+ id: data-mapping
+ from:
+ uri: timer
+ parameters:
+ timerName: order
+ repeatCount: 1
+ steps:
+ - setBody:
+ expression:
+ constant:
+ expression: "resource:file:order.json"
+ - unmarshal:
+ json:
+ library: Jackson
+ - setBody:
+ expression:
+ groovy:
+ expression: "resource:file:shipment-mapping.groovy"
+ - marshal:
+ json:
+ library: Jackson
+ - log:
+ message: "Shipment for the courier: ${body}"
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/data-mapping/order.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/data-mapping/order.json
new file mode 100644
index 000000000000..2327db569ca5
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/data-mapping/order.json
@@ -0,0 +1,3 @@
+{"orderId": "ORD-1001", "customer": "C-482", "country": "DK",
+ "lines": [{"sku": "CAMEL-TSHIRT", "qty": 2, "price": 19.95}, {"sku": "CAMEL-MUG", "qty": 1, "price": 9.50}],
+ "status": "paid"}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/data-mapping/shipment-mapping.groovy b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/data-mapping/shipment-mapping.groovy
new file mode 100644
index 000000000000..679609aebd42
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/data-mapping/shipment-mapping.groovy
@@ -0,0 +1,12 @@
+// Maps the shop's order (the message body, parsed from JSON into a map) to the courier's shipment
+// format. Runs as a Groovy script from the route: the last expression is the new message body.
+def order = body
+
+[
+ shipmentRef : "SHIP-" + order.orderId.replace("ORD-", ""),
+ recipient : [customerNo: order.customer, countryCode: order.country],
+ parcels : order.lines.collect { line -> [article: line.sku, pieces: line.qty] },
+ totalPieces : order.lines.sum { line -> line.qty },
+ service : order.country == "DK" ? "domestic" : "international",
+ createdAt : java.time.LocalDate.now().toString()
+]
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/groovy/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/groovy/README.md
new file mode 100644
index 000000000000..5a4909701348
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/groovy/README.md
@@ -0,0 +1,67 @@
+# Groovy
+
+Two orders come in, one with a valid customer email and one with a bad one. A Groovy expression checks the address
+with Apache Commons Validator, a third-party library the Camel CLI downloads because `application.properties`
+declares it.
+
+## What you will see
+
+```text
+INFO ... groovy.camel.yaml:28 : Order ORD-1001 accepted: anna@example.com is a valid address
+INFO ... groovy.camel.yaml:32 : Order ORD-1002 rejected: not-an-address is not an email address
+```
+
+## Install Camel CLI
+
+Install [JBang](https://www.jbang.dev/download/) and the Camel CLI as described in the
+[root README](../../README.md#install-the-camel-cli); `camel --version` confirms the install.
+
+## Run it
+
+```shell
+camel run *
+```
+
+## How it works
+
+- The timer fires twice with `includeMetadata: true`, so `CamelTimerCounter` tells the first order from the
+ second; a simple expression picks `order.json` or `order-bad-email.json` from it.
+- `unmarshal` with `json` parses the order, so the Groovy expression reads `body.email` on a map.
+- `choice` with a `groovy` expression: `EmailValidator.getInstance().isValid(body.email)` from Commons Validator.
+- `camel.jbang.dependencies=commons-validator:commons-validator:1.10.1` in `application.properties` is how a
+ route declares a library that is not a Camel component; the Camel CLI downloads it.
+
+## Build it step by step
+
+1. A one-shot timer route that sets the body from `order.json`, unmarshals it, and logs `${body[email]}`.
+2. Add a `choice` with a `groovy` expression `body.email.contains('@')` and log accepted or rejected.
+3. Replace the check with Commons Validator's `EmailValidator` and declare the dependency in
+ `application.properties`.
+4. Let the timer fire twice with `includeMetadata: true` and pick the second file for the second order.
+
+## Try changing
+
+- Validate the country code too: `body.country in ['DK', 'SE', 'NO']`.
+- Put the check in a script file, `validate-order.groovy`, and load it with `resource:file:`, as the
+ `data-mapping` example does.
+
+## Integration testing
+
+The example comes with a test in the [Citrus](https://citrusframework.org/) YAML DSL,
+`test/groovy.citrus.it.yaml`, which the Camel CLI runs:
+
+```shell
+camel test run test/groovy.citrus.it.yaml
+```
+
+The test starts the route and verifies that one order is accepted and one rejected.
+
+## Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/groovy/application.properties b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/groovy/application.properties
new file mode 100644
index 000000000000..6fda14e7eb16
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/groovy/application.properties
@@ -0,0 +1,19 @@
+# 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.
+
+# A third-party library the Groovy expression uses; the Camel CLI downloads it
+camel.jbang.dependencies=commons-validator:commons-validator:1.10.1
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/groovy/groovy.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/groovy/groovy.camel.yaml
new file mode 100644
index 000000000000..097bffc71d4b
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/groovy/groovy.camel.yaml
@@ -0,0 +1,33 @@
+# Two orders, one with a valid customer email and one without. A Groovy expression checks the
+# address with Apache Commons Validator, a third-party library the Camel CLI downloads because it
+# is declared in application.properties.
+- route:
+ id: groovy
+ from:
+ uri: timer
+ parameters:
+ timerName: orders
+ repeatCount: 2
+ includeMetadata: true
+ steps:
+ - setBody:
+ expression:
+ simple:
+ expression: "resource:file:${exchangeProperty.CamelTimerCounter == 1 ? 'order.json' : 'order-bad-email.json'}"
+ - unmarshal:
+ json:
+ library: Jackson
+ - choice:
+ when:
+ - expression:
+ groovy:
+ expression: |-
+ import org.apache.commons.validator.routines.EmailValidator
+ EmailValidator.getInstance().isValid(body.email)
+ steps:
+ - log:
+ message: "Order ${body[orderId]} accepted: ${body[email]} is a valid address"
+ otherwise:
+ steps:
+ - log:
+ message: "Order ${body[orderId]} rejected: ${body[email]} is not an email address"
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/groovy/order-bad-email.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/groovy/order-bad-email.json
new file mode 100644
index 000000000000..687988af1d62
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/groovy/order-bad-email.json
@@ -0,0 +1,2 @@
+{"orderId": "ORD-1002", "customer": "C-207", "email": "not-an-address", "country": "DK",
+ "lines": [{"sku": "CAMEL-MUG", "qty": 1, "price": 9.50}], "status": "paid"}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/groovy/order.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/groovy/order.json
new file mode 100644
index 000000000000..1125f80eb919
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/groovy/order.json
@@ -0,0 +1,2 @@
+{"orderId": "ORD-1001", "customer": "C-482", "email": "anna@example.com", "country": "DK",
+ "lines": [{"sku": "CAMEL-TSHIRT", "qty": 2, "price": 19.95}], "status": "paid"}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/json-transform/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/json-transform/README.md
new file mode 100644
index 000000000000..f7e3d2cc2546
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/json-transform/README.md
@@ -0,0 +1,68 @@
+# JSON transform
+
+The shop's order reshaped for the warehouse. jsonpath reads single values out of the order, jq builds the pick
+list the warehouse wants: only the sku and the quantity of each line.
+
+## What you will see
+
+```text
+INFO ... json-transform.camel.yaml:25 : Order ORD-1001 with 2 lines: {"orderId": "ORD-1001", "customer": "C-482", ...}
+INFO ... json-transform.camel.yaml:32 : Pick list for the warehouse: {
+ "order" : "ORD-1001",
+ "country" : "DK",
+ "pick" : [ { "sku" : "CAMEL-TSHIRT", "qty" : 2 }, { "sku" : "CAMEL-MUG", "qty" : 1 } ]
+}
+```
+
+## Install Camel CLI
+
+Install [JBang](https://www.jbang.dev/download/) and the Camel CLI as described in the
+[root README](../../README.md#install-the-camel-cli); `camel --version` confirms the install.
+
+## Run it
+
+```shell
+camel run *
+```
+
+## How it works
+
+- The order comes from `order.json`; `resource:file:` in a constant expression loads a file as the body.
+- `jsonpath` expressions pick the order id and the number of lines into headers.
+- `transform` with a `jq` expression builds a new document from the old one: `.orderId` becomes `order`, and
+ `[.lines[] | {sku, qty}]` keeps two fields of every line.
+- The Camel CLI adds the jq and jsonpath languages on its own.
+
+## Build it step by step
+
+1. A one-shot timer route that sets the body from `order.json` and logs it.
+2. Read the order id into a header with `jsonpath: $.orderId` and log it.
+3. Add the number of lines with `jsonpath: $.lines.length()`.
+4. Add a `transform` step with a jq expression that keeps only the order id, and log the result.
+5. Extend the jq expression with the country and the pick list of sku and qty per line.
+
+## Try changing
+
+- Add the total quantity: `total: ([.lines[].qty] | add)` in the jq expression.
+- Filter lines with more than one piece: `[.lines[] | select(.qty > 1)]`.
+
+## Integration testing
+
+The example comes with a test in the [Citrus](https://citrusframework.org/) YAML DSL,
+`test/json-transform.citrus.it.yaml`, which the Camel CLI runs:
+
+```shell
+camel test run test/json-transform.citrus.it.yaml
+```
+
+The test starts the route and verifies that the pick list is logged.
+
+## Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/json-transform/application.properties b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/json-transform/application.properties
new file mode 100644
index 000000000000..13a83393a912
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/json-transform/application.properties
@@ -0,0 +1,16 @@
+# 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.
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/json-transform/json-transform.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/json-transform/json-transform.camel.yaml
new file mode 100644
index 000000000000..933bb41d7f35
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/json-transform/json-transform.camel.yaml
@@ -0,0 +1,33 @@
+# Reshape the shop's order for the warehouse: jq builds the pick list the warehouse wants, jsonpath
+# picks single values out of the order.
+- route:
+ id: json-transform
+ from:
+ uri: timer
+ parameters:
+ timerName: order
+ repeatCount: 1
+ steps:
+ - setBody:
+ expression:
+ constant:
+ expression: "resource:file:order.json"
+ - setHeader:
+ name: orderId
+ expression:
+ jsonpath:
+ expression: "$.orderId"
+ - setHeader:
+ name: itemCount
+ expression:
+ jsonpath:
+ expression: "$.lines.length()"
+ - log:
+ message: "Order ${header.orderId} with ${header.itemCount} lines: ${body}"
+ - transform:
+ expression:
+ jq:
+ expression: "{order: .orderId, country: .country, pick: [.lines[] | {sku: .sku, qty: .qty}]}"
+ resultType: java.lang.String
+ - log:
+ message: "Pick list for the warehouse: ${body}"
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/json-transform/order.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/json-transform/order.json
new file mode 100644
index 000000000000..2327db569ca5
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/json-transform/order.json
@@ -0,0 +1,3 @@
+{"orderId": "ORD-1001", "customer": "C-482", "country": "DK",
+ "lines": [{"sku": "CAMEL-TSHIRT", "qty": 2, "price": 19.95}, {"sku": "CAMEL-MUG", "qty": 1, "price": 9.50}],
+ "status": "paid"}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xml-to-json/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xml-to-json/README.md
new file mode 100644
index 000000000000..b7e7b725309c
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xml-to-json/README.md
@@ -0,0 +1,67 @@
+# XML to JSON
+
+The supplier sends orders as XML, the shop works in JSON. The Jackson XML data format reads the XML into a map
+and the Jackson JSON data format writes the map out: no mapping code, elements and attributes become fields.
+
+## What you will see
+
+```text
+INFO ... xml-to-json.camel.yaml:13 : Supplier order as XML: <?xml version="1.0" encoding="UTF-8"?><order id="ORD-1001" country="DK">...
+INFO ... xml-to-json.camel.yaml:20 : The same order as JSON: {"id":"ORD-1001","country":"DK","customer":"C-482","line":[{"sku":"CAMEL-TSHIRT","qty":"2","price":"19.95"},{"sku":"CAMEL-MUG","qty":"1","price":"9.50"}],"status":"paid"}
+```
+
+## Install Camel CLI
+
+Install [JBang](https://www.jbang.dev/download/) and the Camel CLI as described in the
+[root README](../../README.md#install-the-camel-cli); `camel --version` confirms the install.
+
+## Run it
+
+```shell
+camel run *
+```
+
+## How it works
+
+- The `file` consumer reads `inbox/supplier-order.xml`; `noop: true` leaves the file where it is so the example
+ can run again.
+- `unmarshal` with `jacksonXml` turns the XML into a map: attributes and child elements become keys, the two
+ `line` elements become a list.
+- `marshal` with `json` writes that map as JSON. Values stay strings, because XML has no numbers; the
+ `data-mapping` example shows how to shape and type the result.
+- The way back, JSON to XML, needs a name for the root element, which the Jackson XML data format takes from a
+ class; that comes with the bean examples later on the ladder.
+
+## Build it step by step
+
+1. A `file` route on `inbox` that logs the XML it reads.
+2. Add `unmarshal` with `jacksonXml` and log the body: a map now.
+3. Add `marshal` with `json` and log again: the same order as JSON.
+4. Drop a second XML file into `inbox` while it runs and watch it get converted too.
+
+## Try changing
+
+- Add `prettyPrint: true` to the `json` data format for indented output.
+- Replace `jacksonXml` by `xslt` when the JSON should have a different shape than the XML: the `xslt` example
+ next door does that for the packing slip.
+
+## Integration testing
+
+The example comes with a test in the [Citrus](https://citrusframework.org/) YAML DSL,
+`test/xml-to-json.citrus.it.yaml`, which the Camel CLI runs:
+
+```shell
+camel test run test/xml-to-json.citrus.it.yaml
+```
+
+The test starts the route and verifies that the order is logged as JSON.
+
+## Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xml-to-json/application.properties b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xml-to-json/application.properties
new file mode 100644
index 000000000000..13a83393a912
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xml-to-json/application.properties
@@ -0,0 +1,16 @@
+# 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.
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xml-to-json/inbox/supplier-order.xml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xml-to-json/inbox/supplier-order.xml
new file mode 100644
index 000000000000..67fc5e6f631c
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xml-to-json/inbox/supplier-order.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<order id="ORD-1001" country="DK">
+ <customer>C-482</customer>
+ <line sku="CAMEL-TSHIRT" qty="2" price="19.95"/>
+ <line sku="CAMEL-MUG" qty="1" price="9.50"/>
+ <status>paid</status>
+</order>
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xml-to-json/xml-to-json.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xml-to-json/xml-to-json.camel.yaml
new file mode 100644
index 000000000000..a4c7225f7cdd
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xml-to-json/xml-to-json.camel.yaml
@@ -0,0 +1,21 @@
+# The supplier sends orders as XML, the shop works in JSON. The Jackson XML data format reads the
+# XML into a map and the Jackson JSON data format writes the map out: no mapping code, the XML
+# elements and attributes become JSON fields.
+- route:
+ id: xml-to-json
+ from:
+ uri: file
+ parameters:
+ directoryName: inbox
+ noop: true
+ include: ".*\\.xml"
+ steps:
+ - log:
+ message: "Supplier order as XML: ${body}"
+ - unmarshal:
+ jacksonXml: {}
+ - marshal:
+ json:
+ library: Jackson
+ - log:
+ message: "The same order as JSON: ${body}"
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xslt/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xslt/README.md
new file mode 100644
index 000000000000..4d799391f234
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xslt/README.md
@@ -0,0 +1,66 @@
+# XSLT
+
+The supplier's XML order becomes the packing slip the warehouse prints, transformed by a stylesheet: one item per
+line and the total pieces to pick.
+
+## What you will see
+
+```text
+INFO ... xslt.camel.yaml:16 : Packing slip:
+<?xml version="1.0" encoding="UTF-8"?><packingSlip order="ORD-1001" customer="C-482" country="DK">
+ <item sku="CAMEL-TSHIRT" pieces="2"/>
+ <item sku="CAMEL-MUG" pieces="1"/>
+ <pieces>3</pieces>
+</packingSlip>
+```
+
+## Install Camel CLI
+
+Install [JBang](https://www.jbang.dev/download/) and the Camel CLI as described in the
+[root README](../../README.md#install-the-camel-cli); `camel --version` confirms the install.
+
+## Run it
+
+```shell
+camel run *
+```
+
+## How it works
+
+- The `file` consumer reads `inbox/supplier-order.xml` and leaves it there with `noop: true`.
+- `to: xslt` applies `packing-slip.xsl`: the template matches `/order`, writes one `item` per `line` with the
+ attributes renamed, and sums the quantities with `sum(line/@qty)`.
+- `${prettyBody}` in the log indents the XML.
+
+## Build it step by step
+
+1. A `file` route on `inbox` that logs the XML.
+2. Add `to: xslt` with a stylesheet that copies the order id into a `packingSlip` root element.
+3. Add a `for-each` over the lines writing one `item` each.
+4. Add the `pieces` total with `sum()`.
+
+## Try changing
+
+- Produce HTML instead: change `xsl:output` to `html` and write a table.
+- Use XSLT 2.0 or 3.0 functions such as `current-dateTime()`: switch to the `xslt-saxon` component.
+
+## Integration testing
+
+The example comes with a test in the [Citrus](https://citrusframework.org/) YAML DSL,
+`test/xslt.citrus.it.yaml`, which the Camel CLI runs:
+
+```shell
+camel test run test/xslt.citrus.it.yaml
+```
+
+The test starts the route and verifies that the packing slip is logged.
+
+## Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xslt/application.properties b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xslt/application.properties
new file mode 100644
index 000000000000..13a83393a912
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xslt/application.properties
@@ -0,0 +1,16 @@
+# 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.
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xslt/inbox/supplier-order.xml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xslt/inbox/supplier-order.xml
new file mode 100644
index 000000000000..67fc5e6f631c
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xslt/inbox/supplier-order.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<order id="ORD-1001" country="DK">
+ <customer>C-482</customer>
+ <line sku="CAMEL-TSHIRT" qty="2" price="19.95"/>
+ <line sku="CAMEL-MUG" qty="1" price="9.50"/>
+ <status>paid</status>
+</order>
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xslt/packing-slip.xsl b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xslt/packing-slip.xsl
new file mode 100644
index 000000000000..22085915d74a
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xslt/packing-slip.xsl
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:output method="xml" indent="yes"/>
+
+ <!-- The supplier's order becomes the packing slip the warehouse prints: one item per line, and
+ the number of pieces to pick in total. -->
+ <xsl:template match="/order">
+ <packingSlip order="{@id}" customer="{customer}" country="{@country}">
+ <xsl:for-each select="line">
+ <item sku="{@sku}" pieces="{@qty}"/>
+ </xsl:for-each>
+ <pieces><xsl:value-of select="sum(line/@qty)"/></pieces>
+ </packingSlip>
+ </xsl:template>
+</xsl:stylesheet>
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xslt/xslt.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xslt/xslt.camel.yaml
new file mode 100644
index 000000000000..9063f8d37e51
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xslt/xslt.camel.yaml
@@ -0,0 +1,17 @@
+# The supplier's XML order dropped in the inbox becomes the packing slip the warehouse prints,
+# transformed by the stylesheet packing-slip.xsl.
+- route:
+ id: xslt
+ from:
+ uri: file
+ parameters:
+ directoryName: inbox
+ noop: true
+ include: ".*\\.xml"
+ steps:
+ - to:
+ uri: xslt
+ parameters:
+ resourceUri: packing-slip.xsl
+ - log:
+ message: "Packing slip:\n${prettyBody}"
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/camel/example/Greeter.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/camel/example/Greeter.java
index ae48efcb5fd7..264b731b4e3b 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/test/java/camel/example/Greeter.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/camel/example/Greeter.java
@@ -16,30 +16,25 @@
*/
package camel.example;
-import org.apache.camel.Exchange;
-import org.apache.camel.Processor;
-
/**
- * Test fixture mirroring {@code src/main/resources/examples/routes/Greeter.java}, which the CLI compiles at runtime. A
- * compiled copy is needed on the test classpath so that {@code examples/routes/beans.yaml} can instantiate its
- * {@code greeter} bean while {@link org.apache.camel.dsl.jbang.core.common.ExampleRoutesLoadTest} pre-parses it.
+ * Test fixture mirroring {@code src/main/resources/examples/quick-start/routes/Greeter.java}, which the CLI compiles at
+ * runtime. A compiled copy is needed on the test classpath so that {@code examples/quick-start/routes/beans.yaml} can
+ * instantiate its {@code greeter} bean while {@link org.apache.camel.dsl.jbang.core.common.ExampleRoutesLoadTest}
+ * pre-parses it.
*
- * Keep this in sync with the example source. Only the type and its {@code message} property are load-bearing for the
- * test: a missing property fails bean binding loudly, whereas the {@link #process} behavior is never exercised (the
- * test does not start the context), so behavioral drift would go unnoticed.
+ * Keep this in sync with the example source (synced from camel-jbang-examples). Only the type and its {@code greeting}
+ * property are load-bearing for the test: a missing property fails bean binding loudly, whereas {@link #greet} is never
+ * called (the test does not start the context), so behavioral drift would go unnoticed.
*/
-public class Greeter implements Processor {
+public class Greeter {
- private String message;
+ private String greeting;
- public void setMessage(String message) {
- this.message = message;
+ public void setGreeting(String greeting) {
+ this.greeting = greeting;
}
- @Override
- public void process(Exchange exchange) throws Exception {
- String body = exchange.getIn().getBody(String.class);
- exchange.getIn().setBody(message + " " + body);
+ public String greet(String name) {
+ return greeting + ", " + name + "!";
}
-
}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/camel/example/OrderNumber.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/camel/example/OrderNumber.java
new file mode 100644
index 000000000000..7f5dad0be5d7
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/camel/example/OrderNumber.java
@@ -0,0 +1,40 @@
+/*
+ * 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 camel.example;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Test fixture mirroring {@code src/main/resources/examples/run/order-generator/OrderNumber.java}, which the CLI
+ * compiles at runtime, so that {@code examples/run/order-generator/beans.yaml} can instantiate its {@code orderNumber}
+ * bean while {@link org.apache.camel.dsl.jbang.core.common.ExampleRoutesLoadTest} pre-parses it.
+ *
+ * Keep this in sync with the example source (synced from camel-jbang-examples). Only the type and its {@code start}
+ * property are load-bearing for the test.
+ */
+public class OrderNumber {
+
+ private final AtomicInteger counter = new AtomicInteger();
+
+ public void setStart(int start) {
+ counter.set(start - 1);
+ }
+
+ public String next() {
+ return "ORD-" + counter.incrementAndGet();
+ }
+}