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