This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-jbang-examples.git
The following commit(s) were added to refs/heads/main by this push:
new 755f85a CAMEL-24808: rung 2 of the ladder: transform and map (#78)
755f85a is described below
commit 755f85a2e4daad5338784d49852aa4abe65ab280
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Sep 18 16:24:15 2026 +0200
CAMEL-24808: rung 2 of the ladder: transform and map (#78)
Six examples under transform/, all on the shop's order from rung 1:
json-transform (jsonpath and
jq), xml-to-json (Jackson XML and JSON data formats), csv-to-json (CSV data
format, split, one
JSON file per invoice), data-mapping (the order mapped to the courier's
shipment format by
shipment-mapping.groovy), and groovy and xslt rewritten for the shop and
moved in from language/
and transformation/. Each has the ladder README, metadata with level
transform and teaches, a
Citrus YAML test the Camel CLI runs, and a CI matrix entry.
---
.github/workflows/build.yml | 12 +
.gitignore | 4 +-
camel-jbang-example-catalog.json | 254 +++++++++++++++++----
language/groovy/README.md | 69 ------
language/groovy/application.properties | 1 -
language/groovy/groovy.camel.yaml | 25 --
language/groovy/metadata.json | 24 --
transform/csv-to-json/README.md | 68 ++++++
transform/csv-to-json/application.properties | 16 ++
transform/csv-to-json/csv-to-json.camel.yaml | 35 +++
transform/csv-to-json/inbox/invoices.csv | 4 +
transform/csv-to-json/metadata.json | 13 ++
.../csv-to-json/test/csv-to-json.citrus.it.yaml | 21 ++
transform/data-mapping/README.md | 64 ++++++
transform/data-mapping/application.properties | 16 ++
transform/data-mapping/data-mapping.camel.yaml | 28 +++
transform/data-mapping/metadata.json | 13 ++
transform/data-mapping/order.json | 3 +
transform/data-mapping/shipment-mapping.groovy | 12 +
.../data-mapping/test/data-mapping.citrus.it.yaml | 24 ++
transform/groovy/README.md | 66 ++++++
transform/groovy/application.properties | 19 ++
transform/groovy/groovy.camel.yaml | 33 +++
transform/groovy/metadata.json | 13 ++
transform/groovy/order-bad-email.json | 2 +
transform/groovy/order.json | 2 +
transform/groovy/test/groovy.citrus.it.yaml | 24 ++
transform/json-transform/README.md | 67 ++++++
transform/json-transform/application.properties | 16 ++
transform/json-transform/json-transform.camel.yaml | 33 +++
transform/json-transform/metadata.json | 12 +
transform/json-transform/order.json | 3 +
.../test/json-transform.citrus.it.yaml | 23 ++
transform/xml-to-json/README.md | 66 ++++++
transform/xml-to-json/application.properties | 16 ++
transform/xml-to-json/inbox/supplier-order.xml | 7 +
transform/xml-to-json/metadata.json | 12 +
.../xml-to-json/test/xml-to-json.citrus.it.yaml | 16 ++
transform/xml-to-json/xml-to-json.camel.yaml | 21 ++
transform/xslt/README.md | 65 ++++++
transform/xslt/application.properties | 16 ++
transform/xslt/inbox/supplier-order.xml | 7 +
transform/xslt/metadata.json | 11 +
transform/xslt/packing-slip.xsl | 15 ++
transform/xslt/test/xslt.citrus.it.yaml | 23 ++
transform/xslt/xslt.camel.yaml | 17 ++
transformation/xslt/README.md | 61 -----
transformation/xslt/consumer.camel.yaml | 15 --
transformation/xslt/input/account.xml | 9 -
transformation/xslt/metadata.json | 18 --
transformation/xslt/stylesheet.xsl | 11 -
51 files changed, 1150 insertions(+), 275 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 997a84b..2cb9c54 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -38,6 +38,18 @@ jobs:
path: run/nightly-report
- name: Properties and profiles
path: run/properties-and-profiles
+ - name: JSON transform
+ path: transform/json-transform
+ - name: XML to JSON
+ path: transform/xml-to-json
+ - name: CSV to JSON
+ path: transform/csv-to-json
+ - name: Data mapping
+ path: transform/data-mapping
+ - name: Groovy
+ path: transform/groovy
+ - name: XSLT
+ path: transform/xslt
- name: OpenAPI server
path: rest/openapi-server
- name: OpenAPI client
diff --git a/.gitignore b/.gitignore
index b9fad1e..87c36a1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,4 +5,6 @@ target/
.idea/
camel-runner.jar
*.i??
-.citrus-jbang/
\ No newline at end of file
+.citrus-jbang/
+# written by the csv-to-json example at runtime
+outbox/
diff --git a/camel-jbang-example-catalog.json b/camel-jbang-example-catalog.json
index de4521d..909c4b7 100644
--- a/camel-jbang-example-catalog.json
+++ b/camel-jbang-example-catalog.json
@@ -502,38 +502,6 @@
"splitter.camel.yaml"
]
},
- {
- "name": "language/groovy",
- "title": "Groovy",
- "description": "A message with an email address in a header is checked
once by a Groovy expression using Apache Commons Validator, a third-party
library declared with camel.jbang.dependencies, and logged as a valid or
invalid contact.",
- "level": "transform",
- "teaches": {
- "components": [
- "once",
- "log"
- ],
- "eips": [
- "choice"
- ],
- "languages": [
- "groovy",
- "simple"
- ]
- },
- "tags": [
- "language",
- "groovy",
- "eip"
- ],
- "bundled": true,
- "requiresDocker": false,
- "hasCitrusTests": false,
- "files": [
- "README.md",
- "application.properties",
- "groovy.camel.yaml"
- ]
- },
{
"name": "messaging/artemis",
"title": "Apache ActiveMQ Artemis",
@@ -940,30 +908,234 @@
]
},
{
- "name": "transformation/xslt",
- "title": "XSLT Transformation",
- "description": "Every XML file dropped in the input directory is
transformed by stylesheet.xsl and the transformed document is logged.",
+ "name": "transform/csv-to-json",
+ "title": "CSV to JSON",
+ "description": "A CSV of invoices dropped in the inbox directory is
read with the CSV data format, its header line naming the fields, split into
one message per invoice, and each invoice is logged as JSON and written to the
outbox directory as its own file.",
+ "level": "transform",
+ "teaches": {
+ "components": [
+ "file",
+ "log"
+ ],
+ "eips": [
+ "unmarshal",
+ "split",
+ "marshal"
+ ],
+ "dataformats": [
+ "csv",
+ "json"
+ ],
+ "languages": [
+ "simple"
+ ]
+ },
+ "tags": [
+ "shop",
+ "csv",
+ "json",
+ "file",
+ "transformation"
+ ],
+ "bundled": false,
+ "requiresDocker": false,
+ "hasCitrusTests": true,
+ "files": [
+ "README.md",
+ "application.properties",
+ "csv-to-json.camel.yaml",
+ "inbox/invoices.csv",
+ "outbox/INV-2001.json",
+ "outbox/INV-2002.json",
+ "outbox/INV-2003.json"
+ ]
+ },
+ {
+ "name": "transform/data-mapping",
+ "title": "Data mapping",
+ "description": "The shop's order in order.json is mapped field by
field to the courier's shipment format, with renamed fields, a nested
recipient, one parcel per line, a computed total and a service chosen from the
country; the order is parsed to a map, the script shipment-mapping.groovy
builds the shipment, and it is logged as JSON.",
+ "level": "transform",
+ "teaches": {
+ "components": [
+ "timer",
+ "log"
+ ],
+ "eips": [
+ "unmarshal",
+ "setBody",
+ "marshal"
+ ],
+ "languages": [
+ "groovy",
+ "constant"
+ ],
+ "dataformats": [
+ "json"
+ ]
+ },
+ "tags": [
+ "shop",
+ "mapping",
+ "groovy",
+ "json",
+ "transformation"
+ ],
+ "bundled": false,
+ "requiresDocker": false,
+ "hasCitrusTests": true,
+ "files": [
+ "README.md",
+ "application.properties",
+ "data-mapping.camel.yaml",
+ "order.json",
+ "shipment-mapping.groovy"
+ ]
+ },
+ {
+ "name": "transform/groovy",
+ "title": "Groovy",
+ "description": "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 declared in application.properties,
and the log shows one order accepted and one rejected.",
+ "level": "transform",
+ "teaches": {
+ "components": [
+ "timer",
+ "log"
+ ],
+ "eips": [
+ "choice",
+ "unmarshal",
+ "setBody"
+ ],
+ "languages": [
+ "groovy",
+ "simple"
+ ],
+ "dataformats": [
+ "json"
+ ]
+ },
+ "tags": [
+ "shop",
+ "groovy",
+ "validation",
+ "dependencies"
+ ],
+ "bundled": false,
+ "requiresDocker": false,
+ "hasCitrusTests": false,
+ "files": [
+ "README.md",
+ "application.properties",
+ "groovy.camel.yaml",
+ "order-bad-email.json",
+ "order.json"
+ ]
+ },
+ {
+ "name": "transform/json-transform",
+ "title": "JSON transform",
+ "description": "The shop's order in order.json is reshaped for the
warehouse: jsonpath reads the order id and the number of lines into headers, jq
builds the pick list with only sku and quantity per line, and both the order
and the pick list are logged.",
+ "level": "transform",
+ "teaches": {
+ "components": [
+ "timer",
+ "log"
+ ],
+ "eips": [
+ "transform",
+ "setHeader",
+ "setBody"
+ ],
+ "languages": [
+ "jq",
+ "jsonpath",
+ "constant",
+ "simple"
+ ]
+ },
+ "tags": [
+ "shop",
+ "json",
+ "jq",
+ "jsonpath",
+ "transformation"
+ ],
+ "bundled": false,
+ "requiresDocker": false,
+ "hasCitrusTests": true,
+ "files": [
+ "README.md",
+ "application.properties",
+ "json-transform.camel.yaml",
+ "order.json"
+ ]
+ },
+ {
+ "name": "transform/xml-to-json",
+ "title": "XML to JSON",
+ "description": "A supplier's XML order dropped in the inbox directory
is read with the Jackson XML data format and written out as the shop's JSON
with the Jackson JSON data format, both logged; no mapping code, the XML
elements and attributes become JSON fields.",
+ "level": "transform",
+ "teaches": {
+ "components": [
+ "file",
+ "log"
+ ],
+ "eips": [
+ "unmarshal",
+ "marshal"
+ ],
+ "dataformats": [
+ "jacksonXml",
+ "json"
+ ]
+ },
+ "tags": [
+ "shop",
+ "xml",
+ "json",
+ "jackson",
+ "transformation"
+ ],
+ "bundled": false,
+ "requiresDocker": false,
+ "hasCitrusTests": true,
+ "files": [
+ "README.md",
+ "application.properties",
+ "inbox/supplier-order.xml",
+ "xml-to-json.camel.yaml"
+ ]
+ },
+ {
+ "name": "transform/xslt",
+ "title": "XSLT",
+ "description": "A supplier's XML order dropped in the inbox directory
is transformed by the stylesheet packing-slip.xsl into the packing slip the
warehouse prints, one item per line and the total pieces to pick, and the slip
is logged.",
"level": "transform",
"teaches": {
"components": [
"file",
"xslt",
"log"
+ ],
+ "languages": [
+ "simple"
]
},
"tags": [
- "transformation",
+ "shop",
"xml",
- "xslt"
+ "xslt",
+ "transformation"
],
- "bundled": true,
+ "bundled": false,
"requiresDocker": false,
"hasCitrusTests": false,
"files": [
"README.md",
- "consumer.camel.yaml",
- "input/account.xml",
- "stylesheet.xsl"
+ "application.properties",
+ "inbox/supplier-order.xml",
+ "packing-slip.xsl",
+ "xslt.camel.yaml"
]
}
]
diff --git a/language/groovy/README.md b/language/groovy/README.md
deleted file mode 100644
index 8cfaec3..0000000
--- a/language/groovy/README.md
+++ /dev/null
@@ -1,69 +0,0 @@
-## Groovy
-
-This example shows how to use Groovy with extra dependencies in Camel CLI.
-
-The route uses `EmailValidator` from [Apache Commons
Validator](https://commons.apache.org/proper/commons-validator/)
-to validate an email address and route the message accordingly using
content-based routing.
-
-The extra dependency is declared in `application.properties` using the
-`camel.jbang.dependencies` property:
-
-```properties
-camel.jbang.dependencies=commons-validator:commons-validator:1.10.1
-```
-
-### Install JBang
-
-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
-```
-
-Which allows to run Camel CLI with `camel` as shown below.
-
-### How to run
-
-You can run this example using:
-
-```sh
-$ camel run *
-```
-
-To see the invalid email branch, edit `groovy.camel.yaml` and change the
`contactEmail` header in the `once` URI to an invalid value:
-
-```yaml
-uri: once:validate?header.contactEmail=not-a-valid-email
-```
-
-You can also declare dependencies as a modeline comment at the top of the YAML
route file:
-
-```yaml
-#//DEPS commons-validator:commons-validator:1.10.1
-```
-
-Or pass the dependency on the command line:
-
-```sh
-$ camel run groovy.camel.yaml --dep=commons-validator:commons-validator:1.10.1
-```
-
-### 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/language/groovy/application.properties
b/language/groovy/application.properties
deleted file mode 100644
index e4a4b82..0000000
--- a/language/groovy/application.properties
+++ /dev/null
@@ -1 +0,0 @@
-camel.jbang.dependencies=commons-validator:commons-validator:1.10.1
diff --git a/language/groovy/groovy.camel.yaml
b/language/groovy/groovy.camel.yaml
deleted file mode 100644
index c183a83..0000000
--- a/language/groovy/groovy.camel.yaml
+++ /dev/null
@@ -1,25 +0,0 @@
-- route:
- from:
- uri: once
- parameters:
- name: validate
- steps:
- - setHeader:
- name: contactEmail
- expression:
- constant:
- expression: [email protected]
- - choice:
- when:
- - expression:
- groovy:
- expression: |-
- import
org.apache.commons.validator.routines.EmailValidator
-
EmailValidator.getInstance().isValid(request.headers['contactEmail'])
- steps:
- - log:
- message: "Valid contact: ${header.contactEmail}"
- otherwise:
- steps:
- - log:
- message: "Invalid email: ${header.contactEmail}"
diff --git a/language/groovy/metadata.json b/language/groovy/metadata.json
deleted file mode 100644
index 9e98b16..0000000
--- a/language/groovy/metadata.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "title": "Groovy",
- "description": "A message with an email address in a header is checked
once by a Groovy expression using Apache Commons Validator, a third-party
library declared with camel.jbang.dependencies, and logged as a valid or
invalid contact.",
- "level": "transform",
- "teaches": {
- "components": [
- "once",
- "log"
- ],
- "eips": [
- "choice"
- ],
- "languages": [
- "groovy",
- "simple"
- ]
- },
- "tags": [
- "language",
- "groovy",
- "eip"
- ],
- "bundled": true
-}
diff --git a/transform/csv-to-json/README.md b/transform/csv-to-json/README.md
new file mode 100644
index 0000000..6069d92
--- /dev/null
+++ b/transform/csv-to-json/README.md
@@ -0,0 +1,68 @@
+# 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
+
+<!-- see installation instructions in ../../install.adoc -->
+
+## 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/transform/csv-to-json/application.properties
b/transform/csv-to-json/application.properties
new file mode 100644
index 0000000..13a8339
--- /dev/null
+++ b/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/transform/csv-to-json/csv-to-json.camel.yaml
b/transform/csv-to-json/csv-to-json.camel.yaml
new file mode 100644
index 0000000..4fdf03d
--- /dev/null
+++ b/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/transform/csv-to-json/inbox/invoices.csv
b/transform/csv-to-json/inbox/invoices.csv
new file mode 100644
index 0000000..8a28913
--- /dev/null
+++ b/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/transform/csv-to-json/metadata.json
b/transform/csv-to-json/metadata.json
new file mode 100644
index 0000000..1448a69
--- /dev/null
+++ b/transform/csv-to-json/metadata.json
@@ -0,0 +1,13 @@
+{
+ "title": "CSV to JSON",
+ "description": "A CSV of invoices dropped in the inbox directory is read
with the CSV data format, its header line naming the fields, split into one
message per invoice, and each invoice is logged as JSON and written to the
outbox directory as its own file.",
+ "level": "transform",
+ "teaches": {
+ "components": ["file", "log"],
+ "eips": ["unmarshal", "split", "marshal"],
+ "dataformats": ["csv", "json"],
+ "languages": ["simple"]
+ },
+ "tags": ["shop", "csv", "json", "file", "transformation"],
+ "bundled": false
+}
diff --git a/transform/csv-to-json/test/csv-to-json.citrus.it.yaml
b/transform/csv-to-json/test/csv-to-json.citrus.it.yaml
new file mode 100644
index 0000000..87a05fc
--- /dev/null
+++ b/transform/csv-to-json/test/csv-to-json.citrus.it.yaml
@@ -0,0 +1,21 @@
+name: csv-to-json-test
+description: The csv-to-json example logs what its README promises
+actions:
+ - camel:
+ jbang:
+ run:
+ integration:
+ name: "csv-to-json"
+ file: "../csv-to-json.camel.yaml"
+ systemProperties:
+ file: "../application.properties"
+ - camel:
+ jbang:
+ verify:
+ integration: "csv-to-json"
+ logMessage: "Invoice INV-2001"
+ - camel:
+ jbang:
+ verify:
+ integration: "csv-to-json"
+ logMessage: "Invoice INV-2003"
diff --git a/transform/data-mapping/README.md b/transform/data-mapping/README.md
new file mode 100644
index 0000000..ce49606
--- /dev/null
+++ b/transform/data-mapping/README.md
@@ -0,0 +1,64 @@
+# 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
+
+<!-- see installation instructions in ../../install.adoc -->
+
+## 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/transform/data-mapping/application.properties
b/transform/data-mapping/application.properties
new file mode 100644
index 0000000..13a8339
--- /dev/null
+++ b/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/transform/data-mapping/data-mapping.camel.yaml
b/transform/data-mapping/data-mapping.camel.yaml
new file mode 100644
index 0000000..57deb54
--- /dev/null
+++ b/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/transform/data-mapping/metadata.json
b/transform/data-mapping/metadata.json
new file mode 100644
index 0000000..36f686c
--- /dev/null
+++ b/transform/data-mapping/metadata.json
@@ -0,0 +1,13 @@
+{
+ "title": "Data mapping",
+ "description": "The shop's order in order.json is mapped field by field to
the courier's shipment format, with renamed fields, a nested recipient, one
parcel per line, a computed total and a service chosen from the country; the
order is parsed to a map, the script shipment-mapping.groovy builds the
shipment, and it is logged as JSON.",
+ "level": "transform",
+ "teaches": {
+ "components": ["timer", "log"],
+ "eips": ["unmarshal", "setBody", "marshal"],
+ "languages": ["groovy", "constant"],
+ "dataformats": ["json"]
+ },
+ "tags": ["shop", "mapping", "groovy", "json", "transformation"],
+ "bundled": false
+}
diff --git a/transform/data-mapping/order.json
b/transform/data-mapping/order.json
new file mode 100644
index 0000000..2327db5
--- /dev/null
+++ b/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/transform/data-mapping/shipment-mapping.groovy
b/transform/data-mapping/shipment-mapping.groovy
new file mode 100644
index 0000000..679609a
--- /dev/null
+++ b/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/transform/data-mapping/test/data-mapping.citrus.it.yaml
b/transform/data-mapping/test/data-mapping.citrus.it.yaml
new file mode 100644
index 0000000..7a9eb37
--- /dev/null
+++ b/transform/data-mapping/test/data-mapping.citrus.it.yaml
@@ -0,0 +1,24 @@
+name: data-mapping-test
+description: The data-mapping example logs what its README promises
+actions:
+ - camel:
+ jbang:
+ run:
+ integration:
+ name: "data-mapping"
+ file: "../data-mapping.camel.yaml"
+ systemProperties:
+ file: "../application.properties"
+ resources:
+ - "order.json"
+ - "shipment-mapping.groovy"
+ - camel:
+ jbang:
+ verify:
+ integration: "data-mapping"
+ logMessage: "\"shipmentRef\":\"SHIP-1001\""
+ - camel:
+ jbang:
+ verify:
+ integration: "data-mapping"
+ logMessage: "\"totalPieces\":3"
diff --git a/transform/groovy/README.md b/transform/groovy/README.md
new file mode 100644
index 0000000..35ba363
--- /dev/null
+++ b/transform/groovy/README.md
@@ -0,0 +1,66 @@
+# 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: [email protected] is a
valid address
+INFO ... groovy.camel.yaml:32 : Order ORD-1002 rejected: not-an-address is not
an email address
+```
+
+## Install Camel CLI
+
+<!-- see installation instructions in ../../install.adoc -->
+
+## 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/transform/groovy/application.properties
b/transform/groovy/application.properties
new file mode 100644
index 0000000..6fda14e
--- /dev/null
+++ b/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/transform/groovy/groovy.camel.yaml
b/transform/groovy/groovy.camel.yaml
new file mode 100644
index 0000000..097bffc
--- /dev/null
+++ b/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/transform/groovy/metadata.json b/transform/groovy/metadata.json
new file mode 100644
index 0000000..b981765
--- /dev/null
+++ b/transform/groovy/metadata.json
@@ -0,0 +1,13 @@
+{
+ "title": "Groovy",
+ "description": "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 declared in application.properties, and the
log shows one order accepted and one rejected.",
+ "level": "transform",
+ "teaches": {
+ "components": ["timer", "log"],
+ "eips": ["choice", "unmarshal", "setBody"],
+ "languages": ["groovy", "simple"],
+ "dataformats": ["json"]
+ },
+ "tags": ["shop", "groovy", "validation", "dependencies"],
+ "bundled": false
+}
diff --git a/transform/groovy/order-bad-email.json
b/transform/groovy/order-bad-email.json
new file mode 100644
index 0000000..687988a
--- /dev/null
+++ b/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/transform/groovy/order.json b/transform/groovy/order.json
new file mode 100644
index 0000000..1125f80
--- /dev/null
+++ b/transform/groovy/order.json
@@ -0,0 +1,2 @@
+{"orderId": "ORD-1001", "customer": "C-482", "email": "[email protected]",
"country": "DK",
+ "lines": [{"sku": "CAMEL-TSHIRT", "qty": 2, "price": 19.95}], "status":
"paid"}
diff --git a/transform/groovy/test/groovy.citrus.it.yaml
b/transform/groovy/test/groovy.citrus.it.yaml
new file mode 100644
index 0000000..f4148cb
--- /dev/null
+++ b/transform/groovy/test/groovy.citrus.it.yaml
@@ -0,0 +1,24 @@
+name: groovy-test
+description: The groovy example logs what its README promises
+actions:
+ - camel:
+ jbang:
+ run:
+ integration:
+ name: "groovy"
+ file: "../groovy.camel.yaml"
+ systemProperties:
+ file: "../application.properties"
+ resources:
+ - "order.json"
+ - "order-bad-email.json"
+ - camel:
+ jbang:
+ verify:
+ integration: "groovy"
+ logMessage: "Order ORD-1001 accepted"
+ - camel:
+ jbang:
+ verify:
+ integration: "groovy"
+ logMessage: "Order ORD-1002 rejected"
diff --git a/transform/json-transform/README.md
b/transform/json-transform/README.md
new file mode 100644
index 0000000..9b79df3
--- /dev/null
+++ b/transform/json-transform/README.md
@@ -0,0 +1,67 @@
+# 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
+
+<!-- see installation instructions in ../../install.adoc -->
+
+## 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/transform/json-transform/application.properties
b/transform/json-transform/application.properties
new file mode 100644
index 0000000..13a8339
--- /dev/null
+++ b/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/transform/json-transform/json-transform.camel.yaml
b/transform/json-transform/json-transform.camel.yaml
new file mode 100644
index 0000000..933bb41
--- /dev/null
+++ b/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/transform/json-transform/metadata.json
b/transform/json-transform/metadata.json
new file mode 100644
index 0000000..18a5da5
--- /dev/null
+++ b/transform/json-transform/metadata.json
@@ -0,0 +1,12 @@
+{
+ "title": "JSON transform",
+ "description": "The shop's order in order.json is reshaped for the
warehouse: jsonpath reads the order id and the number of lines into headers, jq
builds the pick list with only sku and quantity per line, and both the order
and the pick list are logged.",
+ "level": "transform",
+ "teaches": {
+ "components": ["timer", "log"],
+ "eips": ["transform", "setHeader", "setBody"],
+ "languages": ["jq", "jsonpath", "constant", "simple"]
+ },
+ "tags": ["shop", "json", "jq", "jsonpath", "transformation"],
+ "bundled": false
+}
diff --git a/transform/json-transform/order.json
b/transform/json-transform/order.json
new file mode 100644
index 0000000..2327db5
--- /dev/null
+++ b/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/transform/json-transform/test/json-transform.citrus.it.yaml
b/transform/json-transform/test/json-transform.citrus.it.yaml
new file mode 100644
index 0000000..906e206
--- /dev/null
+++ b/transform/json-transform/test/json-transform.citrus.it.yaml
@@ -0,0 +1,23 @@
+name: json-transform-test
+description: The json-transform example logs what its README promises
+actions:
+ - camel:
+ jbang:
+ run:
+ integration:
+ name: "json-transform"
+ file: "../json-transform.camel.yaml"
+ systemProperties:
+ file: "../application.properties"
+ resources:
+ - "order.json"
+ - camel:
+ jbang:
+ verify:
+ integration: "json-transform"
+ logMessage: "\"order\" : \"ORD-1001\""
+ - camel:
+ jbang:
+ verify:
+ integration: "json-transform"
+ logMessage: "\"sku\" : \"CAMEL-TSHIRT\""
diff --git a/transform/xml-to-json/README.md b/transform/xml-to-json/README.md
new file mode 100644
index 0000000..4784dcc
--- /dev/null
+++ b/transform/xml-to-json/README.md
@@ -0,0 +1,66 @@
+# XML to JSON
+
+The supplier sends orders as XML, the shop works in JSON. The Jackson XML data
format reads the XML into a map
+and the Jackson JSON data format writes the map out: no mapping code, elements
and attributes become fields.
+
+## What you will see
+
+```text
+INFO ... xml-to-json.camel.yaml:13 : Supplier order as XML: <?xml
version="1.0" encoding="UTF-8"?><order id="ORD-1001" country="DK">...
+INFO ... xml-to-json.camel.yaml:20 : The same order as JSON:
{"id":"ORD-1001","country":"DK","customer":"C-482","line":[{"sku":"CAMEL-TSHIRT","qty":"2","price":"19.95"},{"sku":"CAMEL-MUG","qty":"1","price":"9.50"}],"status":"paid"}
+```
+
+## Install Camel CLI
+
+<!-- see installation instructions in ../../install.adoc -->
+
+## 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/transform/xml-to-json/application.properties
b/transform/xml-to-json/application.properties
new file mode 100644
index 0000000..13a8339
--- /dev/null
+++ b/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/transform/xml-to-json/inbox/supplier-order.xml
b/transform/xml-to-json/inbox/supplier-order.xml
new file mode 100644
index 0000000..67fc5e6
--- /dev/null
+++ b/transform/xml-to-json/inbox/supplier-order.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<order id="ORD-1001" country="DK">
+ <customer>C-482</customer>
+ <line sku="CAMEL-TSHIRT" qty="2" price="19.95"/>
+ <line sku="CAMEL-MUG" qty="1" price="9.50"/>
+ <status>paid</status>
+</order>
diff --git a/transform/xml-to-json/metadata.json
b/transform/xml-to-json/metadata.json
new file mode 100644
index 0000000..5806389
--- /dev/null
+++ b/transform/xml-to-json/metadata.json
@@ -0,0 +1,12 @@
+{
+ "title": "XML to JSON",
+ "description": "A supplier's XML order dropped in the inbox directory is
read with the Jackson XML data format and written out as the shop's JSON with
the Jackson JSON data format, both logged; no mapping code, the XML elements
and attributes become JSON fields.",
+ "level": "transform",
+ "teaches": {
+ "components": ["file", "log"],
+ "eips": ["unmarshal", "marshal"],
+ "dataformats": ["jacksonXml", "json"]
+ },
+ "tags": ["shop", "xml", "json", "jackson", "transformation"],
+ "bundled": false
+}
diff --git a/transform/xml-to-json/test/xml-to-json.citrus.it.yaml
b/transform/xml-to-json/test/xml-to-json.citrus.it.yaml
new file mode 100644
index 0000000..f46df6a
--- /dev/null
+++ b/transform/xml-to-json/test/xml-to-json.citrus.it.yaml
@@ -0,0 +1,16 @@
+name: xml-to-json-test
+description: The xml-to-json example logs what its README promises
+actions:
+ - camel:
+ jbang:
+ run:
+ integration:
+ name: "xml-to-json"
+ file: "../xml-to-json.camel.yaml"
+ systemProperties:
+ file: "../application.properties"
+ - camel:
+ jbang:
+ verify:
+ integration: "xml-to-json"
+ logMessage: "The same order as JSON: {\"id\":\"ORD-1001\""
diff --git a/transform/xml-to-json/xml-to-json.camel.yaml
b/transform/xml-to-json/xml-to-json.camel.yaml
new file mode 100644
index 0000000..a4c7225
--- /dev/null
+++ b/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/transform/xslt/README.md b/transform/xslt/README.md
new file mode 100644
index 0000000..47df8a3
--- /dev/null
+++ b/transform/xslt/README.md
@@ -0,0 +1,65 @@
+# XSLT
+
+The supplier's XML order becomes the packing slip the warehouse prints,
transformed by a stylesheet: one item per
+line and the total pieces to pick.
+
+## What you will see
+
+```text
+INFO ... xslt.camel.yaml:16 : Packing slip:
+<?xml version="1.0" encoding="UTF-8"?><packingSlip order="ORD-1001"
customer="C-482" country="DK">
+ <item sku="CAMEL-TSHIRT" pieces="2"/>
+ <item sku="CAMEL-MUG" pieces="1"/>
+ <pieces>3</pieces>
+</packingSlip>
+```
+
+## Install Camel CLI
+
+<!-- see installation instructions in ../../install.adoc -->
+
+## 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/transform/xslt/application.properties
b/transform/xslt/application.properties
new file mode 100644
index 0000000..13a8339
--- /dev/null
+++ b/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/transform/xslt/inbox/supplier-order.xml
b/transform/xslt/inbox/supplier-order.xml
new file mode 100644
index 0000000..67fc5e6
--- /dev/null
+++ b/transform/xslt/inbox/supplier-order.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<order id="ORD-1001" country="DK">
+ <customer>C-482</customer>
+ <line sku="CAMEL-TSHIRT" qty="2" price="19.95"/>
+ <line sku="CAMEL-MUG" qty="1" price="9.50"/>
+ <status>paid</status>
+</order>
diff --git a/transform/xslt/metadata.json b/transform/xslt/metadata.json
new file mode 100644
index 0000000..cd31257
--- /dev/null
+++ b/transform/xslt/metadata.json
@@ -0,0 +1,11 @@
+{
+ "title": "XSLT",
+ "description": "A supplier's XML order dropped in the inbox directory is
transformed by the stylesheet packing-slip.xsl into the packing slip the
warehouse prints, one item per line and the total pieces to pick, and the slip
is logged.",
+ "level": "transform",
+ "teaches": {
+ "components": ["file", "xslt", "log"],
+ "languages": ["simple"]
+ },
+ "tags": ["shop", "xml", "xslt", "transformation"],
+ "bundled": false
+}
diff --git a/transform/xslt/packing-slip.xsl b/transform/xslt/packing-slip.xsl
new file mode 100644
index 0000000..2208591
--- /dev/null
+++ b/transform/xslt/packing-slip.xsl
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:output method="xml" indent="yes"/>
+
+ <!-- The supplier's order becomes the packing slip the warehouse prints: one
item per line, and
+ the number of pieces to pick in total. -->
+ <xsl:template match="/order">
+ <packingSlip order="{@id}" customer="{customer}" country="{@country}">
+ <xsl:for-each select="line">
+ <item sku="{@sku}" pieces="{@qty}"/>
+ </xsl:for-each>
+ <pieces><xsl:value-of select="sum(line/@qty)"/></pieces>
+ </packingSlip>
+ </xsl:template>
+</xsl:stylesheet>
diff --git a/transform/xslt/test/xslt.citrus.it.yaml
b/transform/xslt/test/xslt.citrus.it.yaml
new file mode 100644
index 0000000..b1078a0
--- /dev/null
+++ b/transform/xslt/test/xslt.citrus.it.yaml
@@ -0,0 +1,23 @@
+name: xslt-test
+description: The xslt example logs what its README promises
+actions:
+ - camel:
+ jbang:
+ run:
+ integration:
+ name: "xslt"
+ file: "../xslt.camel.yaml"
+ systemProperties:
+ file: "../application.properties"
+ resources:
+ - "packing-slip.xsl"
+ - camel:
+ jbang:
+ verify:
+ integration: "xslt"
+ logMessage: "<packingSlip order=\"ORD-1001\""
+ - camel:
+ jbang:
+ verify:
+ integration: "xslt"
+ logMessage: "<pieces>3</pieces>"
diff --git a/transform/xslt/xslt.camel.yaml b/transform/xslt/xslt.camel.yaml
new file mode 100644
index 0000000..9063f8d
--- /dev/null
+++ b/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/transformation/xslt/README.md b/transformation/xslt/README.md
deleted file mode 100644
index 1c8044f..0000000
--- a/transformation/xslt/README.md
+++ /dev/null
@@ -1,61 +0,0 @@
-## XSLT Transformation
-
-This example shows a basic XML transformation using XSLT style sheet.
-
-### Install JBang
-
-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
-```
-
-Which allows to run Camel CLI with `camel` as shown below.
-
-### How to run
-
-Then you can run this example using:
-
-```sh
-$ camel run *
-```
-
-This reads the XML input file from _./input/account.xml_ and applies XSL
transformation.
-
-### Live updates of message transformation
-
-You can do live changes to the stylesheet and see the output in real-time with
Camel CLI by running:
-
-```bash
-$ camel transform message --body=file:input/account.xml --component=xslt
--template=file:stylesheet.xsl --pretty --watch
-```
-
-You can then edit the `stylesheet.xsl` file, and save the file, and watch the
terminal for updated result.
-
-### Run directly from GitHub
-
-The example can also be run directly by referring to the GitHub URL as shown:
-
-```sh
-$ camel run https://github.com/apache/camel-jbang-examples/tree/main/xslt
-```
-
-### 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/transformation/xslt/consumer.camel.yaml
b/transformation/xslt/consumer.camel.yaml
deleted file mode 100644
index eace713..0000000
--- a/transformation/xslt/consumer.camel.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-- route:
- from:
- uri: file
- parameters:
- directoryName: input
- fileName: account.xml
- noop: true
- steps:
- - to:
- uri: xslt
- parameters:
- resourceUri: stylesheet.xsl
- - log:
- message: "Transformed data:\\n ${prettyBody}"
-
diff --git a/transformation/xslt/input/account.xml
b/transformation/xslt/input/account.xml
deleted file mode 100644
index 4b3dc7e..0000000
--- a/transformation/xslt/input/account.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<hash>
- <id type="integer">1369</id>
- <uid>8c946e1a-fdc5-40d3-9098-44271bdfad65</uid>
- <account-number>8673088731</account-number>
- <iban>GB38EFUA27474531363797</iban>
- <bank-name>ABN AMRO MEZZANINE (UK) LIMITED</bank-name>
- <routing-number>053228004</routing-number>
- <swift-bic>AACCGB21</swift-bic>
-</hash>
\ No newline at end of file
diff --git a/transformation/xslt/metadata.json
b/transformation/xslt/metadata.json
deleted file mode 100644
index aeb8fbd..0000000
--- a/transformation/xslt/metadata.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "title": "XSLT Transformation",
- "description": "Every XML file dropped in the input directory is
transformed by stylesheet.xsl and the transformed document is logged.",
- "level": "transform",
- "teaches": {
- "components": [
- "file",
- "xslt",
- "log"
- ]
- },
- "tags": [
- "transformation",
- "xml",
- "xslt"
- ],
- "bundled": true
-}
diff --git a/transformation/xslt/stylesheet.xsl
b/transformation/xslt/stylesheet.xsl
deleted file mode 100644
index b566f59..0000000
--- a/transformation/xslt/stylesheet.xsl
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version = "1.0"?>
-<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
-
- <xsl:template match="/">
- <bank>
- <name><xsl:value-of select="/hash/bank-name/text()"/></name>
- <bic><xsl:value-of select="/hash/swift-bic/text()"/></bic>
- </bank>
- </xsl:template>
-
-</xsl:stylesheet>