This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new d5c92b0656dd CAMEL-24113: camel-rest-openapi - Fix rest-openapi
producer cross-contamination for same operation
d5c92b0656dd is described below
commit d5c92b0656dd3d1d10f807f3f87662ea35d2dd88
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Jul 16 17:09:54 2026 +0200
CAMEL-24113: camel-rest-openapi - Fix rest-openapi producer
cross-contamination for same operation
Co-Authored-By: Claude Opus 4.6 <[email protected]>
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
Signed-off-by: Claus Ibsen <[email protected]>
Co-authored-by: Claude Opus 4.6 <[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 9c856e06b48e..67cf9b92e73c 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 f77769d89466..3a6d00bc2ed4 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
@@ -568,6 +568,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();