This is an automated email from the ASF dual-hosted git repository. JiriOndrusek pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-quarkus-examples.git
commit 1156929f3894f6837904a992df59607eb662a7c7 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
