From 42bacbd46d6d2177b23d639fd54ffcfb21198efa Mon Sep 17 00:00:00 2001 From: Claus Ibsen Date: Fri, 2 Oct 2026 11:07:59 +0200 Subject: [PATCH] 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) Signed-off-by: Claus Ibsen --- .../examples/camel-jbang-example-catalog.json | 56 ++--- .../connect/file-processing/README.md | 78 +++++++ .../file-processing/application.properties | 16 ++ .../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 + .../file-processing/samples/note-2005.txt | 1 + .../examples/connect/http-client/README.md | 77 +++++++ .../http-client/application.properties | 18 ++ .../http-client/http-client.camel.yaml | 117 ++++++++++ .../http-client/orders/order-1001.json | 2 + .../http-client/orders/order-1002.json | 2 + .../http-client/orders/order-1003.json | 2 + .../examples/connect/http-client/stock.json | 5 + .../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 + .../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 ++++++++++++++++++ .../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 ++++++++++++++++++ .../contracts/openapi-server/stock.json | 5 + .../fail-well/circuit-breaker/README.md | 3 +- .../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 ++++++- .../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 +- .../quick-start/routes/routes.camel.yaml | 12 +- .../examples/quick-start/splitter/README.md | 74 +++++-- .../examples/quick-start/timer-log/README.md | 70 +++++- .../timer-log/application.properties | 17 ++ .../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 + .../route/content-based-router/README.md | 67 ++++++ .../application.properties | 16 ++ .../content-based-router.camel.yaml | 33 +++ .../orders/order-1001.json | 2 + .../orders/order-1002.json | 2 + .../orders/order-1003.json | 2 + .../route/filter-and-multicast/README.md | 69 ++++++ .../application.properties | 16 ++ .../filter-and-multicast.camel.yaml | 50 +++++ .../orders/order-1001.json | 2 + .../orders/order-1002.json | 2 + .../orders/order-1003.json | 2 + .../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 +++ .../nightly-report/nightly-report.camel.yaml | 16 ++ .../run/order-generator/OrderNumber.java | 20 ++ .../examples/run/order-generator/README.md | 74 +++++++ .../order-generator/application.properties | 21 ++ .../examples/run/order-generator/beans.yaml | 5 + .../order-generator.camel.yaml | 24 +++ .../run/properties-and-profiles/README.md | 74 +++++++ .../application-prod.properties | 21 ++ .../application.properties | 21 ++ .../properties-and-profiles.camel.yaml | 16 ++ .../examples/transform/csv-to-json/README.md | 69 ++++++ .../csv-to-json/application.properties | 16 ++ .../csv-to-json/csv-to-json.camel.yaml | 35 +++ .../transform/csv-to-json/inbox/invoices.csv | 4 + .../examples/transform/data-mapping/README.md | 65 ++++++ .../data-mapping/application.properties | 16 ++ .../data-mapping/data-mapping.camel.yaml | 28 +++ .../transform/data-mapping/order.json | 3 + .../data-mapping/shipment-mapping.groovy | 12 ++ .../examples/transform/groovy/README.md | 67 ++++++ .../transform/groovy/application.properties | 19 ++ .../transform/groovy/groovy.camel.yaml | 33 +++ .../transform/groovy/order-bad-email.json | 2 + .../examples/transform/groovy/order.json | 2 + .../transform/json-transform/README.md | 68 ++++++ .../json-transform/application.properties | 16 ++ .../json-transform/json-transform.camel.yaml | 33 +++ .../transform/json-transform/order.json | 3 + .../examples/transform/xml-to-json/README.md | 67 ++++++ .../xml-to-json/application.properties | 16 ++ .../xml-to-json/inbox/supplier-order.xml | 7 + .../xml-to-json/xml-to-json.camel.yaml | 21 ++ .../examples/transform/xslt/README.md | 66 ++++++ .../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 ++- .../test/java/camel/example/OrderNumber.java | 40 ++++ 114 files changed, 3637 insertions(+), 145 deletions(-) create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/README.md create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/application.properties create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/file-processing.camel.yaml create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/samples/invoice-2001.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/samples/invoice-2002.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/samples/invoice-2003.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/samples/invoice-2004.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/file-processing/samples/note-2005.txt create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/http-client/README.md create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/http-client/application.properties create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/http-client/http-client.camel.yaml create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/http-client/orders/order-1001.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/http-client/orders/order-1002.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/http-client/orders/order-1003.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/http-client/stock.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/stock-api/README.md create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/stock-api/application.properties create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/stock-api/stock-api.camel.yaml create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/connect/stock-api/stock.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-client/README.md create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-client/application.properties create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-client/openapi-client.camel.yaml create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-client/orders/order-1001.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-client/orders/order-1002.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-client/orders/order-1003.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-client/stock-api.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-server/README.md create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-server/application.properties create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-server/openapi-server.camel.yaml create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-server/stock-api.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/contracts/openapi-server/stock.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/error-handling/README.md create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/error-handling/application.properties create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/error-handling/error-handling.camel.yaml create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/error-handling/orders/order-1001.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/error-handling/orders/order-1002.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/fail-well/error-handling/orders/order-1003.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/aggregator/README.md create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/aggregator/aggregator.camel.yaml create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/aggregator/application.properties create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/aggregator/orders/order-1001.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/aggregator/orders/order-1002.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/aggregator/orders/order-1003.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/content-based-router/README.md create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/content-based-router/application.properties create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/content-based-router/content-based-router.camel.yaml create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/content-based-router/orders/order-1001.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/content-based-router/orders/order-1002.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/content-based-router/orders/order-1003.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/filter-and-multicast/README.md create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/filter-and-multicast/application.properties create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/filter-and-multicast/filter-and-multicast.camel.yaml create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/filter-and-multicast/orders/order-1001.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/filter-and-multicast/orders/order-1002.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/filter-and-multicast/orders/order-1003.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/order-lines/README.md create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/order-lines/application.properties create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/order-lines/order-lines.camel.yaml create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/order-lines/orders/order-1001.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/order-lines/orders/order-1002.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route/order-lines/orders/order-1003.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/nightly-report/README.md create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/nightly-report/application.properties create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/nightly-report/nightly-report.camel.yaml create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/order-generator/OrderNumber.java create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/order-generator/README.md create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/order-generator/application.properties create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/order-generator/beans.yaml create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/order-generator/order-generator.camel.yaml create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/properties-and-profiles/README.md create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/properties-and-profiles/application-prod.properties create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/properties-and-profiles/application.properties create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/run/properties-and-profiles/properties-and-profiles.camel.yaml create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/csv-to-json/README.md create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/csv-to-json/application.properties create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/csv-to-json/csv-to-json.camel.yaml create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/csv-to-json/inbox/invoices.csv create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/data-mapping/README.md create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/data-mapping/application.properties create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/data-mapping/data-mapping.camel.yaml create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/data-mapping/order.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/data-mapping/shipment-mapping.groovy create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/groovy/README.md create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/groovy/application.properties create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/groovy/groovy.camel.yaml create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/groovy/order-bad-email.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/groovy/order.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/json-transform/README.md create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/json-transform/application.properties create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/json-transform/json-transform.camel.yaml create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/json-transform/order.json create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xml-to-json/README.md create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xml-to-json/application.properties create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xml-to-json/inbox/supplier-order.xml create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xml-to-json/xml-to-json.camel.yaml create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xslt/README.md create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xslt/application.properties create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xslt/inbox/supplier-order.xml create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xslt/packing-slip.xsl create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xslt/xslt.camel.yaml create mode 100644 dsl/camel-jbang/camel-jbang-core/src/test/java/camel/example/OrderNumber.java 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 0434afa3861f2..dbabdbe6816a2 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 0000000000000..3444ea6f50f35 --- /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//` 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 0000000000000..13a83393a9124 --- /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 0000000000000..fd9446c3ce86d --- /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 0000000000000..51d79d522de04 --- /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 0000000000000..0945236038052 --- /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 0000000000000..a084e66f4dd10 --- /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 0000000000000..761c24828f0a1 --- /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 0000000000000..e82a2d8505097 --- /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 0000000000000..f03565365e6f9 --- /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 0000000000000..5d54c44f16dc0 --- /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 0000000000000..79f40db5b2957 --- /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 0000000000000..3eb5f038d6366 --- /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 0000000000000..bb7aa0e9ff1ca --- /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 0000000000000..9edab768cbd74 --- /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 0000000000000..efab5de3aa666 --- /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 0000000000000..10cbd834a40f9 --- /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 0000000000000..5d54c44f16dc0 --- /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 0000000000000..b5464b8032048 --- /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 0000000000000..efab5de3aa666 --- /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 0000000000000..bee062478acc3 --- /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 0000000000000..cfc23ac075c7e --- /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 0000000000000..f45644fce4462 --- /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 0000000000000..3eb5f038d6366 --- /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 0000000000000..bb7aa0e9ff1ca --- /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 0000000000000..9edab768cbd74 --- /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 0000000000000..279b68124da79 --- /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 0000000000000..04fbb1092666a --- /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 0000000000000..5d54c44f16dc0 --- /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 0000000000000..23d364e09707a --- /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 0000000000000..279b68124da79 --- /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 0000000000000..efab5de3aa666 --- /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 f2a99ddcb55d2..c38d1596fe7e4 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 - +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 0000000000000..00b24b2a213f0 --- /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 0000000000000..13a83393a9124 --- /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 0000000000000..8d9f006001105 --- /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 0000000000000..3eb5f038d6366 --- /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 0000000000000..bb7aa0e9ff1ca --- /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 0000000000000..9edab768cbd74 --- /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 db3dac32b83cc..2fc82eea0e840 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 f3ee4076eb177..4abd98e0127a9 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 bdb9a45ec3a62..605356270bcf8 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 8157bcfa24ed6..c8e1d74f6af4b 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 9049d38c3ecf8..a6616c43986d1 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 a984e77987708..ec4497028a003 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 a15415f483a18..76ae52820ca47 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 ea673ccfe3aa8..a07be02973d65 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 f773ce1bad555..5b44f204ef822 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 370577aacb69e..5cc0a54df8e42 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 0000000000000..56789ff9d1e10 --- /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 0000000000000..5148ba54b6b8b --- /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 0000000000000..13a83393a9124 --- /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 0000000000000..3eb5f038d6366 --- /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 0000000000000..bb7aa0e9ff1ca --- /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 0000000000000..9edab768cbd74 --- /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 0000000000000..4bbb6390c7ff5 --- /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 0000000000000..13a83393a9124 --- /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 0000000000000..017afeea9279f --- /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 0000000000000..3eb5f038d6366 --- /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 0000000000000..bb7aa0e9ff1ca --- /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 0000000000000..9edab768cbd74 --- /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 0000000000000..fd45f53480912 --- /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 0000000000000..13a83393a9124 --- /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 0000000000000..1559e2bb13334 --- /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 0000000000000..3eb5f038d6366 --- /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 0000000000000..bb7aa0e9ff1ca --- /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 0000000000000..9edab768cbd74 --- /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 0000000000000..2659d8d2ebd34 --- /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 0000000000000..13a83393a9124 --- /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 0000000000000..9f8c2ad5c3d5f --- /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 0000000000000..3eb5f038d6366 --- /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 0000000000000..bb7aa0e9ff1ca --- /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 0000000000000..9edab768cbd74 --- /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 0000000000000..5b0266512e7ea --- /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 0000000000000..b2f7fada5bd2e --- /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 0000000000000..73f0cfb27d8e4 --- /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 0000000000000..e1e963c051d66 --- /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 0000000000000..18fc09f74ec82 --- /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 0000000000000..bf07ed7c84f1e --- /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 0000000000000..444deab17be6b --- /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 0000000000000..514f42b966b86 --- /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 0000000000000..a5461677e08dd --- /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 0000000000000..33d3d1cdeb862 --- /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 0000000000000..050a9e5f3e886 --- /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 0000000000000..6882e660a65d0 --- /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 0000000000000..8ed95b6f73014 --- /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 0000000000000..13a83393a9124 --- /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 0000000000000..4fdf03dba037d --- /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 0000000000000..8a2891332396d --- /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 0000000000000..7ed0376e65b91 --- /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 0000000000000..13a83393a9124 --- /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 0000000000000..57deb54c5daba --- /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 0000000000000..2327db569ca52 --- /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 0000000000000..679609aebd42a --- /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 0000000000000..5a4909701348a --- /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 0000000000000..6fda14e7eb169 --- /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 0000000000000..097bffc71d4b9 --- /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 0000000000000..687988af1d627 --- /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 0000000000000..1125f80eb9197 --- /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 0000000000000..f7e3d2cc25467 --- /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 0000000000000..13a83393a9124 --- /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 0000000000000..933bb41d7f350 --- /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 0000000000000..2327db569ca52 --- /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 0000000000000..b7e7b725309c7 --- /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: ... +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 0000000000000..13a83393a9124 --- /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 0000000000000..67fc5e6f631c0 --- /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 @@ + + + C-482 + + + paid + 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 0000000000000..a4c7225f7cddd --- /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 0000000000000..4d799391f234c --- /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: + + + + 3 + +``` + +## 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 0000000000000..13a83393a9124 --- /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 0000000000000..67fc5e6f631c0 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xslt/inbox/supplier-order.xml @@ -0,0 +1,7 @@ + + + C-482 + + + paid + 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 0000000000000..22085915d74a3 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transform/xslt/packing-slip.xsl @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + 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 0000000000000..9063f8d37e51a --- /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 ae48efcb5fd7c..264b731b4e3bb 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 0000000000000..7f5dad0be5d74 --- /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(); + } +}