This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 852177c72cc3 chore: camel-jbang - sync the bundled examples: 
rest-code-first, dynamic-routing, enrich-and-dedupe and the circuit-breaker 
fallback (#28190)
852177c72cc3 is described below

commit 852177c72cc318ca1caa6476887f9d24b03d8f4e
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Oct 20 18:21:55 2026 +0200

    chore: camel-jbang - sync the bundled examples: rest-code-first, 
dynamic-routing, enrich-and-dedupe and the circuit-breaker fallback (#28190)
    
    Synced with mvn -Psync-example-catalog generate-resources from 
camel-jbang-examples main (#115-#119, #125-#128):
    
    - the catalog lists five new examples: contracts/rest-code-first, 
route/dynamic-routing and route/enrich-and-dedupe,
      which are bundled, and ai/ticket-classifier and 
connect-service/jms-request-reply, which need a broker or a model;
    - fail-well/circuit-breaker: the fallback tells a failed call from one the 
open breaker did not make;
    - ai/langchain4j-chat: the description says the chat model is configured by 
provider;
    - contracts/openapi-server: the README says what a servers entry without a 
path does.
    
    
    Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
    
    Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
 .../examples/camel-jbang-example-catalog.json      | 282 ++++++++++++++++++++-
 .../examples/contracts/openapi-server/README.md    |   4 +-
 .../examples/contracts/rest-code-first/Order.java  |  30 +++
 .../examples/contracts/rest-code-first/README.md   |  91 +++++++
 .../rest-code-first/application.properties         |  20 ++
 .../examples/contracts/rest-code-first/orders.json |   5 +
 .../rest-code-first/rest-code-first.camel.yaml     | 107 ++++++++
 .../examples/fail-well/circuit-breaker/README.md   |  37 ++-
 .../circuit-breaker/circuit-breaker.camel.yaml     |  21 +-
 .../examples/route/dynamic-routing/README.md       |  82 ++++++
 .../route/dynamic-routing/application.properties   |  16 ++
 .../dynamic-routing/dynamic-routing.camel.yaml     | 111 ++++++++
 .../route/dynamic-routing/orders/order-1001.json   |   2 +
 .../route/dynamic-routing/orders/order-1002.json   |   2 +
 .../route/dynamic-routing/orders/order-1003.json   |   2 +
 .../examples/route/enrich-and-dedupe/README.md     |  86 +++++++
 .../route/enrich-and-dedupe/application.properties |  19 ++
 .../route/enrich-and-dedupe/customers/C-134.json   |   1 +
 .../route/enrich-and-dedupe/customers/C-207.json   |   1 +
 .../route/enrich-and-dedupe/customers/C-482.json   |   1 +
 .../enrich-and-dedupe/enrich-and-dedupe.camel.yaml |  86 +++++++
 .../route/enrich-and-dedupe/orders/order-1001.json |   2 +
 .../route/enrich-and-dedupe/orders/order-1002.json |   2 +
 .../route/enrich-and-dedupe/orders/order-1003.json |   2 +
 .../orders/resent-order-1001.json                  |   2 +
 25 files changed, 985 insertions(+), 29 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 0be9e36d4066..f4e34cb639de 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
@@ -44,12 +44,12 @@
         ],
         "needs": "a local model",
         "ciSkip": true,
-        "order": 4
+        "order": 5
     },
     {
         "name": "ai/langchain4j-chat",
         "title": "LangChain4j chat",
-        "description": "A local Ollama model started with camel infra writes 
the shipping notification for each of the three orders; the chat model is a 
bean built from properties, the prompt comes from the order, and the log shows 
the reply with its token counts.",
+        "description": "A local Ollama model started with camel infra writes 
the shipping notification for each of the three orders; the chat model is 
configured by provider on the endpoint, the prompt comes from the order, and 
the log shows the reply with its token counts.",
         "level": "ai",
         "teaches": {
             "components": [
@@ -138,7 +138,7 @@
             "orders/order-1003.json",
             "stock.json"
         ],
-        "order": 2
+        "order": 3
     },
     {
         "name": "ai/openai-pii-redaction",
@@ -169,7 +169,60 @@
         ],
         "needs": "an OpenAI-compatible API: set `OPENAI_API_KEY`",
         "ciSkip": true,
-        "order": 3
+        "order": 4
+    },
+    {
+        "name": "ai/ticket-classifier",
+        "title": "Ticket classifier",
+        "description": "A local model reads each of three support tickets and 
names its category, shipping, billing or account; the route checks the answer 
and files the ticket in tickets-out/<category>/, and an answer outside the 
three, or no answer within the circuit breaker's timeout, files it under 
unknown.",
+        "level": "ai",
+        "teaches": {
+            "components": [
+                "langchain4j-chat",
+                "file",
+                "log"
+            ],
+            "eips": [
+                "circuitBreaker",
+                "filter",
+                "setVariable",
+                "setHeader",
+                "unmarshal",
+                "marshal"
+            ],
+            "languages": [
+                "simple",
+                "constant"
+            ],
+            "dataformats": [
+                "json"
+            ]
+        },
+        "tags": [
+            "shop",
+            "ai",
+            "llm",
+            "ollama",
+            "langchain4j",
+            "classification"
+        ],
+        "bundled": false,
+        "requiresDocker": true,
+        "hasCitrusTests": true,
+        "files": [
+            "README.md",
+            "application.properties",
+            "ticket-classifier.camel.yaml",
+            "tickets/T-1.json",
+            "tickets/T-2.json",
+            "tickets/T-3.json"
+        ],
+        "infraServices": [
+            "ollama"
+        ],
+        "needs": "a local model: `ollama pull granite4:3b`",
+        "ciSkip": true,
+        "order": 2
     },
     {
         "name": "cloud/aws-sqs",
@@ -299,7 +352,7 @@
         "infraServices": [
             "artemis"
         ],
-        "order": 6
+        "order": 7
     },
     {
         "name": "connect-service/ftp",
@@ -343,7 +396,64 @@
             "artemis",
             "ftp"
         ],
-        "order": 5
+        "order": 6
+    },
+    {
+        "name": "connect-service/jms-request-reply",
+        "title": "JMS request-reply",
+        "description": "The three orders go on a queue of an ActiveMQ Artemis 
broker started with camel infra; a consumer asks a stock service over JMS 
request-reply for each, publishes the reserved ones on a topic that shipping 
and invoicing both subscribe to, and ORD-1003, which the stock service cannot 
reserve, is redelivered three times and then moved to a dead letter queue.",
+        "level": "connect-service",
+        "teaches": {
+            "components": [
+                "jms",
+                "file",
+                "log"
+            ],
+            "eips": [
+                "errorHandler",
+                "deadLetterChannel",
+                "redeliveryPolicy",
+                "filter",
+                "throwException",
+                "setVariable",
+                "setHeader",
+                "unmarshal"
+            ],
+            "languages": [
+                "simple",
+                "jsonpath",
+                "groovy"
+            ],
+            "dataformats": [
+                "json"
+            ]
+        },
+        "tags": [
+            "shop",
+            "jms",
+            "artemis",
+            "queue",
+            "topic",
+            "request-reply",
+            "dead-letter",
+            "messaging"
+        ],
+        "bundled": false,
+        "requiresDocker": true,
+        "hasCitrusTests": true,
+        "files": [
+            "README.md",
+            "application.properties",
+            "jms-request-reply.camel.yaml",
+            "orders/order-1001.json",
+            "orders/order-1002.json",
+            "orders/order-1003.json",
+            "stock.json"
+        ],
+        "infraServices": [
+            "artemis"
+        ],
+        "order": 3
     },
     {
         "name": "connect-service/kafka-orders",
@@ -392,7 +502,7 @@
         "infraServices": [
             "kafka"
         ],
-        "order": 4
+        "order": 5
     },
     {
         "name": "connect-service/mqtt",
@@ -433,7 +543,7 @@
         "infraServices": [
             "mosquitto"
         ],
-        "order": 3
+        "order": 4
     },
     {
         "name": "connect-service/sql",
@@ -657,7 +767,7 @@
         "infraServices": [
             "keycloak"
         ],
-        "order": 3
+        "order": 4
     },
     {
         "name": "contracts/openapi-client",
@@ -705,7 +815,7 @@
             "stock-api.json"
         ],
         "needs": "`contracts/openapi-server` running first",
-        "order": 2
+        "order": 3
     },
     {
         "name": "contracts/openapi-server",
@@ -753,12 +863,60 @@
             "stock-api.json",
             "stock.json"
         ],
+        "order": 2
+    },
+    {
+        "name": "contracts/rest-code-first",
+        "title": "REST code first",
+        "description": "The shop's order API written with the REST DSL: GET 
/orders and GET /orders/{id} answer the orders as JSON (404 for an unknown id), 
POST /orders binds the JSON of a new order to a Java record and answers 201 
(400 without lines), and the API description generated from the operations is 
served at /openapi.",
+        "level": "contracts",
+        "teaches": {
+            "components": [
+                "rest",
+                "platform-http",
+                "direct",
+                "log"
+            ],
+            "eips": [
+                "restConfiguration",
+                "rest",
+                "choice",
+                "filter",
+                "setHeader",
+                "unmarshal"
+            ],
+            "languages": [
+                "simple",
+                "groovy"
+            ],
+            "dataformats": [
+                "json"
+            ]
+        },
+        "tags": [
+            "shop",
+            "rest",
+            "code-first",
+            "binding",
+            "openapi",
+            "java-record"
+        ],
+        "bundled": true,
+        "requiresDocker": false,
+        "hasCitrusTests": true,
+        "files": [
+            "Order.java",
+            "README.md",
+            "application.properties",
+            "orders.json",
+            "rest-code-first.camel.yaml"
+        ],
         "order": 1
     },
     {
         "name": "fail-well/circuit-breaker",
         "title": "Circuit breaker",
-        "description": "A stock check calls the supplier every second; the 
supplier goes down for nine calls, the breaker opens after two failures in its 
window of four, answers from the fallback while open, tries the supplier again 
after five seconds and closes once a call succeeds; each line logs the breaker 
state.",
+        "description": "A stock check calls the supplier every second; the 
supplier goes down for nine calls, the breaker opens after two failures in its 
window of four, answers from the fallback without calling the supplier while 
open, tries it again after five seconds and closes once a call succeeds; each 
line logs the breaker state and whether the supplier failed or was not called.",
         "level": "fail-well",
         "teaches": {
             "components": [
@@ -1047,6 +1205,108 @@
         ],
         "order": 1
     },
+    {
+        "name": "route/dynamic-routing",
+        "title": "Dynamic routing",
+        "description": "Each of the three orders goes to the warehouse of its 
country and to the billing of its status through a recipient list computed from 
the order; billing a paid order fails over from a primary invoice service that 
is down to a backup one, the pending order gets no invoice, and the US 
warehouse is throttled to one order per second.",
+        "level": "route",
+        "teaches": {
+            "components": [
+                "file",
+                "direct",
+                "log"
+            ],
+            "eips": [
+                "recipientList",
+                "loadBalance",
+                "failoverLoadBalancer",
+                "throttle",
+                "throwException",
+                "noErrorHandler",
+                "unmarshal"
+            ],
+            "languages": [
+                "simple",
+                "constant"
+            ],
+            "dataformats": [
+                "json"
+            ]
+        },
+        "tags": [
+            "shop",
+            "eip",
+            "recipient-list",
+            "failover",
+            "load-balancer",
+            "throttle"
+        ],
+        "bundled": true,
+        "requiresDocker": false,
+        "hasCitrusTests": true,
+        "files": [
+            "README.md",
+            "application.properties",
+            "dynamic-routing.camel.yaml",
+            "orders/order-1001.json",
+            "orders/order-1002.json",
+            "orders/order-1003.json"
+        ],
+        "order": 6
+    },
+    {
+        "name": "route/enrich-and-dedupe",
+        "title": "Enrich and deduplicate",
+        "description": "The three orders and a resent copy of ORD-1001 are 
read; an idempotent consumer accepts each order id once and logs the resent one 
as a duplicate, each accepted order is enriched with its customer's name and 
tier from customers/ with pollEnrich, and a wire tap writes a copy to audit/ 
without holding the order up.",
+        "level": "route",
+        "teaches": {
+            "components": [
+                "file",
+                "direct",
+                "log"
+            ],
+            "eips": [
+                "idempotentConsumer",
+                "pollEnrich",
+                "wireTap",
+                "choice",
+                "setHeader",
+                "unmarshal",
+                "marshal"
+            ],
+            "languages": [
+                "simple"
+            ],
+            "dataformats": [
+                "json"
+            ]
+        },
+        "tags": [
+            "shop",
+            "eip",
+            "idempotent",
+            "duplicates",
+            "enrich",
+            "wire-tap",
+            "audit"
+        ],
+        "bundled": true,
+        "requiresDocker": false,
+        "hasCitrusTests": true,
+        "files": [
+            "README.md",
+            "application.properties",
+            "customers/C-134.json",
+            "customers/C-207.json",
+            "customers/C-482.json",
+            "enrich-and-dedupe.camel.yaml",
+            "orders/order-1001.json",
+            "orders/order-1002.json",
+            "orders/order-1003.json",
+            "orders/resent-order-1001.json"
+        ],
+        "order": 5
+    },
     {
         "name": "route/filter-and-multicast",
         "title": "Filter and multicast",
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
index 746aaa90b5fa..2fb9be631c60 100644
--- 
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
@@ -42,8 +42,8 @@ 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`,
+  come from the contract, not from the route file, and the base path `/api` 
from its `servers` entry (an entry
+  without a path serves the operations at the root). `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`.
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/rest-code-first/Order.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/rest-code-first/Order.java
new file mode 100644
index 000000000000..cc393590bd71
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/rest-code-first/Order.java
@@ -0,0 +1,30 @@
+/*
+ * 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.List;
+
+/**
+ * An order of the shop, as the JSON of POST /orders binds to it: the REST DSL 
turns the request body into this record
+ * (bindingMode json, type camel.example.Order), and the record back into JSON 
for the answer.
+ */
+public record Order(String orderId, String customer, String country, String 
status, List<Line> lines) {
+
+    /** One line of an order. */
+    public record Line(String sku, int qty, double price) {
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/rest-code-first/README.md
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/rest-code-first/README.md
new file mode 100644
index 000000000000..fe96d6caa15e
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/rest-code-first/README.md
@@ -0,0 +1,91 @@
+# REST code first
+
+The shop's order API written with the REST DSL. The routes declare each 
operation, the JSON of a new order is bound
+to a Java record, and Camel builds the API description from the operations and 
serves it at `/openapi`.
+
+## What you will see
+
+```text
+INFO ... rest-code-first.camel.yaml:101 : Order ORD-1004 created with 1 line(s)
+```
+
+and the answers of the API:
+
+```shell
+$ curl http://localhost:8080/orders/ORD-1002
+{"orderId":"ORD-1002","customer":"C-207","country":"DE","status":"paid","lines":[{"sku":"CAMEL-MUG","qty":3,"price":9.5}]}
+
+$ curl -i http://localhost:8080/orders/ORD-9999
+HTTP/1.1 404 Not Found
+{"error": "unknown order ORD-9999"}
+
+$ curl -i -H 'Content-Type: application/json' -d '{"orderId": "ORD-1004", 
"customer": "C-482", "country": "DK", "status": "paid", "lines": [{"sku": 
"CAMEL-MUG", "qty": 1, "price": 9.5}]}' http://localhost:8080/orders
+HTTP/1.1 201 Created
+{"orderId":"ORD-1004","customer":"C-482","country":"DK","status":"paid","lines":[{"sku":"CAMEL-MUG","qty":1,"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 *
+```
+
+Then call the API as shown above. `curl http://localhost:8080/openapi` shows 
the API description.
+Stop the example with `ctrl` + `c`.
+
+## How it works
+
+- `rest-code-first.camel.yaml` declares the API with the REST DSL: `GET 
/orders`, `GET /orders/{id}` and
+  `POST /orders`, each with an id and a description, and each sent to a 
`direct` route.
+- `restConfiguration` sets `bindingMode: json`. The answers are Java objects 
(a List, a Map, a record), and the
+  binding writes them as JSON. For `POST /orders`, `type: camel.example.Order` 
makes the binding read the request
+  JSON into the record.
+- `Order.java` is the record, with a nested record per line. Simple reads its 
fields as `${body.orderId}`.
+- An error answer, 404 or 400, is not bound (`skipBindingOnErrorCode` is on by 
default), so the route writes it as
+  JSON text.
+- `apiContextPath: openapi` serves the API description that Camel generates 
from the operations.
+  `application.properties` adds `camel-openapi-java` for it, which `camel run` 
does not add by itself for a
+  code-first API.
+- `orders.json` holds the three orders of the shop.
+
+## Build it step by step
+
+Ask your assistant, or type it yourself, one step at a time, and run after 
each:
+
+1. A REST DSL `GET /orders` that answers the orders from `orders.json`.
+2. `GET /orders/{id}`: the order with that id, or 404 with `{"error": "unknown 
order <id>"}`.
+3. `POST /orders` with JSON binding to a Java record `camel.example.Order`: 
400 when the order has no lines,
+   otherwise log the order and answer 201.
+4. Serve the API description at `/openapi`.
+
+## Try changing
+
+- `bindingMode: off`: the answers are no longer JSON, and the routes have to 
marshal them themselves.
+- Add `PUT /orders/{id}` that changes the status of an order.
+- Compare with the [openapi-server](../openapi-server/) example, which starts 
from the contract instead.
+
+## Integration testing
+
+The example comes with a test in the [Citrus](https://citrusframework.org/) 
YAML DSL,
+`test/rest-code-first.citrus.it.yaml`, which the Camel CLI runs:
+
+```shell
+camel test run test/rest-code-first.citrus.it.yaml
+```
+
+The test calls each operation and checks the answers, and the API description.
+
+## 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/rest-code-first/application.properties
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/rest-code-first/application.properties
new file mode 100644
index 000000000000..ee269d643547
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/rest-code-first/application.properties
@@ -0,0 +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.
+
+camel.server.port=8080
+# the API description at /openapi needs camel-openapi-java, which camel run 
does not add by itself for code-first REST
+camel.jbang.dependencies=camel:openapi-java
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/rest-code-first/orders.json
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/rest-code-first/orders.json
new file mode 100644
index 000000000000..ceea517ee3b5
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/rest-code-first/orders.json
@@ -0,0 +1,5 @@
+[
+  {"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.5}]},
+  {"orderId": "ORD-1002", "customer": "C-207", "country": "DE", "status": 
"paid", "lines": [{"sku": "CAMEL-MUG", "qty": 3, "price": 9.5}]},
+  {"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.0}, {"sku": "CAMEL-MUG", "qty": 2, "price": 
9.5}]}
+]
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/rest-code-first/rest-code-first.camel.yaml
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/rest-code-first/rest-code-first.camel.yaml
new file mode 100644
index 000000000000..b5f0daa22f4d
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/rest-code-first/rest-code-first.camel.yaml
@@ -0,0 +1,107 @@
+# The shop's order API, code first: the REST DSL declares each operation, 
binds the JSON of a new order to a Java
+# record, and builds the API description from the operations, served at 
/openapi.
+- restConfiguration:
+    bindingMode: json
+    apiContextPath: openapi
+
+- rest:
+    path: /orders
+    get:
+      - id: listOrders
+        description: All orders
+        to:
+          uri: direct:list-orders
+      - id: getOrder
+        path: "/{id}"
+        description: One order, by its id
+        to:
+          uri: direct:get-order
+    post:
+      - id: createOrder
+        description: Creates an order
+        type: camel.example.Order
+        to:
+          uri: direct:create-order
+
+# The answers are Java objects (a List, a Map, a record): the json binding 
writes them as JSON
+- route:
+    id: list-orders
+    from:
+      uri: direct
+      parameters:
+        name: list-orders
+      steps:
+        - setBody:
+            expression:
+              constant:
+                expression: "resource:file:orders.json"
+        - unmarshal:
+            json:
+              library: Jackson
+
+- route:
+    id: get-order
+    from:
+      uri: direct
+      parameters:
+        name: get-order
+      steps:
+        - setBody:
+            expression:
+              constant:
+                expression: "resource:file:orders.json"
+        - unmarshal:
+            json:
+              library: Jackson
+        - setBody:
+            expression:
+              groovy:
+                expression: "body.find { it.orderId == headers.id }"
+        - filter:
+            expression:
+              simple:
+                expression: "${body} == null"
+            steps:
+              - setHeader:
+                  name: CamelHttpResponseCode
+                  expression:
+                    constant:
+                      expression: "404"
+              # an error answer is not bound to JSON (skipBindingOnErrorCode), 
so it is written as JSON text
+              - setBody:
+                  expression:
+                    simple:
+                      expression: '{"error": "unknown order ${header.id}"}'
+
+# The binding has turned the request into a camel.example.Order record
+- route:
+    id: create-order
+    from:
+      uri: direct
+      parameters:
+        name: create-order
+      steps:
+        - choice:
+            when:
+              - expression:
+                  simple:
+                    expression: "${body.lines} == null || 
${body.lines.isEmpty()} == true"
+                steps:
+                  - setHeader:
+                      name: CamelHttpResponseCode
+                      expression:
+                        constant:
+                          expression: "400"
+                  - setBody:
+                      expression:
+                        constant:
+                          expression: '{"error": "an order needs at least one 
line"}'
+            otherwise:
+              steps:
+                - log:
+                    message: "Order ${body.orderId} created with 
${body.lines.size()} line(s)"
+                - setHeader:
+                    name: CamelHttpResponseCode
+                    expression:
+                      constant:
+                        expression: "201"
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 c38d1596fe7e..a688f20dc21a 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
@@ -7,14 +7,18 @@ fallback without calling the supplier, and tries again after 
five seconds until
 ## What you will see
 
 ```text
-INFO ... circuit-breaker.camel.yaml:30 : Stock check 1 (breaker CLOSED): 
CAMEL-MUG: 47 in stock at the supplier
-INFO ... circuit-breaker.camel.yaml:30 : Stock check 2 (breaker CLOSED): 
CAMEL-MUG: 35 in stock at the supplier
-INFO ... circuit-breaker.camel.yaml:30 : Stock check 3 (breaker CLOSED): 
CAMEL-MUG: 49 in stock at the supplier
-INFO ... circuit-breaker.camel.yaml:30 : Stock check 4 (breaker CLOSED): no 
answer, using last known stock
-INFO ... circuit-breaker.camel.yaml:30 : Stock check 5 (breaker OPEN): no 
answer, using last known stock
+INFO ... circuit-breaker.camel.yaml:43 : Stock check 1 (breaker CLOSED): 
CAMEL-MUG: 24 in stock at the supplier
+INFO ... circuit-breaker.camel.yaml:43 : Stock check 2 (breaker CLOSED): 
CAMEL-MUG: 44 in stock at the supplier
+INFO ... circuit-breaker.camel.yaml:43 : Stock check 3 (breaker CLOSED): 
CAMEL-MUG: 31 in stock at the supplier
+INFO ... circuit-breaker.camel.yaml:43 : Stock check 4 (breaker CLOSED): 
supplier failed, using last known stock
+INFO ... circuit-breaker.camel.yaml:43 : Stock check 5 (breaker OPEN): 
supplier failed, using last known stock
+INFO ... circuit-breaker.camel.yaml:43 : Stock check 6 (breaker OPEN): breaker 
open, supplier not called, using last known stock
 ...
-INFO ... circuit-breaker.camel.yaml:30 : Stock check 14 (breaker OPEN): no 
answer, using last known stock
-INFO ... circuit-breaker.camel.yaml:30 : Stock check 15 (breaker CLOSED): 
CAMEL-MUG: 58 in stock at the supplier
+INFO ... circuit-breaker.camel.yaml:43 : Stock check 10 (breaker OPEN): 
supplier failed, using last known stock
+INFO ... circuit-breaker.camel.yaml:43 : Stock check 11 (breaker OPEN): 
breaker open, supplier not called, using last known stock
+...
+INFO ... circuit-breaker.camel.yaml:43 : Stock check 14 (breaker OPEN): 
breaker open, supplier not called, using last known stock
+INFO ... circuit-breaker.camel.yaml:43 : Stock check 15 (breaker CLOSED): 
CAMEL-MUG: 46 in stock at the supplier
 ```
 
 ## Install Camel CLI
@@ -35,13 +39,18 @@ Stop it with `ctrl` + `c`; the state logged on each line is 
the breaker's state
 - `circuitBreaker` wraps the call to `direct:supplier` with 
[Resilience4j](https://resilience4j.readme.io/):
   the breaker counts the last four calls (`slidingWindowSize`) and opens when 
half of them failed
   (`failureRateThreshold`), which happens on check 5.
-- While open, the steps are not run at all; `onFallback` sets the body 
instead. After `waitDurationInOpenState`,
-  five seconds, the breaker goes half open and lets one call through 
(`permittedNumberOfCallsInHalfOpenState`):
-  check 10 still fails, so it opens for another five seconds; check 15 
succeeds and closes it.
+- `onFallback` runs both when a call fails and when the open breaker does not 
make the call at all, and
+  `CamelCircuitBreakerResponseRejected` tells them apart: `false` on checks 4 
and 5, where the supplier was
+  called and failed (the failure on check 5 opens the breaker, so the state 
after it is `OPEN`), and `true` on
+  check 6 onwards, where the steps are not run at all. That difference is what 
the breaker buys: no call to a
+  service that is known to be down.
+- After `waitDurationInOpenState`, five seconds, the breaker goes half open 
and lets one call through
+  (`permittedNumberOfCallsInHalfOpenState`): check 10 is that call and still 
fails, so it opens for another five
+  seconds; check 15 succeeds and closes it.
 - The supplier is a second route with `noErrorHandler`, so its exception 
reaches the breaker; it is down while
   the timer counter is 4 to 12.
-- `CamelCircuitBreakerState` is one of the exchange properties the breaker 
sets; `CamelCircuitBreakerResponseSuccessfulExecution`
-  and `CamelCircuitBreakerResponseFromFallback` are the others.
+- `CamelCircuitBreakerState` and `CamelCircuitBreakerResponseRejected` are 
exchange properties the breaker sets;
+  `CamelCircuitBreakerResponseSuccessfulExecution` and 
`CamelCircuitBreakerResponseFromFallback` are others.
 
 ## Build it step by step
 
@@ -51,9 +60,13 @@ Stop it with `ctrl` + `c`; the state logged on each line is 
the breaker's state
 3. Wrap the call in `circuitBreaker` with `onFallback`: no more stack traces, 
but the supplier is still called
    nine times because the default window is 100 calls.
 4. Add the `resilience4jConfiguration` shown here and log 
`CamelCircuitBreakerState` to watch it open and close.
+5. Choose the fallback body on `CamelCircuitBreakerResponseRejected`, so the 
log shows which checks the breaker
+   saved from calling the supplier.
 
 ## Try changing
 
+- Run `camel get circuit-breaker` in another terminal while it runs: the state 
and the success, failure,
+  rejected, fallback and timeout counters of the breaker.
 - Set `waitDurationInOpenState` to 2000 and see the breaker try the supplier 
sooner.
 - Add `timeoutEnabled: true` and `timeoutDuration: 500` and a `delay` of 1000 
in the supplier: a slow supplier
   counts as a failure too.
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/circuit-breaker/circuit-breaker.camel.yaml
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/circuit-breaker/circuit-breaker.camel.yaml
index 90d73bfaaf16..87f6ef96c301 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/circuit-breaker/circuit-breaker.camel.yaml
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/circuit-breaker/circuit-breaker.camel.yaml
@@ -23,10 +23,23 @@
                   uri: direct:supplier
             onFallback:
               steps:
-                - setBody:
-                    expression:
-                      simple:
-                        expression: "no answer, using last known stock"
+                # rejected: the breaker is open, so the supplier was not 
called at all
+                - choice:
+                    when:
+                      - expression:
+                          simple:
+                            expression: 
"${exchangeProperty.CamelCircuitBreakerResponseRejected} == true"
+                        steps:
+                          - setBody:
+                              expression:
+                                simple:
+                                  expression: "breaker open, supplier not 
called, using last known stock"
+                    otherwise:
+                      steps:
+                        - setBody:
+                            expression:
+                              simple:
+                                expression: "supplier failed, using last known 
stock"
         - log:
             message: "Stock check ${header.CamelTimerCounter} (breaker 
${exchangeProperty.CamelCircuitBreakerState}): ${body}"
 
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/dynamic-routing/README.md
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/dynamic-routing/README.md
new file mode 100644
index 000000000000..16e3c56bb48e
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/dynamic-routing/README.md
@@ -0,0 +1,82 @@
+# Dynamic routing
+
+Where an order goes is computed from the order itself: a recipient list sends 
it to the warehouse of its country
+and to the billing of its status. Billing fails over to a backup invoice 
service when the primary one is down, and
+the US warehouse takes at most one order per second.
+
+## What you will see
+
+```text
+INFO ... dynamic-routing.camel.yaml:16 : Order ORD-1001 for DK, paid
+INFO ... dynamic-routing.camel.yaml:30 : Warehouse DK picks ORD-1001
+INFO ... dynamic-routing.camel.yaml:110 : Invoice for ORD-1001 via the backup 
service
+...
+INFO ... dynamic-routing.camel.yaml:16 : Order ORD-1003 for US, pending
+INFO ... dynamic-routing.camel.yaml:56 : Warehouse US picks ORD-1003
+INFO ... dynamic-routing.camel.yaml:86 : No invoice for ORD-1003 until it is 
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 the example with `ctrl` + `c`.
+
+## How it works
+
+- `recipientList` computes the endpoints for each order with Simple:
+  `direct:warehouse-${body[country]},direct:billing-${body[status]}`. ORD-1001 
goes to `direct:warehouse-DK` and
+  `direct:billing-paid`, ORD-1003 to `direct:warehouse-US` and 
`direct:billing-pending`. Each endpoint is a route.
+- `billing-paid` sends the order through a `failoverLoadBalancer`: the primary 
invoice service first, and the backup
+  when the primary fails with an `IllegalStateException`. 
`inheritErrorHandler: false` makes it fail over at once,
+  without the route's error handler logging the failure first.
+- `invoice-primary` is down: it throws the exception, and `noErrorHandler` 
hands the failure straight back to the
+  load balancer.
+- `throttle` lets at most one order per second into the US warehouse 
(`timePeriodMillis: 1000`); an order over the
+  limit waits.
+
+## Build it step by step
+
+Ask your assistant, or type it yourself, one step at a time, and run after 
each:
+
+1. A file route on `orders` that unmarshals each order and logs its country 
and status.
+2. A recipient list to `direct:warehouse-<country>` and 
`direct:billing-<status>`, with a route for each.
+3. Bill a paid order through a failover load balancer: a primary invoice 
service that fails, and a backup that
+   takes over.
+4. Throttle the US warehouse to one order per second.
+
+## Try changing
+
+- Copy `order-1003.json` to `order-1004.json` and `order-1005.json` with new 
order ids: the US warehouse picks
+  them one second apart.
+- Remove the `throwException` from `invoice-primary`: every invoice goes 
through the primary service.
+- Add an order for a country without a warehouse route: the recipient list 
fails on the unknown endpoint, unless
+  you set `ignoreInvalidEndpoints: true`.
+
+## Integration testing
+
+The example comes with a test in the [Citrus](https://citrusframework.org/) 
YAML DSL,
+`test/dynamic-routing.citrus.it.yaml`, which the Camel CLI runs:
+
+```shell
+camel test run test/dynamic-routing.citrus.it.yaml
+```
+
+The test runs the routes and verifies the warehouses, the failover and the 
pending order.
+
+## 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/dynamic-routing/application.properties
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/dynamic-routing/application.properties
new file mode 100644
index 000000000000..13a83393a912
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/dynamic-routing/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/dynamic-routing/dynamic-routing.camel.yaml
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/dynamic-routing/dynamic-routing.camel.yaml
new file mode 100644
index 000000000000..e9f5f7e5e8af
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/dynamic-routing/dynamic-routing.camel.yaml
@@ -0,0 +1,111 @@
+# Each order goes to the warehouse of its country and to the billing of its 
status: a recipient list computes both
+# endpoints from the order. Billing a paid order fails over to a backup 
invoice service when the primary one is down,
+# and the US warehouse takes at most one order per second.
+- route:
+    id: incoming-orders
+    from:
+      uri: file
+      parameters:
+        directoryName: orders
+        noop: true
+        sortBy: "file:name"
+      steps:
+        - unmarshal:
+            json:
+              library: Jackson
+        - log:
+            message: "Order ${body[orderId]} for ${body[country]}, 
${body[status]}"
+        - recipientList:
+            expression:
+              simple:
+                expression: 
"direct:warehouse-${body[country]},direct:billing-${body[status]}"
+
+- route:
+    id: warehouse-DK
+    from:
+      uri: direct
+      parameters:
+        name: warehouse-DK
+      steps:
+        - log:
+            message: "Warehouse DK picks ${body[orderId]}"
+
+- route:
+    id: warehouse-DE
+    from:
+      uri: direct
+      parameters:
+        name: warehouse-DE
+      steps:
+        - log:
+            message: "Warehouse DE picks ${body[orderId]}"
+
+# At most one order per second: an order over the limit waits
+- route:
+    id: warehouse-US
+    from:
+      uri: direct
+      parameters:
+        name: warehouse-US
+      steps:
+        - throttle:
+            expression:
+              constant:
+                expression: "1"
+            timePeriodMillis: 1000
+        - log:
+            message: "Warehouse US picks ${body[orderId]}"
+
+# The failover load balancer tries the primary invoice service first, and the 
backup when it fails
+- route:
+    id: billing-paid
+    from:
+      uri: direct
+      parameters:
+        name: billing-paid
+      steps:
+        - loadBalance:
+            failoverLoadBalancer:
+              # fail over at once, without the route's error handler logging 
the failure first
+              inheritErrorHandler: false
+              exception:
+                - java.lang.IllegalStateException
+            steps:
+              - to:
+                  uri: direct:invoice-primary
+              - to:
+                  uri: direct:invoice-backup
+
+- route:
+    id: billing-pending
+    from:
+      uri: direct
+      parameters:
+        name: billing-pending
+      steps:
+        - log:
+            message: "No invoice for ${body[orderId]} until it is paid"
+
+# The primary invoice service is down; noErrorHandler hands the failure 
straight back to the load balancer
+- route:
+    id: invoice-primary
+    errorHandler:
+      noErrorHandler: {}
+    from:
+      uri: direct
+      parameters:
+        name: invoice-primary
+      steps:
+        - throwException:
+            exceptionType: java.lang.IllegalStateException
+            message: "The invoice service is down"
+
+- route:
+    id: invoice-backup
+    from:
+      uri: direct
+      parameters:
+        name: invoice-backup
+      steps:
+        - log:
+            message: "Invoice for ${body[orderId]} via the backup service"
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/dynamic-routing/orders/order-1001.json
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/dynamic-routing/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/dynamic-routing/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/dynamic-routing/orders/order-1002.json
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/dynamic-routing/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/dynamic-routing/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/dynamic-routing/orders/order-1003.json
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/dynamic-routing/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/dynamic-routing/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/enrich-and-dedupe/README.md
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/enrich-and-dedupe/README.md
new file mode 100644
index 000000000000..541594325463
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/enrich-and-dedupe/README.md
@@ -0,0 +1,86 @@
+# Enrich and deduplicate
+
+The shop sometimes sends an order twice. An idempotent consumer accepts each 
order id once, each accepted order is
+enriched with its customer, and a wire tap writes a copy to an audit trail 
without holding the order up.
+
+## What you will see
+
+```text
+INFO ... enrich-and-dedupe.camel.yaml:19 : Order ORD-1001 received
+INFO ... enrich-and-dedupe.camel.yaml:39 : Order ORD-1001 accepted
+INFO ... enrich-and-dedupe.camel.yaml:60 : Order ORD-1001 for Anna Jensen 
(gold)
+...
+INFO ... enrich-and-dedupe.camel.yaml:19 : Order ORD-1001 received
+INFO ... enrich-and-dedupe.camel.yaml:35 : Order ORD-1001 is a duplicate, 
skipped
+INFO ... enrich-and-dedupe.camel.yaml:85 : Audited ORD-1001.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 *
+```
+
+The copies of the accepted orders land in `audit/`. Stop the example with 
`ctrl` + `c`.
+
+## How it works
+
+- `orders/` holds the three orders and `resent-order-1001.json`, the same 
ORD-1001 sent a second time.
+  `customers/` holds a file per customer with the name and the tier.
+- The `beans` entry declares `orderIds`, an in-memory idempotent repository: 
the set of order ids seen so far.
+- `idempotentConsumer` computes the order id with Simple and checks it against 
the repository. With
+  `skipDuplicate: false` a duplicate is not dropped but marked with the 
exchange property `CamelDuplicateMessage`,
+  so the `choice` can log it.
+- `pollEnrich` reads the customer's file. The file name is computed into a 
header first: the file consumer that
+  `pollEnrich` uses evaluates `fileName` with the headers of the message, not 
its body. `variableReceive: customer`
+  keeps the customer in a variable, so the order stays the body, and 
`unmarshal` with `variableSend` and
+  `variableReceive` parses the variable in place.
+- `wireTap` sends a copy of the order to the `audit` route and goes on at 
once: the audit never holds the order up.
+  The `audit` route marshals the copy to JSON and writes 
`audit/<orderId>.json`.
+- `application.properties` declares `audit/` as data (`camel.jbang.dataDir`), 
so `camel run --dev` does not reload
+  when the audit writes a file.
+
+## Build it step by step
+
+Ask your assistant, or type it yourself, one step at a time, and run after 
each:
+
+1. A file route on `orders` that unmarshals each order and logs "Order ... 
received" (four files).
+2. Accept each order id only once with an idempotent consumer and an in-memory 
repository, and log the resent
+   order as a duplicate.
+3. Read the customer's file from `customers/` with `pollEnrich` into a 
variable, and log the name and the tier.
+4. Copy each accepted order to `audit/<orderId>.json` with a wire tap.
+
+## Try changing
+
+- `skipDuplicate: true`, the default: the duplicate is dropped without a word.
+- Replace the in-memory repository with a file one,
+  `org.apache.camel.support.processor.idempotent.FileIdempotentRepository` 
with a `fileStore` property: the ids
+  then survive a restart, and on the next run every order is a duplicate.
+- Add an order for a customer without a file: `pollEnrich` waits for its 
`timeout` and the variable stays empty.
+
+## Integration testing
+
+The example comes with a test in the [Citrus](https://citrusframework.org/) 
YAML DSL,
+`test/enrich-and-dedupe.citrus.it.yaml`, which the Camel CLI runs:
+
+```shell
+camel test run test/enrich-and-dedupe.citrus.it.yaml
+```
+
+The test runs the routes and verifies the enrichment, the duplicate and the 
audit.
+
+## 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/enrich-and-dedupe/application.properties
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/enrich-and-dedupe/application.properties
new file mode 100644
index 000000000000..e38c10e165c3
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/enrich-and-dedupe/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.
+
+# audit/ is written by the example: dev mode does not watch it
+camel.jbang.dataDir=audit
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/enrich-and-dedupe/customers/C-134.json
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/enrich-and-dedupe/customers/C-134.json
new file mode 100644
index 000000000000..b1594dcd7e32
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/enrich-and-dedupe/customers/C-134.json
@@ -0,0 +1 @@
+{"id": "C-134", "name": "Emily Carter", "tier": "bronze"}
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/enrich-and-dedupe/customers/C-207.json
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/enrich-and-dedupe/customers/C-207.json
new file mode 100644
index 000000000000..db5d2590a9aa
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/enrich-and-dedupe/customers/C-207.json
@@ -0,0 +1 @@
+{"id": "C-207", "name": "Jonas Weber", "tier": "silver"}
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/enrich-and-dedupe/customers/C-482.json
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/enrich-and-dedupe/customers/C-482.json
new file mode 100644
index 000000000000..0d6881cbe4d1
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/enrich-and-dedupe/customers/C-482.json
@@ -0,0 +1 @@
+{"id": "C-482", "name": "Anna Jensen", "tier": "gold"}
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/enrich-and-dedupe/enrich-and-dedupe.camel.yaml
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/enrich-and-dedupe/enrich-and-dedupe.camel.yaml
new file mode 100644
index 000000000000..f81fb64b3309
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/enrich-and-dedupe/enrich-and-dedupe.camel.yaml
@@ -0,0 +1,86 @@
+# Each order id is accepted once: the shop sent ORD-1001 twice 
(resent-order-1001.json). An accepted order is
+# enriched with its customer, read from customers/, and a copy goes to an 
audit trail without holding the order up.
+- beans:
+    - name: orderIds
+      type: 
org.apache.camel.support.processor.idempotent.MemoryIdempotentRepository
+
+- route:
+    id: incoming-orders
+    from:
+      uri: file
+      parameters:
+        directoryName: orders
+        noop: true
+        sortBy: "file:name"
+      steps:
+        - unmarshal:
+            json:
+              library: Jackson
+        - log:
+            message: "Order ${body[orderId]} received"
+        # skipDuplicate false lets a duplicate through, marked with the 
CamelDuplicateMessage property
+        - idempotentConsumer:
+            expression:
+              simple:
+                expression: "${body[orderId]}"
+            idempotentRepository: orderIds
+            skipDuplicate: false
+            steps:
+              - choice:
+                  when:
+                    - expression:
+                        simple:
+                          expression: 
"${exchangeProperty.CamelDuplicateMessage} == true"
+                      steps:
+                        - log:
+                            message: "Order ${body[orderId]} is a duplicate, 
skipped"
+                  otherwise:
+                    steps:
+                      - log:
+                          message: "Order ${body[orderId]} accepted"
+                      # the file name goes in a header: the file consumer of 
pollEnrich evaluates fileName with
+                      # the headers of the message, not its body; the customer 
goes to a variable, so the order
+                      # stays the body
+                      - setHeader:
+                          name: customerFile
+                          expression:
+                            simple:
+                              expression: "${body[customer]}.json"
+                      - pollEnrich:
+                          expression:
+                            simple:
+                              expression: 
"file:customers?fileName=${header.customerFile}&noop=true&idempotent=false"
+                          timeout: 2000
+                          variableReceive: customer
+                      - unmarshal:
+                          variableSend: customer
+                          variableReceive: customer
+                          json:
+                            library: Jackson
+                      - log:
+                          message: "Order ${body[orderId]} for 
${variable.customer[name]} (${variable.customer[tier]})"
+                      - wireTap:
+                          uri: direct:audit
+
+# Writes a copy of each accepted order to audit/<orderId>.json
+- route:
+    id: audit
+    from:
+      uri: direct
+      parameters:
+        name: audit
+      steps:
+        - setHeader:
+            name: CamelFileName
+            expression:
+              simple:
+                expression: "${body[orderId]}.json"
+        - marshal:
+            json:
+              library: Jackson
+        - to:
+            uri: file
+            parameters:
+              directoryName: audit
+        - log:
+            message: "Audited ${header.CamelFileName}"
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/enrich-and-dedupe/orders/order-1001.json
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/enrich-and-dedupe/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/enrich-and-dedupe/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/enrich-and-dedupe/orders/order-1002.json
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/enrich-and-dedupe/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/enrich-and-dedupe/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/enrich-and-dedupe/orders/order-1003.json
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/enrich-and-dedupe/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/enrich-and-dedupe/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/enrich-and-dedupe/orders/resent-order-1001.json
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/enrich-and-dedupe/orders/resent-order-1001.json
new file mode 100644
index 000000000000..3eb5f038d636
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/enrich-and-dedupe/orders/resent-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}]}

Reply via email to