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 1d90e534 rest-keycloak-soap-jms: encode JSON responses with the REST 
binding
1d90e534 is described below

commit 1d90e5344f374b7b3f6fb570fd66ce5171276394
Author: James Netherton <[email protected]>
AuthorDate: Mon Aug 10 17:25:09 2026 +0100

    rest-keycloak-soap-jms: encode JSON responses with the REST binding
    
    The order response was assembled with String.format, interpolating the
    message returned by the SOAP service straight into a JSON template. A
    quote or backslash in that message produces malformed JSON. Build an
    OrderResponse instead and let the existing json binding mode encode it.
    
    The Keycloak admin error handler embedded ${exception.message} in a
    constant(), so the placeholder was emitted literally rather than
    resolved. Return a static message and keep the exception detail in the
    log, which now runs at ERROR level.
    
    Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
---
 .../main/java/org/acme/model/OrderResponse.java    | 59 ++++++++++++++++++++++
 .../java/org/acme/routes/KeycloakAdminRoute.java   |  7 +--
 .../main/java/org/acme/routes/RestOrderRoute.java  | 15 +++---
 .../test/java/org/acme/AmqBrokerKeycloakTest.java  | 13 +++--
 4 files changed, 77 insertions(+), 17 deletions(-)

diff --git 
a/rest-keycloak-soap-jms/src/main/java/org/acme/model/OrderResponse.java 
b/rest-keycloak-soap-jms/src/main/java/org/acme/model/OrderResponse.java
new file mode 100644
index 00000000..9f4c24b5
--- /dev/null
+++ b/rest-keycloak-soap-jms/src/main/java/org/acme/model/OrderResponse.java
@@ -0,0 +1,59 @@
+/*
+ * 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 OrderResponse {
+    private boolean success;
+    private String message;
+    private int newStock;
+
+    public OrderResponse() {
+    }
+
+    public OrderResponse(boolean success, String message, int newStock) {
+        this.success = success;
+        this.message = message;
+        this.newStock = newStock;
+    }
+
+    public boolean isSuccess() {
+        return success;
+    }
+
+    public void setSuccess(boolean success) {
+        this.success = success;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public int getNewStock() {
+        return newStock;
+    }
+
+    public void setNewStock(int newStock) {
+        this.newStock = newStock;
+    }
+}
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
index 4f7c950f..e80d8364 100644
--- 
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
@@ -22,6 +22,7 @@ import java.util.List;
 import java.util.Map;
 
 import jakarta.enterprise.context.ApplicationScoped;
+import org.apache.camel.LoggingLevel;
 import org.apache.camel.builder.RouteBuilder;
 
 @ApplicationScoped
@@ -29,13 +30,13 @@ public class KeycloakAdminRoute extends RouteBuilder {
 
     @Override
     public void configure() {
-        // Error handling for Keycloak operations
+        // Error handling for Keycloak operations. Exception details are 
logged, not returned to the caller
         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}");
+                .setBody(constant("{\"error\": \"Internal server error\"}"))
+                .log(LoggingLevel.ERROR, "Error in Keycloak admin route: 
${exception.message}");
 
         rest("/api/admin")
                 .get("/users")
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
index a8027ece..cf70b477 100644
--- 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
@@ -20,6 +20,8 @@ import jakarta.enterprise.context.ApplicationScoped;
 import org.acme.inventory.UpdateStockRequest;
 import org.acme.inventory.UpdateStockResponse;
 import org.acme.model.Order;
+import org.acme.model.OrderResponse;
+import org.apache.camel.Exchange;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.model.rest.RestBindingMode;
 
@@ -35,6 +37,7 @@ public class RestOrderRoute extends RouteBuilder {
                 .produces("application/json")
                 .bindingMode(RestBindingMode.json)
                 .type(Order.class)
+                .outType(OrderResponse.class)
                 .to("direct:process-order");
 
         // Main route: REST → SOAP (synchronous) + async event notification
@@ -53,17 +56,15 @@ public class RestOrderRoute extends RouteBuilder {
                 })
                 // Sync: Call SOAP service and wait for response
                 .to("cxf:bean:inventoryServiceClient")
-                // Return SOAP response to REST client
+                // Return SOAP response to REST client, JSON encoding is 
handled by the REST binding
                 .process(exchange -> {
                     UpdateStockResponse soapResponse = 
exchange.getIn().getBody(UpdateStockResponse.class);
-                    String jsonResponse = String.format(
-                            
"{\"success\":%b,\"message\":\"%s\",\"newStock\":%d}",
+                    exchange.getIn().setBody(new OrderResponse(
                             soapResponse.isSuccess(),
                             soapResponse.getMessage(),
-                            soapResponse.getNewStock());
-                    exchange.getIn().setBody(jsonResponse);
-                    exchange.getIn().setHeader("Content-Type", 
"application/json");
-                });
+                            soapResponse.getNewStock()));
+                })
+                .setHeader(Exchange.CONTENT_TYPE).constant("application/json");
 
         // Async event publisher: send to JMS topic
         from("direct:order-events")
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
index adb65d14..c0ac33d4 100644
--- a/rest-keycloak-soap-jms/src/test/java/org/acme/AmqBrokerKeycloakTest.java
+++ b/rest-keycloak-soap-jms/src/test/java/org/acme/AmqBrokerKeycloakTest.java
@@ -18,12 +18,14 @@ package org.acme;
 
 import io.quarkus.test.junit.QuarkusTest;
 import io.quarkus.test.keycloak.client.KeycloakTestClient;
+import io.restassured.http.ContentType;
 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.hamcrest.Matchers.is;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 @QuarkusTest
@@ -54,19 +56,16 @@ public class AmqBrokerKeycloakTest {
     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()
+        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);
+                .contentType(ContentType.JSON)
+                .body("success", is(true))
+                .body("message", is("Stock updated successfully"));
     }
 
     @Test

Reply via email to