This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch fix/CAMEL-24113 in repository https://gitbox.apache.org/repos/asf/camel.git
commit 81efec1b4edea3a159a2af542d3bad375bfe9f1d Author: Claus Ibsen <[email protected]> AuthorDate: Thu Jul 16 15:59:39 2026 +0200 CAMEL-24113: camel-rest-openapi - Include produces, consumes, queryParameters, and producerComponentName in delegate rest: endpoint URI to prevent cross-contamination between routes targeting the same operation Co-Authored-By: Claude Opus 4.6 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../rest/openapi/RestOpenApiEndpoint.java | 34 +++++- .../rest/openapi/RestOpenApiEndpointV3Test.java | 121 +++++++++++++++++++++ 2 files changed, 154 insertions(+), 1 deletion(-) diff --git a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpoint.java b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpoint.java index 761ca925126c..a8462b7374bc 100644 --- a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpoint.java +++ b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpoint.java @@ -559,8 +559,40 @@ public final class RestOpenApiEndpoint extends DefaultEndpoint { boolean hasHost = params.containsKey("host"); String basePath = determineBasePath(openapi); String componentEndpointUri = "rest:" + method + ":" + basePath + ":" + uriTemplate; + + // include all distinguishing options in the URI so each unique combination + // gets its own cached endpoint and avoids cross-contamination (CAMEL-24113) + StringBuilder query = new StringBuilder(); if (hasHost) { - componentEndpointUri += "?host=" + params.get("host"); + query.append("host=").append(params.get("host")); + } + if (params.containsKey("producerComponentName")) { + if (!query.isEmpty()) { + query.append('&'); + } + query.append("producerComponentName=").append(params.get("producerComponentName")); + } + if (params.containsKey("consumes")) { + if (!query.isEmpty()) { + query.append('&'); + } + query.append("consumes=").append(params.get("consumes")); + } + if (params.containsKey("produces")) { + if (!query.isEmpty()) { + query.append('&'); + } + query.append("produces=").append(params.get("produces")); + } + if (params.containsKey("queryParameters")) { + if (!query.isEmpty()) { + query.append('&'); + } + query.append("queryParameters=") + .append(UnsafeUriCharactersEncoder.encode(params.get("queryParameters").toString())); + } + if (!query.isEmpty()) { + componentEndpointUri += "?" + query; } Endpoint endpoint = camelContext.getEndpoint(componentEndpointUri); diff --git a/components/camel-rest-openapi/src/test/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpointV3Test.java b/components/camel-rest-openapi/src/test/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpointV3Test.java index 24ed458fb49f..30c1e47e438f 100644 --- a/components/camel-rest-openapi/src/test/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpointV3Test.java +++ b/components/camel-rest-openapi/src/test/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpointV3Test.java @@ -504,6 +504,127 @@ public class RestOpenApiEndpointV3Test { assertThat(endpoint.literalPathParameterValue(pathParameter)).isEqualTo("va%20lue"); } + @Test + public void shouldNotCrossContaminateProducersForSameOperation() throws Exception { + final CamelContext camelContext = new DefaultCamelContext(); + camelContext.start(); + + try { + final RestOpenApiComponent component = new RestOpenApiComponent(camelContext); + camelContext.addComponent("rest-openapi", component); + + final OpenAPI openapi = new OpenAPI(); + openapi.addServersItem(new Server().url("http://petstore.example.com")); + final Operation operation = new Operation().operationId("findPets"); + + // first endpoint: produces JSON + final RestOpenApiEndpoint endpoint1 = new RestOpenApiEndpoint( + "rest-openapi:spec#findPets", "spec#findPets", component, Collections.emptyMap()); + endpoint1.setCamelContext(camelContext); + endpoint1.setHost("http://petstore.example.com"); + endpoint1.setProduces("application/json"); + + // second endpoint: produces XML + final RestOpenApiEndpoint endpoint2 = new RestOpenApiEndpoint( + "rest-openapi:spec#findPets", "spec#findPets", component, Collections.emptyMap()); + endpoint2.setCamelContext(camelContext); + endpoint2.setHost("http://petstore.example.com"); + endpoint2.setProduces("application/xml"); + + endpoint1.createProducerFor(openapi, operation, "get", "/pets"); + endpoint2.createProducerFor(openapi, operation, "get", "/pets"); + + // the two delegate rest: endpoints must be distinct instances + long restEndpointCount = camelContext.getEndpoints().stream() + .filter(e -> e.getEndpointUri().startsWith("rest:")) + .count(); + assertThat(restEndpointCount) + .as("Two rest-openapi endpoints with different 'produces' should create two distinct rest: delegate endpoints") + .isEqualTo(2); + + // verify each has the correct produces value + org.apache.camel.component.rest.RestEndpoint restEp1 = camelContext.getEndpoints().stream() + .filter(e -> e.getEndpointUri().startsWith("rest:")) + .filter(e -> e.getEndpointUri().contains("produces=application/json")) + .map(org.apache.camel.component.rest.RestEndpoint.class::cast) + .findFirst().orElseThrow(); + assertThat(restEp1.getProduces()).isEqualTo("application/json"); + + org.apache.camel.component.rest.RestEndpoint restEp2 = camelContext.getEndpoints().stream() + .filter(e -> e.getEndpointUri().startsWith("rest:")) + .filter(e -> e.getEndpointUri().contains("produces=application/xml")) + .map(org.apache.camel.component.rest.RestEndpoint.class::cast) + .findFirst().orElseThrow(); + assertThat(restEp2.getProduces()).isEqualTo("application/xml"); + } finally { + camelContext.stop(); + } + } + + @Test + public void shouldNotCrossContaminateQueryParameters() throws Exception { + final CamelContext camelContext = new DefaultCamelContext(); + camelContext.start(); + + try { + final RestOpenApiComponent component = new RestOpenApiComponent(camelContext); + camelContext.addComponent("rest-openapi", component); + + final OpenAPI openapi = new OpenAPI(); + openapi.addServersItem(new Server().url("http://petstore.example.com")); + final Operation operation = new Operation().operationId("findPetsByStatus"); + + // first endpoint: status=available + Map<String, Object> params1 = new HashMap<>(); + params1.put("status", "available"); + final RestOpenApiEndpoint endpoint1 = new RestOpenApiEndpoint( + "rest-openapi:spec#findPetsByStatus", "spec#findPetsByStatus", component, params1); + endpoint1.setCamelContext(camelContext); + endpoint1.setHost("http://petstore.example.com"); + + // second endpoint: status=sold + Map<String, Object> params2 = new HashMap<>(); + params2.put("status", "sold"); + final RestOpenApiEndpoint endpoint2 = new RestOpenApiEndpoint( + "rest-openapi:spec#findPetsByStatus", "spec#findPetsByStatus", component, params2); + endpoint2.setCamelContext(camelContext); + endpoint2.setHost("http://petstore.example.com"); + + // add a query parameter to the operation + operation.addParametersItem(new Parameter().name("status").in("query").required(true)); + + endpoint1.createProducerFor(openapi, operation, "get", "/pet/findByStatus"); + endpoint2.createProducerFor(openapi, operation, "get", "/pet/findByStatus"); + + // verify two distinct delegate endpoints + long restEndpointCount = camelContext.getEndpoints().stream() + .filter(e -> e.getEndpointUri().startsWith("rest:")) + .count(); + assertThat(restEndpointCount) + .as("Two rest-openapi endpoints with different queryParameters should create two distinct rest: delegate endpoints") + .isEqualTo(2); + + // verify each has the correct query parameter value + org.apache.camel.component.rest.RestEndpoint restEp1 = camelContext.getEndpoints().stream() + .filter(e -> e.getEndpointUri().startsWith("rest:")) + .filter(e -> e instanceof org.apache.camel.component.rest.RestEndpoint) + .map(org.apache.camel.component.rest.RestEndpoint.class::cast) + .filter(e -> e.getQueryParameters() != null && e.getQueryParameters().contains("status=available")) + .findFirst().orElseThrow(); + assertThat(restEp1.getQueryParameters()).contains("status=available"); + + org.apache.camel.component.rest.RestEndpoint restEp2 = camelContext.getEndpoints().stream() + .filter(e -> e.getEndpointUri().startsWith("rest:")) + .filter(e -> e instanceof org.apache.camel.component.rest.RestEndpoint) + .map(org.apache.camel.component.rest.RestEndpoint.class::cast) + .filter(e -> e.getQueryParameters() != null && e.getQueryParameters().contains("status=sold")) + .findFirst().orElseThrow(); + assertThat(restEp2.getQueryParameters()).contains("status=sold"); + } finally { + camelContext.stop(); + } + } + @Test public void shouldUseDefaultSpecificationUri() { final RestOpenApiComponent component = new RestOpenApiComponent();
