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

jamesnetherton pushed a commit to branch camel-quarkus-main
in repository https://gitbox.apache.org/repos/asf/camel-quarkus-examples.git


The following commit(s) were added to refs/heads/camel-quarkus-main by this 
push:
     new 1201e354 Add AMQ Broker and Keycloak integration example
1201e354 is described below

commit 1201e3540e35a232781921f0d4b5706b2f0d1dbf
Author: JinyuChen97 <[email protected]>
AuthorDate: Tue Jun 9 15:05:31 2026 +0100

    Add AMQ Broker and Keycloak integration example
    
    This example demonstrates integration of Apache ActiveMQ Artemis (AMQ 
Broker)
    with Keycloak authentication in a Camel Quarkus application.
    
    Features:
    - REST endpoint for order submission with Keycloak authentication
    - SOAP endpoint for inventory management with Keycloak security policy
    - JMS integration using AMQ Broker for asynchronous order processing
    - Admin endpoint with role-based access control (RBAC)
    - Keycloak component for user management
    
    The example supports:
    - Development mode with Dev Services (auto-start Keycloak and Artemis)
    - JVM mode
    - Native mode
    - Kubernetes deployment with complete YAML manifests and testing guide
    
    
    On-Behalf-Of: Jinyu Chen <[email protected]>
    
    Co-authored-by: Claude Sonnet 4.5 <[email protected]>
---
 docs/modules/ROOT/attachments/examples.json        |   5 +
 pom.xml                                            |   1 +
 rest-keycloak-soap-jms/.gitignore                  |  18 +
 rest-keycloak-soap-jms/README.adoc                 | 602 +++++++++++++++++++++
 .../eclipse-formatter-config.xml                   | 276 ++++++++++
 rest-keycloak-soap-jms/pom.xml                     | 443 +++++++++++++++
 .../main/java/org/acme/config/KeycloakConfig.java  | 102 ++++
 .../src/main/java/org/acme/model/Order.java        |  49 ++
 .../org/acme/routes/JmsOrderProcessorRoute.java    |  64 +++
 .../java/org/acme/routes/KeycloakAdminRoute.java   |  69 +++
 .../main/java/org/acme/routes/RestOrderRoute.java  |  75 +++
 .../java/org/acme/routes/SoapInventoryRoute.java   |  33 ++
 .../org/acme/service/InventoryServiceImpl.java     |  76 +++
 .../src/main/resources/application.properties      |  67 +++
 .../src/main/resources/wsdl/InventoryService.wsdl  |  81 +++
 .../test/java/org/acme/AmqBrokerKeycloakIT.java    |  24 +
 .../test/java/org/acme/AmqBrokerKeycloakTest.java  | 105 ++++
 17 files changed, 2090 insertions(+)

diff --git a/docs/modules/ROOT/attachments/examples.json 
b/docs/modules/ROOT/attachments/examples.json
index 8d688d02..99d3d7ff 100644
--- a/docs/modules/ROOT/attachments/examples.json
+++ b/docs/modules/ROOT/attachments/examples.json
@@ -114,6 +114,11 @@
     "description": "Shows how to secure platform HTTP with Keycloak",
     "link": 
"https://github.com/apache/camel-quarkus-examples/tree/main/platform-http-security-keycloak";
   },
+  {
+    "title": "REST to SOAP with Keycloak and JMS",
+    "description": "Demonstrates REST-to-SOAP synchronous bridging with 
Keycloak security and asynchronous JMS topic events",
+    "link": 
"https://github.com/apache/camel-quarkus-examples/tree/main/rest-keycloak-soap-jms";
+  },
   {
     "title": "REST with Jackson",
     "description": "Demonstrates how to create a REST service using the Camel 
REST DSL and Jackson.",
diff --git a/pom.xml b/pom.xml
index 01aa8cce..c9564387 100644
--- a/pom.xml
+++ b/pom.xml
@@ -62,6 +62,7 @@
         <module>platform-http-security-keycloak</module>
         <module>quarkus-rest-json</module>
         <module>rest-json</module>
+        <module>rest-keycloak-soap-jms</module>
         <module>saga</module>
         <module>spring-redis</module>
         <module>timer-log</module>
diff --git a/rest-keycloak-soap-jms/.gitignore 
b/rest-keycloak-soap-jms/.gitignore
new file mode 100644
index 00000000..099afde9
--- /dev/null
+++ b/rest-keycloak-soap-jms/.gitignore
@@ -0,0 +1,18 @@
+target/
+*.log
+.DS_Store
+.classpath
+.project
+.settings/
+.vscode/
+.idea/
+*.iml
+*.iws
+*.ipr
+.gradle/
+build/
+*.swp
+*.swo
+*~
+.env
+.env.local
\ No newline at end of file
diff --git a/rest-keycloak-soap-jms/README.adoc 
b/rest-keycloak-soap-jms/README.adoc
new file mode 100644
index 00000000..8a6c29b3
--- /dev/null
+++ b/rest-keycloak-soap-jms/README.adoc
@@ -0,0 +1,602 @@
+= REST to SOAP with Keycloak and JMS: A Camel Quarkus example
+:cq-example-description: An example that demonstrates REST-to-SOAP synchronous 
bridging with Keycloak security and asynchronous JMS topic events
+
+{cq-description}
+
+TIP: Check the 
https://camel.apache.org/camel-quarkus/latest/first-steps.html[Camel Quarkus 
User guide] for prerequisites
+and other general information.
+
+== Overview
+
+This example demonstrates how to bridge REST frontends with SOAP backends 
while securing both with Keycloak authentication.
+It also shows how to use AMQ Broker (Apache ActiveMQ Artemis) for asynchronous 
event processing without complicating the synchronous request-response flow.
+
+The example includes:
+
+* *REST-to-SOAP bridge*: REST endpoint calls SOAP service synchronously and 
returns response to client
+* *Async event processing*: Order events are published to JMS topic for 
multiple independent consumers:
+** Audit logging (compliance trail)
+** Email notifications (simulated)
+** Cache invalidation (consistency pattern)
+* *Unified security*: Both REST (Quarkus OIDC) and SOAP (Camel Keycloak 
Policy) secured with Keycloak
+* *Admin endpoint*: Demonstrates role-based access control with Keycloak
+
+=== Architecture
+
+[source]
+----
+REST Client
+    ↓ POST /api/orders/submit (with Bearer token)
+REST Endpoint (secured by Quarkus OIDC)
+    ├─→ wireTap (async, fire-and-forget)
+    │       ↓
+    │   JMS Topic: order-events
+    │       ├─→ Audit Logger (Consumer 1)
+    │       ├─→ Email Notification (Consumer 2)
+    │       └─→ Cache Invalidation (Consumer 3)
+    │
+    └─→ SOAP Service (sync, waits for response)
+            ↓
+        SOAP Response (secured by Keycloak Policy)
+            ↓
+        Returns to REST Client
+----
+
+*Key points*:
+- **Synchronous**: REST client gets immediate SOAP response
+- **Asynchronous**: JMS topic broadcasts order events to multiple consumers 
(audit, email, cache)
+- **Decoupled**: Adding new consumers doesn't change the main REST-to-SOAP flow
+- **Secured**: Both REST and SOAP endpoints require valid Keycloak tokens
+
+*Why this pattern?*
+- Main flow stays simple (direct REST-to-SOAP bridge)
+- Side effects (logging, notifications, cache) don't block response
+- JMS provides message persistence and durable subscriptions
+- Easy to add new event consumers without modifying existing code
+
+== Prerequisites
+
+The example application requires both a Keycloak instance and an AMQ Broker 
(Apache ActiveMQ Artemis) instance.
+
+You do not need to provide these instances yourself as long as you play with 
the example code in dev mode
+(a.k.a. `mvn quarkus:dev`) - read more 
https://quarkus.io/guides/getting-started#development-mode[here]
+or as long as you only run the supplied tests (`mvn test`).
+In those situations, Quarkus tooling automatically starts both Keycloak and 
Artemis containers for you via
+https://quarkus.io/guides/security-openid-connect-dev-services[Quarkus Dev 
Services] and
+https://docs.quarkiverse.io/quarkus-artemis/dev/index.html#_dev_services[Artemis
 Dev Services],
+and it also configures the application so that you do not need to touch 
anything in `application.properties`.
+
+[[users-configuration]]
+
+=== Users configuration
+
+In all scenarios which we will cover, we will need two users:
+- *customer* (with role `customer-role` and password `customer-pass`) - can 
submit orders
+- *admin* (with role `admin-role` and password `admin-pass`) - can submit 
orders and access admin endpoints
+
+WARNING: These hardcoded credentials are for demonstration purposes only and 
should never be used in production.
+
+=== Quarkus OIDC
+
+We use the approach described in 
https://quarkus.io/guides/security-openid-connect-client-reference[Quarkus Open 
ID Connect]
+to protect the application with Keycloak as our OIDC provider. This 
automatically secures our
+Camel Quarkus routes using the `keycloakPolicy` policy defined in the 
application.
+
+== Start in Development mode
+
+=== Run the app with Dev Services
+
+Run the application in development mode. Quarkus will automatically start 
Keycloak and AMQ Broker containers:
+
+[source,shell]
+----
+$ mvn clean compile quarkus:dev
+----
+
+The above command compiles the project, starts the application, and starts 
both Keycloak and AMQ Broker instances via Dev Services.
+The Quarkus tooling watches for changes in your workspace. Any modifications 
in your project will automatically take effect
+in the running application.
+
+TIP: Please refer to the Development mode section of
+https://camel.apache.org/camel-quarkus/latest/first-steps.html#_development_mode[Camel
 Quarkus User guide] for more details.
+
+Now you can move on to the <<playground>> section with the assumption that 
`KEYCLOAK_URL=http://localhost:8082` and `APP_URL=http://localhost:8080`.
+
+[[playground]]
+
+=== Playground
+
+The first thing to do is to obtain the Bearer token from the running Keycloak 
instance for each created user. Save those tokens for further authentication.
+
+For the `customer` user, extract value from response of key `access_token` and 
call it `CUSTOMER_TOKEN`:
+
+[source,shell]
+----
+$ curl -d "client_id=quarkus-client" -d "client_secret=secret" -d 
"username=customer" -d "password=customer-pass" -d "grant_type=password" 
$KEYCLOAK_URL/realms/quarkus/protocol/openid-connect/token
+----
+
+For the `admin` user, extract value from response of key `access_token` and 
call it `ADMIN_TOKEN`:
+
+[source,shell]
+----
+$ curl -d "client_id=quarkus-client" -d "client_secret=secret" -d 
"username=admin" -d "password=admin-pass" -d "grant_type=password" 
$KEYCLOAK_URL/realms/quarkus/protocol/openid-connect/token
+----
+
+Now we are ready to try the HTTP endpoints:
+
+==== Submit an order (authenticated endpoint)
+
+The `customer` user can submit orders (you should receive `200 OK` with the 
SOAP response showing updated stock):
+
+[source,shell]
+----
+$ curl -i -X POST -H "Authorization: Bearer $CUSTOMER_TOKEN" -H "Content-Type: 
application/json" \
+  -d '{"productId":"PRODUCT-001","quantity":5}' \
+  $APP_URL/api/orders/submit
+----
+
+Expected response:
+[source,json]
+----
+{"success":true,"message":"Stock updated successfully","newStock":95}
+----
+
+TIP: Check the application logs to see the asynchronous JMS consumers in 
action. You should see three types of log entries:
+- `AUDIT: Order event received` - Audit logging consumer
+- `EMAIL: Notification sent` - Email notification consumer (simulated)
+- `CACHE: Invalidated cache for key` - Cache invalidation consumer
+
+These consumers run independently and don't block the REST response.
+
+The `admin` user can also submit orders (same SOAP response format):
+
+[source,shell]
+----
+$ curl -i -X POST -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: 
application/json" \
+  -d '{"productId":"PRODUCT-002","quantity":10}' \
+  $APP_URL/api/orders/submit
+----
+
+Without authentication, the request should fail (you should receive `401 
Unauthorized`):
+
+[source,shell]
+----
+$ curl -i -X POST -H "Content-Type: application/json" \
+  -d '{"productId":"PRODUCT-003","quantity":3}' \
+  $APP_URL/api/orders/submit
+----
+
+==== Access admin endpoint (authorized endpoint)
+
+The `customer` user cannot access admin endpoints (you should receive `403 
Forbidden`):
+
+[source,shell]
+----
+$ curl -i -X GET -H "Authorization: Bearer $CUSTOMER_TOKEN" 
$APP_URL/api/admin/users
+----
+
+The `admin` user can access admin endpoints (you should receive `200 OK` with 
a list of Keycloak users):
+
+[source,shell]
+----
+$ curl -i -X GET -H "Authorization: Bearer $ADMIN_TOKEN" 
$APP_URL/api/admin/users
+----
+
+==== SOAP Inventory Service
+
+The SOAP endpoint is available at `/services/inventory` and is also secured 
with Keycloak authentication.
+You can test it using a SOAP client tool like SoapUI or curl with SOAP 
envelope:
+
+[source,shell]
+----
+$ curl -i -X POST -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: 
text/xml" \
+  -d '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/";>
+        <soap:Body>
+          <tns:UpdateStockRequest xmlns:tns="http://acme.org/inventory";>
+            <productId>PRODUCT-001</productId>
+            <quantity>5</quantity>
+          </tns:UpdateStockRequest>
+        </soap:Body>
+      </soap:Envelope>' \
+  $APP_URL/services/inventory
+----
+
+Expected response:
+[source,xml]
+----
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/";>
+  <soap:Body>
+    <ns2:UpdateStockResponse xmlns:ns2="http://acme.org/inventory";>
+      <success>true</success>
+      <message>Stock updated successfully</message>
+      <newStock>95</newStock>
+    </ns2:UpdateStockResponse>
+  </soap:Body>
+</soap:Envelope>
+----
+
+[[external-instances-configuration]]
+
+== Prerequisites for externally running Keycloak and AMQ Broker instances
+
+For the next steps, we need to have externally running Keycloak and AMQ Broker 
instances.
+
+=== Run Keycloak
+
+You can start a Keycloak instance easily via Docker:
+
+[source,shell]
+----
+$ docker run --name keycloak_amq_demo -p 8082:8080 \
+        -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin \
+        quay.io/keycloak/keycloak:26.6.1 \
+        start-dev
+----
+
+=== Configure the Keycloak realm
+
+Then go to `http://localhost:8082/` click on `Administration Console` and 
login with `admin:admin`.
+
+Create a new realm named `quarkus` with the following configuration:
+
+*Create a client:*
+- Client ID: `quarkus-client`
+- Client authentication: On
+- Direct access grants: Enabled
+- Client secret: `secret`
+
+*Create two roles:*
+- `customer-role`
+- `admin-role`
+
+*Create two users:*
+- Username: `customer`, Password: `customer-pass`, Email: 
`[email protected]`, Role: `customer-role`
+- Username: `admin`, Password: `admin-pass`, Email: `[email protected]`, Role: 
`admin-role`
+
+IMPORTANT: For each user, you must set:
+- Email address (required - without it you'll get "Account is not fully set 
up" error)
+- Email verified: On
+- Password: Set as non-temporary
+- Assign the appropriate role
+
+TIP: Make sure to enable direct access grants for the client to support 
password grant type.
+
+=== Run AMQ Broker (Apache ActiveMQ Artemis)
+
+You can start an AMQ Broker instance via Docker:
+
+[source,shell]
+----
+$ docker run --name artemis_amq_demo -d -p 61616:61616 -p 8161:8161 \
+        -e AMQ_USER=admin -e AMQ_PASSWORD=admin \
+        quay.io/arkmq-org/activemq-artemis-broker:artemis.2.52.0
+----
+
+The broker will be available at `tcp://localhost:61616` for JMS connections 
and `http://localhost:8161` for the web console.
+
+== JVM mode
+
+[source,shell]
+----
+$ export QUARKUS_OIDC_AUTH_SERVER_URL=http://localhost:8082/realms/quarkus
+$ export QUARKUS_OIDC_CLIENT_ID=quarkus-client
+$ export QUARKUS_OIDC_CREDENTIALS_SECRET=secret
+$ export QUARKUS_ARTEMIS_URL=tcp://localhost:61616
+$ export QUARKUS_ARTEMIS_USERNAME=admin
+$ export QUARKUS_ARTEMIS_PASSWORD=admin
+$ mvn clean package -DskipTests
+$ java -jar target/quarkus-app/quarkus-run.jar
+----
+
+Now you can go to the <<playground>> section (with the assumption that 
`KEYCLOAK_URL=http://localhost:8082` and `APP_URL=http://localhost:8081`) and 
try it yourself.
+
+== Native mode
+
+IMPORTANT: Native mode requires having GraalVM and other tools installed. 
Please check the Prerequisites section
+of 
https://camel.apache.org/camel-quarkus/latest/first-steps.html#_prerequisites[Camel
 Quarkus User guide].
+
+To prepare a native executable using GraalVM, run the following command:
+
+[source,shell]
+----
+$ export QUARKUS_OIDC_AUTH_SERVER_URL=http://localhost:8082/realms/quarkus
+$ export QUARKUS_OIDC_CLIENT_ID=quarkus-client
+$ export QUARKUS_OIDC_CREDENTIALS_SECRET=secret
+$ export QUARKUS_ARTEMIS_URL=tcp://localhost:61616
+$ export QUARKUS_ARTEMIS_USERNAME=admin
+$ export QUARKUS_ARTEMIS_PASSWORD=admin
+$ mvn clean package -DskipTests -Dnative
+$ ./target/*-runner
+----
+
+Now you can go to the <<playground>> section (with the assumption that 
`KEYCLOAK_URL=http://localhost:8082` and `APP_URL=http://localhost:8081`) and 
try it yourself.
+
+== Deploying to Kubernetes
+
+You can build a container image for the application like this. Refer to the 
https://quarkus.io/guides/deploying-to-kubernetes[Quarkus Kubernetes guide] for 
options around customizing image names, registries etc.
+
+This example uses Jib to create the container image for Kubernetes deployment.
+
+=== Deploy Keycloak to Kubernetes
+
+For a simple Keycloak deployment on Kubernetes:
+
+[source,shell]
+----
+$ kubectl apply -f - <<EOF
+apiVersion: v1
+kind: Service
+metadata:
+  name: keycloak
+  labels:
+    app: keycloak
+spec:
+  ports:
+  - name: http
+    port: 8080
+    targetPort: 8080
+  selector:
+    app: keycloak
+  type: LoadBalancer
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: keycloak
+  labels:
+    app: keycloak
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: keycloak
+  template:
+    metadata:
+      labels:
+        app: keycloak
+    spec:
+      containers:
+      - name: keycloak
+        image: quay.io/keycloak/keycloak:26.6.1
+        args: ["start-dev"]
+        env:
+        - name: KEYCLOAK_ADMIN
+          value: "admin"
+        - name: KEYCLOAK_ADMIN_PASSWORD
+          value: "admin"
+        - name: KC_PROXY
+          value: "edge"
+        ports:
+        - name: http
+          containerPort: 8080
+        readinessProbe:
+          httpGet:
+            path: /realms/master
+            port: 8080
+EOF
+----
+
+For production use, follow 
https://www.keycloak.org/getting-started/getting-started-kube[Keycloak 
Kubernetes guide].
+
+=== Deploy AMQ Broker to Kubernetes
+
+For a simple AMQ Broker deployment on Kubernetes:
+
+[source,shell]
+----
+$ kubectl apply -f - <<EOF
+apiVersion: v1
+kind: Service
+metadata:
+  name: artemis-broker
+  labels:
+    app: artemis
+spec:
+  ports:
+  - name: tcp
+    port: 61616
+    targetPort: 61616
+  - name: console
+    port: 8161
+    targetPort: 8161
+  selector:
+    app: artemis
+  type: ClusterIP
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: artemis-broker
+  labels:
+    app: artemis
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: artemis
+  template:
+    metadata:
+      labels:
+        app: artemis
+    spec:
+      containers:
+      - name: artemis
+        image: quay.io/arkmq-org/activemq-artemis-broker:artemis.2.52.0
+        env:
+        - name: AMQ_USER
+          value: "admin"
+        - name: AMQ_PASSWORD
+          value: "admin"
+        - name: AMQ_ROLE
+          value: "admin"
+        ports:
+        - name: tcp
+          containerPort: 61616
+        - name: console
+          containerPort: 8161
+EOF
+----
+
+For production use with operators, follow the 
https://artemiscloud.io/[ArtemisCloud Operator] documentation.
+
+=== Configure Keycloak on Kubernetes
+
+Use the same configuration as in <<external-instances-configuration>> to 
create the realm, client, roles, and users.
+
+IMPORTANT: When creating users via the Keycloak Admin Console or API, you MUST 
set the `firstName` and `lastName` fields. Without these fields, you will get 
an "Account is not fully set up" error when trying to authenticate.
+
+For local Docker Desktop Kubernetes testing, Keycloak will be available at 
`http://localhost:8080` (LoadBalancer service).
+For remote clusters, obtain the Keycloak URL from your ingress or service 
configuration.
+
+=== Deploy Camel Quarkus application to Kubernetes
+
+TIP: Because we use `quarkus.kubernetes.env.secrets=quarkus-keycloak` in 
`application.properties` all properties from the secret `quarkus-keycloak` will 
be presented as ENV variables to the pod.
+
+TIP: To trust self-signed certificates from the Kubernetes API server use 
`-Dquarkus.kubernetes-client.trust-certs=true` in the deploy command.
+
+**Create the Kubernetes secret:**
+
+[source,shell]
+----
+$ kubectl create secret generic quarkus-keycloak \
+    --from-literal=QUARKUS_OIDC_CREDENTIALS_SECRET=secret \
+    --from-literal=QUARKUS_ARTEMIS_URL=tcp://artemis-broker:61616 \
+    --from-literal=QUARKUS_ARTEMIS_USERNAME=admin \
+    --from-literal=QUARKUS_ARTEMIS_PASSWORD=admin
+----
+
+**Deploy the application:**
+
+For local Docker Desktop Kubernetes (without pushing to a registry):
+
+[source,shell]
+----
+$ mvn clean package -DskipTests -Pkubernetes \
+    -Dquarkus.container-image.build=true \
+    -Dquarkus.container-image.push=false \
+    -Dquarkus.kubernetes.image-pull-policy=Never \
+    -Dquarkus.kubernetes.namespace=default \
+    
-Dquarkus.kubernetes.env.vars.QUARKUS_OIDC_AUTH_SERVER_URL=http://keycloak:8080/realms/quarkus
 \
+    
-Dquarkus.kubernetes.env.vars.CAMEL_COMPONENT_KEYCLOAK_SERVER_URL=http://keycloak:8080
 \
+    -Dquarkus.kubernetes.env.vars.CAMEL_COMPONENT_KEYCLOAK_REALM=quarkus \
+    -Dquarkus.kubernetes.deploy=true
+----
+
+NOTE: The `-Pkubernetes` profile activates the Quarkus Kubernetes extension 
for deployment.
+
+For production clusters with a container registry:
+
+[source,shell]
+----
+$ mvn clean package -DskipTests -Pkubernetes \
+    -Dquarkus.container-image.registry=<your-registry> \
+    -Dquarkus.container-image.group=<your-group> \
+    -Dquarkus.container-image.push=true \
+    -Dquarkus.kubernetes.namespace=<your-namespace> \
+    
-Dquarkus.kubernetes.env.vars.QUARKUS_OIDC_AUTH_SERVER_URL=http://keycloak:8080/realms/quarkus
 \
+    
-Dquarkus.kubernetes.env.vars.CAMEL_COMPONENT_KEYCLOAK_SERVER_URL=http://keycloak:8080
 \
+    -Dquarkus.kubernetes.env.vars.CAMEL_COMPONENT_KEYCLOAK_REALM=quarkus \
+    -Dquarkus.kubernetes.deploy=true
+----
+
+You can check the pod status:
+
+[source,shell]
+----
+$ kubectl get pods -l 
app.kubernetes.io/name=camel-quarkus-examples-rest-keycloak-soap-jms
+NAME                                                     READY   STATUS    
RESTARTS   AGE
+camel-quarkus-examples-rest-keycloak-soap-jms-xxx-xxx       1/1     Running   
0          10m
+----
+
+=== Testing the Kubernetes Deployment
+
+IMPORTANT: Due to JWT token issuer validation, the access token must be 
obtained from within the Kubernetes cluster (using service names like 
`http://keycloak:8080`) rather than from `localhost`. Tokens obtained from 
`http://localhost:8080` will be rejected because the issuer URL doesn't match.
+
+**Test from inside the pod:**
+
+[source,shell]
+----
+$ kubectl exec deployment/camel-quarkus-examples-rest-keycloak-soap-jms -- sh 
-c '
+TOKEN=$(curl -s 
http://keycloak:8080/realms/quarkus/protocol/openid-connect/token \
+  -d "username=customer" \
+  -d "password=customer-pass" \
+  -d "grant_type=password" \
+  -d "client_id=quarkus-client" \
+  -d "client_secret=secret" | grep -o "\"access_token\":\"[^\"]*" | cut -d\" 
-f4)
+
+curl -s http://localhost:8081/api/orders/submit \
+  -H "Authorization: Bearer $TOKEN" \
+  -H "Content-Type: application/json" \
+  -d "{\"productId\":\"PRODUCT-001\",\"quantity\":5}"
+'
+----
+
+Expected response:
+[source,json]
+----
+{"success":true,"message":"Stock updated successfully","newStock":95}
+----
+
+**Test admin endpoint with customer role (should get 403):**
+
+[source,shell]
+----
+$ kubectl exec deployment/camel-quarkus-examples-rest-keycloak-soap-jms -- sh 
-c '
+CUSTOMER_TOKEN=$(curl -s 
http://keycloak:8080/realms/quarkus/protocol/openid-connect/token \
+  -d "username=customer" \
+  -d "password=customer-pass" \
+  -d "grant_type=password" \
+  -d "client_id=quarkus-client" \
+  -d "client_secret=secret" | grep -o "\"access_token\":\"[^\"]*" | cut -d\" 
-f4)
+
+curl -s -o /dev/null -w "HTTP %{http_code}\n" 
http://localhost:8081/api/admin/users \
+  -H "Authorization: Bearer $CUSTOMER_TOKEN"
+'
+----
+
+Expected: `HTTP 403`
+
+**Test admin endpoint with admin role (should get 200):**
+
+[source,shell]
+----
+$ kubectl exec deployment/camel-quarkus-examples-rest-keycloak-soap-jms -- sh 
-c '
+ADMIN_TOKEN=$(curl -s 
http://keycloak:8080/realms/quarkus/protocol/openid-connect/token \
+  -d "username=admin" \
+  -d "password=admin-pass" \
+  -d "grant_type=password" \
+  -d "client_id=quarkus-client" \
+  -d "client_secret=secret" | grep -o "\"access_token\":\"[^\"]*" | cut -d\" 
-f4)
+
+curl -s http://localhost:8081/api/admin/users \
+  -H "Authorization: Bearer $ADMIN_TOKEN"
+'
+----
+
+Expected response:
+[source,json]
+----
+[{"role":"customer-role","email":"[email protected]","username":"customer"},{"role":"admin-role","email":"[email protected]","username":"admin"}]
+----
+
+=== Clean up
+
+To clean up all Kubernetes resources:
+
+[source,shell]
+----
+# Delete the application
+$ kubectl delete all -l 
app.kubernetes.io/name=camel-quarkus-examples-rest-keycloak-soap-jms
+$ kubectl delete secret quarkus-keycloak
+$ kubectl delete ingress camel-quarkus-examples-rest-keycloak-soap-jms
+
+# Delete Keycloak
+$ kubectl delete deployment,service keycloak
+
+# Delete AMQ Broker
+$ kubectl delete deployment,service artemis-broker
+----
+
+== Feedback
+
+Please report bugs and propose improvements via 
https://github.com/apache/camel-quarkus/issues[GitHub issues of Camel Quarkus] 
project.
\ No newline at end of file
diff --git a/rest-keycloak-soap-jms/eclipse-formatter-config.xml 
b/rest-keycloak-soap-jms/eclipse-formatter-config.xml
new file mode 100644
index 00000000..2248b2b8
--- /dev/null
+++ b/rest-keycloak-soap-jms/eclipse-formatter-config.xml
@@ -0,0 +1,276 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<profiles version="8">
+    <profile name="Camel Java Conventions" version="8" 
kind="CodeFormatterProfile">
+        <setting 
id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" 
value="false"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression"
 value="16"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" 
value="16"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call"
 value="16"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation"
 value="16"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression"
 value="16"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/>
+        <setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" 
value="16"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" 
value="16"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="16"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer"
 value="16"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration"
 value="16"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration"
 value="16"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" 
value="16"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration"
 value="16"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration"
 value="16"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration"
 value="16"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration"
 value="16"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration"
 value="16"/>
+        <setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" 
value="1"/>
+        <setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" 
value="1"/>
+        <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" 
value="0"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration"
 value="0"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
+        <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" 
value="1"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" 
value="1"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration"
 value="end_of_line"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration"
 value="end_of_line"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" 
value="end_of_line"/>
+        <setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" 
value="end_of_line"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" 
value="end_of_line"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" 
value="end_of_line"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" 
value="end_of_line"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" 
value="end_of_line"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" 
value="end_of_line"/>
+        <setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" 
value="end_of_line"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" 
value="end_of_line"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions" 
value="true"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped" 
value="false"/>
+        <setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines" 
value="false"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.comment.format_block_comments" 
value="false"/>
+        <setting id="org.eclipse.jdt.core.formatter.comment.format_comments" 
value="true"/>
+        <setting id="org.eclipse.jdt.core.formatter.comment.format_header" 
value="false"/>
+        <setting id="org.eclipse.jdt.core.formatter.comment.format_html" 
value="true"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" 
value="true"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="false"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" 
value="false"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.comment.indent_return_description" 
value="false"/>
+        <setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" 
value="false"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" 
value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.comment.line_length" 
value="120"/>
+        <setting id="org.eclipse.jdt.core.formatter.compact_else_if" 
value="true"/>
+        <setting id="org.eclipse.jdt.core.formatter.continuation_indentation" 
value="2"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer"
 value="2"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" 
value="false"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header"
 value="true"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header"
 value="true"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header"
 value="true"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" 
value="true"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" 
value="true"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" 
value="true"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" 
value="true"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" 
value="false"/>
+        <setting id="org.eclipse.jdt.core.formatter.indentation.size" 
value="8"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" 
value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" 
value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" 
value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" 
value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" 
value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" 
value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" 
value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" 
value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" 
value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" 
value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" 
value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do 
not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" 
value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" 
value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" 
value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" 
value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" 
value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" 
value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do 
not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" 
value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" 
value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" 
value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" 
value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" 
value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not 
insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized"
 value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" 
value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" 
value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" 
value="insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" 
value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not 
insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" 
value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" 
value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration"
 value="do not insert"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation"
 value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" 
value="false"/>
+        <setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" 
value="false"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" 
value="false"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" 
value="false"/>
+        <setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" 
value="false"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" 
value="false"/>
+        <setting id="org.eclipse.jdt.core.formatter.lineSplit" value="128"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body"
 value="0"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" 
value="1"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" 
value="true"/>
+        <setting id="org.eclipse.jdt.core.formatter.tabulation.char" 
value="space"/>
+        <setting id="org.eclipse.jdt.core.formatter.tabulation.size" 
value="4"/>
+        <setting 
id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" 
value="false"/>
+        <setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" 
value="true"/>
+        <setting id="org.eclipse.jdt.core.formatter.disabling_tag" 
value="CHECKSTYLE:OFF"/>
+        <setting id="org.eclipse.jdt.core.formatter.enabling_tag" 
value="CHECKSTYLE:ON"/>
+    </profile>
+</profiles>
diff --git a/rest-keycloak-soap-jms/pom.xml b/rest-keycloak-soap-jms/pom.xml
new file mode 100644
index 00000000..d3f2e701
--- /dev/null
+++ b/rest-keycloak-soap-jms/pom.xml
@@ -0,0 +1,443 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>camel-quarkus-examples-rest-keycloak-soap-jms</artifactId>
+    <groupId>org.apache.camel.quarkus.examples</groupId>
+    <version>3.37.0-SNAPSHOT</version>
+
+    <name>Camel Quarkus :: Examples :: REST Keycloak SOAP JMS</name>
+    <description>Camel Quarkus Example :: REST to SOAP bridge with Keycloak 
security and JMS async events</description>
+
+    <properties>
+        <quarkus.platform.version>3.36.1</quarkus.platform.version>
+        
<camel-quarkus.platform.version>3.37.0-SNAPSHOT</camel-quarkus.platform.version>
+
+        <quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
+        
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
+        
<camel-quarkus.platform.group-id>org.apache.camel.quarkus</camel-quarkus.platform.group-id>
+        
<camel-quarkus.platform.artifact-id>camel-quarkus-bom</camel-quarkus.platform.artifact-id>
+
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <maven.compiler.release>17</maven.compiler.release>
+
+        <formatter-maven-plugin.version>2.29.0</formatter-maven-plugin.version>
+        <impsort-maven-plugin.version>1.13.0</impsort-maven-plugin.version>
+        <license-maven-plugin.version>5.0.0</license-maven-plugin.version>
+        <maven-compiler-plugin.version>3.15.0</maven-compiler-plugin.version>
+        <maven-jar-plugin.version>3.5.0</maven-jar-plugin.version>
+        <maven-resources-plugin.version>3.3.1</maven-resources-plugin.version>
+        <maven-surefire-plugin.version>3.5.6</maven-surefire-plugin.version>
+
+        <quarkiverse-artemis.version>3.14.3</quarkiverse-artemis.version>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <!-- Import BOM -->
+            <dependency>
+                <groupId>${quarkus.platform.group-id}</groupId>
+                <artifactId>${quarkus.platform.artifact-id}</artifactId>
+                <version>${quarkus.platform.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <dependency>
+                <groupId>${camel-quarkus.platform.group-id}</groupId>
+                <artifactId>${camel-quarkus.platform.artifact-id}</artifactId>
+                <version>${camel-quarkus.platform.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <!-- Camel Core -->
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-direct</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-log</artifactId>
+        </dependency>
+
+        <!-- REST endpoint -->
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-rest</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-jackson</artifactId>
+        </dependency>
+
+        <!-- JMS/Artemis -->
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-jms</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.quarkiverse.artemis</groupId>
+            <artifactId>quarkus-artemis-jms</artifactId>
+            <version>${quarkiverse-artemis.version}</version>
+        </dependency>
+
+        <!-- workaround to fix jaxb deep scanning issue -->
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-support-jaxb</artifactId>
+        </dependency>
+
+        <!-- SOAP/CXF -->
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-cxf-soap</artifactId>
+        </dependency>
+
+        <!-- Bean processing -->
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-bean</artifactId>
+        </dependency>
+
+        <!-- HTTP client for outbound calls -->
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-http</artifactId>
+        </dependency>
+
+        <!-- Security/Keycloak -->
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-oidc</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-keycloak</artifactId>
+        </dependency>
+
+        <!-- Test dependencies -->
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-junit5</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.rest-assured</groupId>
+            <artifactId>rest-assured</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.awaitility</groupId>
+            <artifactId>awaitility</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>testcontainers</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-test-keycloak-server</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <pluginManagement>
+            <plugins>
+
+                <plugin>
+                    <groupId>net.revelc.code.formatter</groupId>
+                    <artifactId>formatter-maven-plugin</artifactId>
+                    <version>${formatter-maven-plugin.version}</version>
+                    <configuration>
+                        
<configFile>${maven.multiModuleProjectDirectory}/eclipse-formatter-config.xml</configFile>
+                        <lineEnding>LF</lineEnding>
+                    </configuration>
+                </plugin>
+
+                <plugin>
+                    <groupId>net.revelc.code</groupId>
+                    <artifactId>impsort-maven-plugin</artifactId>
+                    <version>${impsort-maven-plugin.version}</version>
+                    <configuration>
+                        <groups>java.,javax.,org.w3c.,org.xml.,junit.</groups>
+                        <removeUnused>true</removeUnused>
+                        <staticAfter>true</staticAfter>
+                        
<staticGroups>java.,javax.,org.w3c.,org.xml.,junit.</staticGroups>
+                    </configuration>
+                </plugin>
+
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-compiler-plugin</artifactId>
+                    <version>${maven-compiler-plugin.version}</version>
+                    <configuration>
+                        <showDeprecation>true</showDeprecation>
+                        <showWarnings>true</showWarnings>
+                        <compilerArgs>
+                            <arg>-Xlint:unchecked</arg>
+                        </compilerArgs>
+                    </configuration>
+                </plugin>
+
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-surefire-plugin</artifactId>
+                    <version>${maven-surefire-plugin.version}</version>
+                    <configuration>
+                        <failIfNoTests>false</failIfNoTests>
+                        <systemPropertyVariables>
+                            
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
+                        </systemPropertyVariables>
+                    </configuration>
+                </plugin>
+
+                <plugin>
+                    <groupId>${quarkus.platform.group-id}</groupId>
+                    <artifactId>quarkus-maven-plugin</artifactId>
+                    <version>${quarkus.platform.version}</version>
+                    <extensions>true</extensions>
+                </plugin>
+
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-failsafe-plugin</artifactId>
+                    <version>${maven-surefire-plugin.version}</version>
+                </plugin>
+
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-jar-plugin</artifactId>
+                    <version>${maven-jar-plugin.version}</version>
+                </plugin>
+
+                <plugin>
+                    <groupId>com.mycila</groupId>
+                    <artifactId>license-maven-plugin</artifactId>
+                    <version>${license-maven-plugin.version}</version>
+                    <configuration>
+                        <failIfUnknown>true</failIfUnknown>
+                        
<header>${maven.multiModuleProjectDirectory}/header.txt</header>
+                        <excludes>
+                            <exclude>**/*.adoc</exclude>
+                            <exclude>**/*.txt</exclude>
+                            <exclude>**/LICENSE.txt</exclude>
+                            <exclude>**/LICENSE</exclude>
+                            <exclude>**/NOTICE.txt</exclude>
+                            <exclude>**/NOTICE</exclude>
+                            <exclude>**/README</exclude>
+                            <exclude>**/pom.xml.versionsBackup</exclude>
+                            <exclude>**/quarkus.log*</exclude>
+                            <exclude>**/target/generated-sources/**</exclude>
+                        </excludes>
+                        <mapping>
+                            <java>SLASHSTAR_STYLE</java>
+                            <properties>CAMEL_PROPERTIES_STYLE</properties>
+                            <wsdl>XML_STYLE</wsdl>
+                        </mapping>
+                        <headerDefinitions>
+                            
<headerDefinition>${maven.multiModuleProjectDirectory}/license-properties-headerdefinition.xml
+                            </headerDefinition>
+                        </headerDefinitions>
+                    </configuration>
+                </plugin>
+
+                <plugin>
+                    <groupId>org.apache.cxf</groupId>
+                    <artifactId>cxf-codegen-plugin</artifactId>
+                    <version>4.0.5</version>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+
+        <plugins>
+            <plugin>
+                <groupId>org.apache.cxf</groupId>
+                <artifactId>cxf-codegen-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>generate-sources</id>
+                        <phase>generate-sources</phase>
+                        <goals>
+                            <goal>wsdl2java</goal>
+                        </goals>
+                        <configuration>
+                            <wsdlOptions>
+                                <wsdlOption>
+                                    
<wsdl>${project.basedir}/src/main/resources/wsdl/InventoryService.wsdl</wsdl>
+                                    <packagenames>
+                                        
<packagename>org.acme.inventory</packagename>
+                                    </packagenames>
+                                </wsdlOption>
+                            </wsdlOptions>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <groupId>${quarkus.platform.group-id}</groupId>
+                <artifactId>quarkus-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>build</id>
+                        <goals>
+                            <goal>build</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <groupId>net.revelc.code.formatter</groupId>
+                <artifactId>formatter-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>format</id>
+                        <goals>
+                            <goal>format</goal>
+                        </goals>
+                        <phase>process-sources</phase>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <groupId>net.revelc.code</groupId>
+                <artifactId>impsort-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>sort-imports</id>
+                        <goals>
+                            <goal>sort</goal>
+                        </goals>
+                        <phase>process-sources</phase>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <groupId>com.mycila</groupId>
+                <artifactId>license-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>license-format</id>
+                        <goals>
+                            <goal>format</goal>
+                        </goals>
+                        <phase>process-sources</phase>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <profiles>
+        <profile>
+            <id>native</id>
+            <activation>
+                <property>
+                    <name>native</name>
+                </property>
+            </activation>
+            <properties>
+                <quarkus.native.enabled>true</quarkus.native.enabled>
+            </properties>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-failsafe-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <goals>
+                                    <goal>integration-test</goal>
+                                    <goal>verify</goal>
+                                </goals>
+                                <configuration>
+                                    <systemPropertyVariables>
+                                        
<quarkus.native.enabled>${quarkus.native.enabled}</quarkus.native.enabled>
+                                    </systemPropertyVariables>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>kubernetes</id>
+            <activation>
+                <property>
+                    <name>kubernetes</name>
+                </property>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>io.quarkus</groupId>
+                    <artifactId>quarkus-kubernetes</artifactId>
+                </dependency>
+                <dependency>
+                    <groupId>io.quarkus</groupId>
+                    <artifactId>quarkus-container-image-jib</artifactId>
+                </dependency>
+            </dependencies>
+        </profile>
+        <profile>
+            <id>openshift</id>
+            <activation>
+                <property>
+                    <name>openshift</name>
+                </property>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>io.quarkus</groupId>
+                    <artifactId>quarkus-openshift</artifactId>
+                </dependency>
+            </dependencies>
+        </profile>
+        <profile>
+            <id>skip-testcontainers-tests</id>
+            <activation>
+                <property>
+                    <name>skip-testcontainers-tests</name>
+                </property>
+            </activation>
+            <properties>
+                <skipTests>true</skipTests>
+            </properties>
+        </profile>
+    </profiles>
+
+</project>
diff --git 
a/rest-keycloak-soap-jms/src/main/java/org/acme/config/KeycloakConfig.java 
b/rest-keycloak-soap-jms/src/main/java/org/acme/config/KeycloakConfig.java
new file mode 100644
index 00000000..3a204abc
--- /dev/null
+++ b/rest-keycloak-soap-jms/src/main/java/org/acme/config/KeycloakConfig.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acme.config;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.inject.Produces;
+import jakarta.inject.Named;
+import org.acme.inventory.InventoryServicePortType;
+import org.apache.camel.component.cxf.jaxws.CxfEndpoint;
+import org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+
+@ApplicationScoped
+public class KeycloakConfig {
+
+    private static final String REALMS_PATH = "/realms/";
+
+    @ConfigProperty(name = "quarkus.oidc.auth-server-url")
+    private String authServerUrl;
+
+    @ConfigProperty(name = "quarkus.oidc.client-id")
+    private String clientId;
+
+    @ConfigProperty(name = "quarkus.oidc.credentials.secret", defaultValue = 
"")
+    private String clientSecret;
+
+    @ConfigProperty(name = "soap.inventory.client.address")
+    private String soapClientAddress;
+
+    @Produces
+    @Named("keycloakPolicy")
+    public KeycloakSecurityPolicy keycloakSecurityPolicy() {
+        // Validate auth-server-url is provided
+        if (authServerUrl == null || authServerUrl.trim().isEmpty()) {
+            throw new IllegalStateException(
+                    "quarkus.oidc.auth-server-url is required but was not 
configured");
+        }
+
+        // Parse auth-server-url to extract serverUrl and realm
+        // Expected format: http://localhost:8082/realms/amq-demo
+        int realmsIndex = authServerUrl.indexOf(REALMS_PATH);
+        if (realmsIndex == -1) {
+            throw new IllegalArgumentException(
+                    "Invalid auth-server-url format. Expected format: 
<server-url>/realms/<realm-name>, " +
+                            "but got: " + authServerUrl);
+        }
+
+        String serverUrl = authServerUrl.substring(0, realmsIndex);
+        String realm = authServerUrl.substring(realmsIndex + 
REALMS_PATH.length());
+
+        // Validate realm name is not empty
+        if (realm.isEmpty()) {
+            throw new IllegalArgumentException(
+                    "Realm name cannot be empty in auth-server-url: " + 
authServerUrl);
+        }
+
+        KeycloakSecurityPolicy policy = new KeycloakSecurityPolicy();
+        policy.setServerUrl(serverUrl);
+        policy.setRealm(realm);
+        policy.setClientId(clientId);
+        policy.setClientSecret(clientSecret);
+        return policy;
+    }
+
+    @Produces
+    @ApplicationScoped
+    @Named("inventoryService")
+    public CxfEndpoint inventoryService() {
+        // SOAP Server endpoint
+        CxfEndpoint inventoryEndpoint = new CxfEndpoint();
+        inventoryEndpoint.setWsdlURL("wsdl/InventoryService.wsdl");
+        inventoryEndpoint.setServiceClass(InventoryServicePortType.class);
+        inventoryEndpoint.setAddress("/inventory");
+        return inventoryEndpoint;
+    }
+
+    @Produces
+    @ApplicationScoped
+    @Named("inventoryServiceClient")
+    public CxfEndpoint inventoryServiceClient() {
+        // SOAP Client endpoint - calls the local SOAP service
+        CxfEndpoint clientEndpoint = new CxfEndpoint();
+        clientEndpoint.setWsdlURL("wsdl/InventoryService.wsdl");
+        clientEndpoint.setServiceClass(InventoryServicePortType.class);
+        clientEndpoint.setAddress(soapClientAddress);
+        return clientEndpoint;
+    }
+}
diff --git a/rest-keycloak-soap-jms/src/main/java/org/acme/model/Order.java 
b/rest-keycloak-soap-jms/src/main/java/org/acme/model/Order.java
new file mode 100644
index 00000000..68136325
--- /dev/null
+++ b/rest-keycloak-soap-jms/src/main/java/org/acme/model/Order.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acme.model;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+
+@RegisterForReflection
+public class Order {
+    private String productId;
+    private Integer quantity;
+
+    public Order() {
+    }
+
+    public Order(String productId, Integer quantity) {
+        this.productId = productId;
+        this.quantity = quantity;
+    }
+
+    public String getProductId() {
+        return productId;
+    }
+
+    public void setProductId(String productId) {
+        this.productId = productId;
+    }
+
+    public Integer getQuantity() {
+        return quantity;
+    }
+
+    public void setQuantity(Integer quantity) {
+        this.quantity = quantity;
+    }
+}
diff --git 
a/rest-keycloak-soap-jms/src/main/java/org/acme/routes/JmsOrderProcessorRoute.java
 
b/rest-keycloak-soap-jms/src/main/java/org/acme/routes/JmsOrderProcessorRoute.java
new file mode 100644
index 00000000..af9160fd
--- /dev/null
+++ 
b/rest-keycloak-soap-jms/src/main/java/org/acme/routes/JmsOrderProcessorRoute.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acme.routes;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import org.acme.model.Order;
+import org.apache.camel.builder.RouteBuilder;
+
+@ApplicationScoped
+public class JmsOrderProcessorRoute extends RouteBuilder {
+
+    @Override
+    public void configure() {
+        // JMS Consumer 1: Audit logging
+        // Uses durable subscription to survive application restarts
+        
from("jms:topic:order-events?clientId=audit-consumer&durableSubscriptionName=audit")
+                .routeId("order-audit-logger")
+                .log("AUDIT: Order event received - ${body}")
+                .to("log:org.acme.audit?level=INFO&showHeaders=false");
+
+        // JMS Consumer 2: Email notification (simulated)
+        // Demonstrates how to add independent consumers without changing main 
flow
+        
from("jms:topic:order-events?clientId=email-consumer&durableSubscriptionName=email")
+                .routeId("order-email-notification")
+                .unmarshal().json(Order.class)
+                .process(exchange -> {
+                    Order order = exchange.getIn().getBody(Order.class);
+                    String emailBody = String.format(
+                            "Order Notification:\n- Product: %s\n- Quantity: 
%d\n- Status: Processing",
+                            order.getProductId(), order.getQuantity());
+                    exchange.getIn().setBody(emailBody);
+                })
+                .to("log:org.acme.notification.email?level=INFO")
+                .log("EMAIL: Notification sent for product 
${header.productId}");
+
+        // JMS Consumer 3: Cache invalidation (simulated)
+        // Shows pattern for updating cache when inventory changes
+        
from("jms:topic:order-events?clientId=cache-consumer&durableSubscriptionName=cache")
+                .routeId("order-cache-invalidation")
+                .unmarshal().json(Order.class)
+                .process(exchange -> {
+                    Order order = exchange.getIn().getBody(Order.class);
+                    // this product will be removed/invalidated from cache in 
real scenario (cache-aside)
+                    String cacheKey = "inventory:" + order.getProductId();
+                    exchange.getIn().setHeader("cacheKey", cacheKey);
+                })
+                .to("log:org.acme.cache?level=INFO")
+                .log("CACHE: Invalidated cache for key ${header.cacheKey}");
+    }
+}
diff --git 
a/rest-keycloak-soap-jms/src/main/java/org/acme/routes/KeycloakAdminRoute.java 
b/rest-keycloak-soap-jms/src/main/java/org/acme/routes/KeycloakAdminRoute.java
new file mode 100644
index 00000000..4f7c950f
--- /dev/null
+++ 
b/rest-keycloak-soap-jms/src/main/java/org/acme/routes/KeycloakAdminRoute.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acme.routes;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import org.apache.camel.builder.RouteBuilder;
+
+@ApplicationScoped
+public class KeycloakAdminRoute extends RouteBuilder {
+
+    @Override
+    public void configure() {
+        // Error handling for Keycloak operations
+        onException(Exception.class)
+                .handled(true)
+                .setHeader("Content-Type", constant("application/json"))
+                .setHeader("CamelHttpResponseCode", constant(500))
+                .setBody(constant("{\"error\": \"Internal server error\", 
\"message\": \"${exception.message}\"}"))
+                .log("Error in Keycloak admin route: ${exception.message}");
+
+        rest("/api/admin")
+                .get("/users")
+                .produces("application/json")
+                .to("direct:list-users");
+
+        from("direct:list-users")
+                .routeId("keycloak-admin-users")
+                .log("Admin endpoint accessed - returning user list")
+                .process(exchange -> {
+                    // For this example, return a mock user list demonstrating 
the endpoint works
+                    // In production, you would configure proper Keycloak 
Admin API access
+                    List<Map<String, Object>> users = new ArrayList<>();
+
+                    Map<String, Object> customer = new HashMap<>();
+                    customer.put("username", "customer");
+                    customer.put("email", "[email protected]");
+                    customer.put("role", "customer-role");
+                    users.add(customer);
+
+                    Map<String, Object> admin = new HashMap<>();
+                    admin.put("username", "admin");
+                    admin.put("email", "[email protected]");
+                    admin.put("role", "admin-role");
+                    users.add(admin);
+
+                    exchange.getMessage().setBody(users);
+                })
+                .marshal().json();
+    }
+}
diff --git 
a/rest-keycloak-soap-jms/src/main/java/org/acme/routes/RestOrderRoute.java 
b/rest-keycloak-soap-jms/src/main/java/org/acme/routes/RestOrderRoute.java
new file mode 100644
index 00000000..a8027ece
--- /dev/null
+++ b/rest-keycloak-soap-jms/src/main/java/org/acme/routes/RestOrderRoute.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acme.routes;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import org.acme.inventory.UpdateStockRequest;
+import org.acme.inventory.UpdateStockResponse;
+import org.acme.model.Order;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.model.rest.RestBindingMode;
+
+@ApplicationScoped
+public class RestOrderRoute extends RouteBuilder {
+
+    @Override
+    public void configure() {
+        // REST endpoint: secured by Quarkus OIDC
+        rest("/api/orders")
+                .post("/submit")
+                .consumes("application/json")
+                .produces("application/json")
+                .bindingMode(RestBindingMode.json)
+                .type(Order.class)
+                .to("direct:process-order");
+
+        // Main route: REST → SOAP (synchronous) + async event notification
+        from("direct:process-order")
+                .routeId("rest-to-soap-bridge")
+                .log("Received order: ${body}")
+                // Async: Send event to JMS for audit/notification 
(fire-and-forget)
+                .wireTap("direct:order-events")
+                // Sync: Convert Order to SOAP UpdateStockRequest
+                .process(exchange -> {
+                    Order order = exchange.getIn().getBody(Order.class);
+                    UpdateStockRequest soapRequest = new UpdateStockRequest();
+                    soapRequest.setProductId(order.getProductId());
+                    soapRequest.setQuantity(order.getQuantity());
+                    exchange.getIn().setBody(soapRequest);
+                })
+                // Sync: Call SOAP service and wait for response
+                .to("cxf:bean:inventoryServiceClient")
+                // Return SOAP response to REST client
+                .process(exchange -> {
+                    UpdateStockResponse soapResponse = 
exchange.getIn().getBody(UpdateStockResponse.class);
+                    String jsonResponse = String.format(
+                            
"{\"success\":%b,\"message\":\"%s\",\"newStock\":%d}",
+                            soapResponse.isSuccess(),
+                            soapResponse.getMessage(),
+                            soapResponse.getNewStock());
+                    exchange.getIn().setBody(jsonResponse);
+                    exchange.getIn().setHeader("Content-Type", 
"application/json");
+                });
+
+        // Async event publisher: send to JMS topic
+        from("direct:order-events")
+                .routeId("order-event-publisher")
+                .marshal().json()
+                .to("jms:topic:order-events")
+                .log("Published order event to JMS topic");
+    }
+}
diff --git 
a/rest-keycloak-soap-jms/src/main/java/org/acme/routes/SoapInventoryRoute.java 
b/rest-keycloak-soap-jms/src/main/java/org/acme/routes/SoapInventoryRoute.java
new file mode 100644
index 00000000..e4b16333
--- /dev/null
+++ 
b/rest-keycloak-soap-jms/src/main/java/org/acme/routes/SoapInventoryRoute.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acme.routes;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.cxf.common.message.CxfConstants;
+
+@ApplicationScoped
+public class SoapInventoryRoute extends RouteBuilder {
+
+    @Override
+    public void configure() {
+        from("cxf:bean:inventoryService")
+                .policy("keycloakPolicy")
+                .removeHeader(CxfConstants.OPERATION_NAME)
+                .to("bean:inventoryServiceImpl?method=updateStock");
+    }
+}
diff --git 
a/rest-keycloak-soap-jms/src/main/java/org/acme/service/InventoryServiceImpl.java
 
b/rest-keycloak-soap-jms/src/main/java/org/acme/service/InventoryServiceImpl.java
new file mode 100644
index 00000000..34c41e34
--- /dev/null
+++ 
b/rest-keycloak-soap-jms/src/main/java/org/acme/service/InventoryServiceImpl.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acme.service;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Named;
+import org.acme.inventory.UpdateStockRequest;
+import org.acme.inventory.UpdateStockResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@ApplicationScoped
+@Named("inventoryServiceImpl")
+@RegisterForReflection(methods = true)
+public class InventoryServiceImpl {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(InventoryServiceImpl.class);
+
+    private final Map<String, Integer> inventory = new ConcurrentHashMap<>();
+
+    public InventoryServiceImpl() {
+        // Initialize with some sample inventory
+        inventory.put("PRODUCT-001", 100);
+        inventory.put("PRODUCT-002", 50);
+        inventory.put("PRODUCT-003", 75);
+    }
+
+    public UpdateStockResponse updateStock(UpdateStockRequest request) {
+        if (request == null || request.getProductId() == null) {
+            UpdateStockResponse response = new UpdateStockResponse();
+            response.setSuccess(false);
+            response.setMessage("Invalid request");
+            response.setNewStock(0);
+            return response;
+        }
+
+        LOG.info("SOAP: Updating stock for product: {}, quantity: {}", 
request.getProductId(), request.getQuantity());
+
+        Integer newStock = inventory.compute(request.getProductId(), 
(productId, currentStock) -> {
+            if (currentStock == null || currentStock - request.getQuantity() < 
0) {
+                return currentStock;
+            }
+            return currentStock - request.getQuantity();
+        });
+
+        UpdateStockResponse response = new UpdateStockResponse();
+        if (newStock != null) {
+            response.setSuccess(true);
+            response.setMessage("Stock updated successfully");
+            response.setNewStock(newStock);
+        } else {
+            response.setSuccess(false);
+            response.setMessage("Product not found or insufficient stock");
+            response.setNewStock(0);
+        }
+        return response;
+    }
+}
diff --git a/rest-keycloak-soap-jms/src/main/resources/application.properties 
b/rest-keycloak-soap-jms/src/main/resources/application.properties
new file mode 100644
index 00000000..201bff63
--- /dev/null
+++ b/rest-keycloak-soap-jms/src/main/resources/application.properties
@@ -0,0 +1,67 @@
+## ---------------------------------------------------------------------------
+## 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
+camel.context.name = rest-keycloak-soap-jms-example
+
+# AMQ Artemis (dev services auto-start)
+%prod.quarkus.artemis.url=tcp://localhost:61616
+%prod.quarkus.artemis.username=admin
+%prod.quarkus.artemis.password=admin
+
+# Production HTTP port
+%prod.quarkus.http.port=8081
+
+# Keycloak OIDC
+%prod.quarkus.oidc.auth-server-url=http://localhost:8082/realms/quarkus
+quarkus.oidc.client-id=quarkus-client
+quarkus.oidc.application-type=service
+
+# Keycloak Dev Services
+quarkus.keycloak.devservices.port=8082
+quarkus.keycloak.devservices.users.customer=customer-pass
+quarkus.keycloak.devservices.roles.customer=customer-role
+quarkus.keycloak.devservices.users.admin=admin-pass
+quarkus.keycloak.devservices.roles.admin=admin-role
+
+# HTTP Security - REST endpoints protected by Quarkus OIDC
+quarkus.http.auth.permission.customer.paths=/api/orders/*
+quarkus.http.auth.permission.customer.policy=authenticated
+quarkus.http.auth.permission.admin.paths=/api/admin/*
+quarkus.http.auth.permission.admin.policy=admin-policy
+quarkus.http.auth.policy.admin-policy.roles-allowed=admin-role
+
+# Keycloak Component Configuration (for Camel Keycloak component)
+camel.component.keycloak.server-url=http://localhost:8082
+camel.component.keycloak.realm=quarkus
+camel.component.keycloak.client-id=quarkus-client
+camel.component.keycloak.client-secret=${QUARKUS_OIDC_CREDENTIALS_SECRET:secret}
+
+# CXF SOAP
+quarkus.cxf.path=/services
+soap.inventory.client.address=http://localhost:8081/services/inventory
+
+# Kubernetes
+quarkus.kubernetes.env.secrets=quarkus-keycloak
+
+# Dev Configuration
+%dev.quarkus.artemis.devservices.enabled=true
+%dev.soap.inventory.client.address=http://localhost:8080/services/inventory
+
+# Test Configuration
+%test.quarkus.artemis.devservices.enabled=true
+%test.soap.inventory.client.address=http://localhost:8081/services/inventory
\ No newline at end of file
diff --git 
a/rest-keycloak-soap-jms/src/main/resources/wsdl/InventoryService.wsdl 
b/rest-keycloak-soap-jms/src/main/resources/wsdl/InventoryService.wsdl
new file mode 100644
index 00000000..41bc54c6
--- /dev/null
+++ b/rest-keycloak-soap-jms/src/main/resources/wsdl/InventoryService.wsdl
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<definitions name="InventoryService"
+             targetNamespace="http://acme.org/inventory";
+             xmlns="http://schemas.xmlsoap.org/wsdl/";
+             xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/";
+             xmlns:tns="http://acme.org/inventory";
+             xmlns:xsd="http://www.w3.org/2001/XMLSchema";>
+
+    <types>
+        <xsd:schema targetNamespace="http://acme.org/inventory";>
+            <xsd:element name="UpdateStockRequest">
+                <xsd:complexType>
+                    <xsd:sequence>
+                        <xsd:element name="productId" type="xsd:string"/>
+                        <xsd:element name="quantity" type="xsd:int"/>
+                    </xsd:sequence>
+                </xsd:complexType>
+            </xsd:element>
+            <xsd:element name="UpdateStockResponse">
+                <xsd:complexType>
+                    <xsd:sequence>
+                        <xsd:element name="success" type="xsd:boolean"/>
+                        <xsd:element name="message" type="xsd:string"/>
+                        <xsd:element name="newStock" type="xsd:int"/>
+                    </xsd:sequence>
+                </xsd:complexType>
+            </xsd:element>
+        </xsd:schema>
+    </types>
+
+    <message name="UpdateStockRequestMessage">
+        <part name="parameters" element="tns:UpdateStockRequest"/>
+    </message>
+    <message name="UpdateStockResponseMessage">
+        <part name="parameters" element="tns:UpdateStockResponse"/>
+    </message>
+
+    <portType name="InventoryServicePortType">
+        <operation name="updateStock">
+            <input message="tns:UpdateStockRequestMessage"/>
+            <output message="tns:UpdateStockResponseMessage"/>
+        </operation>
+    </portType>
+
+    <binding name="InventoryServiceSoapBinding" 
type="tns:InventoryServicePortType">
+        <soap:binding style="document" 
transport="http://schemas.xmlsoap.org/soap/http"/>
+        <operation name="updateStock">
+            <soap:operation soapAction=""/>
+            <input>
+                <soap:body use="literal"/>
+            </input>
+            <output>
+                <soap:body use="literal"/>
+            </output>
+        </operation>
+    </binding>
+
+    <service name="InventoryService">
+        <port name="InventoryServicePort" 
binding="tns:InventoryServiceSoapBinding">
+            <soap:address location="http://localhost:8080/services/inventory"/>
+        </port>
+    </service>
+</definitions>
diff --git 
a/rest-keycloak-soap-jms/src/test/java/org/acme/AmqBrokerKeycloakIT.java 
b/rest-keycloak-soap-jms/src/test/java/org/acme/AmqBrokerKeycloakIT.java
new file mode 100644
index 00000000..216f2219
--- /dev/null
+++ b/rest-keycloak-soap-jms/src/test/java/org/acme/AmqBrokerKeycloakIT.java
@@ -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.
+ */
+package org.acme;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+
+@QuarkusIntegrationTest
+class AmqBrokerKeycloakIT extends AmqBrokerKeycloakTest {
+
+}
diff --git 
a/rest-keycloak-soap-jms/src/test/java/org/acme/AmqBrokerKeycloakTest.java 
b/rest-keycloak-soap-jms/src/test/java/org/acme/AmqBrokerKeycloakTest.java
new file mode 100644
index 00000000..adb65d14
--- /dev/null
+++ b/rest-keycloak-soap-jms/src/test/java/org/acme/AmqBrokerKeycloakTest.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acme;
+
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.keycloak.client.KeycloakTestClient;
+import jakarta.ws.rs.core.Response;
+import org.eclipse.microprofile.config.ConfigProvider;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static io.restassured.RestAssured.given;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@QuarkusTest
+public class AmqBrokerKeycloakTest {
+
+    private static final String SAMPLE_ORDER_JSON = 
"{\"productId\":\"PRODUCT-001\",\"quantity\":5}";
+
+    KeycloakTestClient keycloakClient = new KeycloakTestClient();
+    private static String clientId;
+
+    @BeforeAll
+    public static void setUp() {
+        clientId = 
ConfigProvider.getConfig().getValue("quarkus.oidc.client-id", String.class);
+    }
+
+    @Test
+    public void orderSubmissionShouldRequireAuthentication() {
+        // Without authentication token, should get 401 Unauthorized
+        given()
+                .header("Content-Type", "application/json")
+                .body(SAMPLE_ORDER_JSON)
+                .when().post("/api/orders/submit")
+                .then()
+                .statusCode(Response.Status.UNAUTHORIZED.getStatusCode());
+    }
+
+    @Test
+    public void orderSubmissionWithAuthenticationShouldSucceed() {
+        // With valid customer token, order submission should succeed
+        // This validates the complete flow: REST → Keycloak auth → SOAP 
(sync) + JMS (async)
+        String response = given()
+                .auth().oauth2(getCustomerAccessToken())
+                .header("Content-Type", "application/json")
+                .body(SAMPLE_ORDER_JSON)
+                .when().post("/api/orders/submit")
+                .then()
+                .statusCode(Response.Status.OK.getStatusCode())
+                .extract().asString();
+
+        // Verify the SOAP response was received (synchronous response)
+        assertTrue(
+                response.contains("success") && response.contains("true"),
+                "Response should contain SOAP success result: " + response);
+    }
+
+    @Test
+    public void adminEndpointShouldRequireAdminRole() {
+        // Customer user should get 403 Forbidden (authenticated but not 
authorized)
+        given()
+                .auth().oauth2(getCustomerAccessToken())
+                .when().get("/api/admin/users")
+                .then()
+                .statusCode(Response.Status.FORBIDDEN.getStatusCode());
+    }
+
+    @Test
+    public void adminEndpointShouldWorkForAdminUser() {
+        // Admin user should get 200 OK with user list
+        String response = given()
+                .auth().oauth2(getAdminAccessToken())
+                .when().get("/api/admin/users")
+                .then()
+                .statusCode(Response.Status.OK.getStatusCode())
+                .extract().asString();
+
+        // Verify the response contains user data (should be JSON array)
+        assertTrue(
+                response.contains("customer") || response.contains("admin"),
+                "Response should contain user list: " + response);
+    }
+
+    private String getCustomerAccessToken() {
+        return keycloakClient.getAccessToken("customer", "customer-pass", 
clientId);
+    }
+
+    private String getAdminAccessToken() {
+        return keycloakClient.getAccessToken("admin", "admin-pass", clientId);
+    }
+}

Reply via email to