This is an automated email from the ASF dual-hosted git repository.
Croway 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 840c566a6b06 CAMEL-24589: Fix platform-http shared path consumer
removal on route stop (#26054)
840c566a6b06 is described below
commit 840c566a6b062d09066d2c1a44a2fad6f4e53c43
Author: Omar Atie <[email protected]>
AuthorDate: Fri Sep 4 02:15:23 2026 -0700
CAMEL-24589: Fix platform-http shared path consumer removal on route stop
(#26054)
* CAMEL-24589: Fix platform-http shared path consumer removal on route stop
When multiple platform-http consumers share the same path with different
HTTP methods, stopping one route must not unregister siblings. Track
HttpEndpointModel identity by consumer reference and remove endpoints by
consumer on doStop instead of by path alone.
Co-authored-by: Cursor <[email protected]>
* CAMEL-24589: Harden shared-path endpoint removal after review
Use LinkedHashSet instead of TreeSet for endpoint registry, guard null
consumer removal, and add restart/null-consumer lifecycle tests.
Co-authored-by: Cursor <[email protected]>
* CAMEL-24589: Address platform-http shared path PR review feedback
- Restore HttpEndpointModel Comparable with consumer-aware compareTo
- Refactor endpoint removal to a shared Predicate-based helper
- Add removeHttpEndpoint(String, Consumer) for null-consumer registrations
- Guard doStart registration when platformHttpConsumer is null
- Fix rest-openapi and MCP server to remove by consumer identity
- Stabilize MainHttpServerUtil startup summary across route reloads
- Document registry behavior change in the 4.23 upgrade guide
Co-authored-by: Cursor <[email protected]>
* Regen
---------
Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: github-actions[bot]
<github-actions[bot]@users.noreply.github.com>
---
.../mcp/server/vertx/VertxMcpServerEngine.java | 2 +-
.../platform/http/main/MainHttpServerUtil.java | 24 ++-
.../platform/http/DefaultPlatformHttpConsumer.java | 6 +-
.../component/platform/http/HttpEndpointModel.java | 19 ++-
.../platform/http/PlatformHttpComponent.java | 50 +++++-
.../platform/http/HttpEndpointModelTest.java | 72 +++++++++
.../PlatformHttpSharedPathRouteLifecycleTest.java | 179 +++++++++++++++++++++
.../DefaultRestOpenapiProcessorStrategy.java | 14 +-
.../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc | 19 +++
9 files changed, 360 insertions(+), 25 deletions(-)
diff --git
a/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/vertx/VertxMcpServerEngine.java
b/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/vertx/VertxMcpServerEngine.java
index 6b56a0e574b5..7da999cddc6b 100644
---
a/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/vertx/VertxMcpServerEngine.java
+++
b/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/vertx/VertxMcpServerEngine.java
@@ -147,7 +147,7 @@ public class VertxMcpServerEngine extends ServiceSupport
implements McpServerEng
PlatformHttpComponent platformHttpComponent
= (PlatformHttpComponent)
camelContext.hasComponent("platform-http");
if (platformHttpComponent != null && info != null) {
- platformHttpComponent.removeHttpEndpoint(info.path());
+ platformHttpComponent.removeHttpEndpoint(info.path(), null);
}
transport = null;
}
diff --git
a/components/camel-platform-http-main/src/main/java/org/apache/camel/component/platform/http/main/MainHttpServerUtil.java
b/components/camel-platform-http-main/src/main/java/org/apache/camel/component/platform/http/main/MainHttpServerUtil.java
index 782e7e5c1db6..5d5658b152d2 100644
---
a/components/camel-platform-http-main/src/main/java/org/apache/camel/component/platform/http/main/MainHttpServerUtil.java
+++
b/components/camel-platform-http-main/src/main/java/org/apache/camel/component/platform/http/main/MainHttpServerUtil.java
@@ -17,7 +17,9 @@
package org.apache.camel.component.platform.http.main;
import java.util.HashSet;
+import java.util.Objects;
import java.util.Set;
+import java.util.stream.Collectors;
import org.apache.camel.CamelContext;
import org.apache.camel.StartupListener;
@@ -35,14 +37,15 @@ public class MainHttpServerUtil {
CamelContext camelContext, Set<HttpEndpointModel> endpoints, int
serverPort, boolean ssl, String header)
throws Exception {
camelContext.addStartupListener(new StartupListener() {
- private volatile Set<HttpEndpointModel> last;
+ private volatile Set<String> lastEndpointSignatures;
private void logSummary() {
if (endpoints.isEmpty()) {
return;
}
- // log only if changed
- if (last == null || last.size() != endpoints.size() ||
!last.containsAll(endpoints)) {
+ // log only if changed (ignore consumer identity on route
reload)
+ Set<String> currentSignatures = endpointSignatures(endpoints);
+ if (lastEndpointSignatures == null ||
!lastEndpointSignatures.equals(currentSignatures)) {
LOG.info(header);
int longestEndpoint = 0;
int longestVerbs = 0;
@@ -78,8 +81,19 @@ public class MainHttpServerUtil {
}
}
- // use a defensive copy of last known endpoints
- last = new HashSet<>(endpoints);
+ lastEndpointSignatures = currentSignatures;
+ }
+
+ private Set<String> endpointSignatures(Set<HttpEndpointModel>
endpointModels) {
+ return endpointModels.stream()
+ .map(this::endpointSignature)
+ .collect(Collectors.toCollection(HashSet::new));
+ }
+
+ private String endpointSignature(HttpEndpointModel model) {
+ return model.getUri() + "|" +
Objects.toString(model.getVerbs(), "") + "|"
+ + Objects.toString(model.getConsumes(), "") + "|"
+ + Objects.toString(model.getProduces(), "");
}
private String getEndpoint(HttpEndpointModel httpEndpointModel,
boolean ssl) {
diff --git
a/components/camel-platform-http/src/main/java/org/apache/camel/component/platform/http/DefaultPlatformHttpConsumer.java
b/components/camel-platform-http/src/main/java/org/apache/camel/component/platform/http/DefaultPlatformHttpConsumer.java
index 70773c1d4020..3c7afd4cf982 100644
---
a/components/camel-platform-http/src/main/java/org/apache/camel/component/platform/http/DefaultPlatformHttpConsumer.java
+++
b/components/camel-platform-http/src/main/java/org/apache/camel/component/platform/http/DefaultPlatformHttpConsumer.java
@@ -107,7 +107,7 @@ public class DefaultPlatformHttpConsumer extends
DefaultConsumer
protected void doStart() throws Exception {
super.doStart();
ServiceHelper.startService(platformHttpConsumer);
- if (register) {
+ if (register && platformHttpConsumer != null) {
getComponent().addHttpEndpoint(getEndpoint().getPath(),
getEndpoint().getHttpMethodRestrict(),
getEndpoint().getConsumes(), getEndpoint().getProduces(),
platformHttpConsumer);
}
@@ -116,8 +116,8 @@ public class DefaultPlatformHttpConsumer extends
DefaultConsumer
@Override
protected void doStop() throws Exception {
super.doStop();
- if (register) {
- getComponent().removeHttpEndpoint(getEndpoint().getPath());
+ if (register && platformHttpConsumer != null) {
+ getComponent().removeHttpEndpoint(platformHttpConsumer);
}
ServiceHelper.stopAndShutdownServices(platformHttpConsumer);
}
diff --git
a/components/camel-platform-http/src/main/java/org/apache/camel/component/platform/http/HttpEndpointModel.java
b/components/camel-platform-http/src/main/java/org/apache/camel/component/platform/http/HttpEndpointModel.java
index 0e75f19ead6e..a323439205fa 100644
---
a/components/camel-platform-http/src/main/java/org/apache/camel/component/platform/http/HttpEndpointModel.java
+++
b/components/camel-platform-http/src/main/java/org/apache/camel/component/platform/http/HttpEndpointModel.java
@@ -96,16 +96,29 @@ public class HttpEndpointModel implements
Comparable<HttpEndpointModel> {
return false;
}
HttpEndpointModel that = (HttpEndpointModel) o;
- return uri.equals(that.uri);
+ return uri.equals(that.uri) && consumer == that.consumer;
}
@Override
public int hashCode() {
- return Objects.hash(uri);
+ return Objects.hash(uri, consumer);
}
@Override
public int compareTo(HttpEndpointModel o) {
- return uri.compareTo(o.uri);
+ int cmp = uri.compareTo(o.uri);
+ if (cmp != 0) {
+ return cmp;
+ }
+ if (consumer == o.consumer) {
+ return 0;
+ }
+ if (consumer == null) {
+ return -1;
+ }
+ if (o.consumer == null) {
+ return 1;
+ }
+ return Integer.compare(System.identityHashCode(consumer),
System.identityHashCode(o.consumer));
}
}
diff --git
a/components/camel-platform-http/src/main/java/org/apache/camel/component/platform/http/PlatformHttpComponent.java
b/components/camel-platform-http/src/main/java/org/apache/camel/component/platform/http/PlatformHttpComponent.java
index df2bdabc8030..3840abbb13fe 100644
---
a/components/camel-platform-http/src/main/java/org/apache/camel/component/platform/http/PlatformHttpComponent.java
+++
b/components/camel-platform-http/src/main/java/org/apache/camel/component/platform/http/PlatformHttpComponent.java
@@ -18,10 +18,11 @@ package org.apache.camel.component.platform.http;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.TreeSet;
+import java.util.function.Predicate;
import org.apache.camel.CamelContext;
import org.apache.camel.CamelContextAware;
@@ -72,8 +73,8 @@ public class PlatformHttpComponent extends
HeaderFilterStrategyComponent
+ " or all requests must be handled by Camel.")
private boolean serverRequestValidation = true;
- private final Set<HttpEndpointModel> httpEndpoints = new TreeSet<>();
- private final Set<HttpEndpointModel> httpManagementEndpoints = new
TreeSet<>();
+ private final Set<HttpEndpointModel> httpEndpoints = new LinkedHashSet<>();
+ private final Set<HttpEndpointModel> httpManagementEndpoints = new
LinkedHashSet<>();
private final List<PlatformHttpListener> listeners = new ArrayList<>();
private volatile boolean localEngine;
@@ -164,19 +165,54 @@ public class PlatformHttpComponent extends
HeaderFilterStrategyComponent
* Removes a known http endpoint managed by this component.
*/
public void removeHttpEndpoint(String uri) {
- this.removeHttpEndpoint(this.httpEndpoints, uri);
+ removeHttpEndpoints(this.httpEndpoints, e -> e.getUri().equals(uri));
+ }
+
+ /**
+ * Removes the http endpoint registered for the given consumer.
+ */
+ public void removeHttpEndpoint(Consumer consumer) {
+ if (consumer == null) {
+ return;
+ }
+ removeHttpEndpoints(this.httpEndpoints, e -> e.getConsumer() ==
consumer);
+ }
+
+ /**
+ * Removes the http endpoint registered for the given uri and consumer
reference.
+ * <p>
+ * Use this when multiple registrations share the same uri but have
different consumers, or when the registration
+ * used a {@code null} consumer (for example MCP server metadata).
+ * </p>
+ */
+ public void removeHttpEndpoint(String uri, Consumer consumer) {
+ removeHttpEndpoints(this.httpEndpoints, e -> e.getUri().equals(uri) &&
e.getConsumer() == consumer);
}
/**
* Removes a known http endpoint managed by this component.
*/
public void removeHttpManagementEndpoint(String uri) {
- this.removeHttpEndpoint(this.httpManagementEndpoints, uri);
+ removeHttpEndpoints(this.httpManagementEndpoints, e ->
e.getUri().equals(uri));
+ }
+
+ /**
+ * Removes the http management endpoint registered for the given consumer.
+ * <p>
+ * Provided for symmetry with {@link
#removeHttpManagementEndpoint(String)} for callers that track a management
+ * consumer reference.
+ * </p>
+ */
+ public void removeHttpManagementEndpoint(Consumer consumer) {
+ if (consumer == null) {
+ return;
+ }
+ removeHttpEndpoints(this.httpManagementEndpoints, e -> e.getConsumer()
== consumer);
}
- private void removeHttpEndpoint(Set<HttpEndpointModel> endpoints, String
uri) {
+ private void removeHttpEndpoints(Set<HttpEndpointModel> endpoints,
Predicate<HttpEndpointModel> filter) {
List<HttpEndpointModel> toRemove = new ArrayList<>();
- endpoints.stream().filter(e -> e.getUri().equals(uri)).forEach(model
-> {
+ endpoints.stream().filter(filter).forEach(model -> {
toRemove.add(model);
for (PlatformHttpListener listener : listeners) {
try {
diff --git
a/components/camel-platform-http/src/test/java/org/apache/camel/component/platform/http/HttpEndpointModelTest.java
b/components/camel-platform-http/src/test/java/org/apache/camel/component/platform/http/HttpEndpointModelTest.java
new file mode 100644
index 000000000000..0be24d293c87
--- /dev/null
+++
b/components/camel-platform-http/src/test/java/org/apache/camel/component/platform/http/HttpEndpointModelTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.apache.camel.component.platform.http;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.camel.Consumer;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+
+class HttpEndpointModelTest {
+
+ @Test
+ void setRetainsMultipleConsumersOnSamePath() {
+ Consumer getConsumer = mock(Consumer.class);
+ Consumer postConsumer = mock(Consumer.class);
+
+ HttpEndpointModel getModel = new HttpEndpointModel("/shared", "GET",
null, null, getConsumer);
+ HttpEndpointModel postModel = new HttpEndpointModel("/shared", "POST",
null, null, postConsumer);
+
+ Set<HttpEndpointModel> endpoints = new HashSet<>();
+ assertTrue(endpoints.add(getModel));
+ assertTrue(endpoints.add(postModel));
+ assertEquals(2, endpoints.size());
+ }
+
+ @Test
+ void equalsAndHashCodeUseConsumerIdentity() {
+ Consumer first = mock(Consumer.class);
+ Consumer second = mock(Consumer.class);
+
+ HttpEndpointModel firstModel = new HttpEndpointModel("/shared", "GET",
null, null, first);
+ HttpEndpointModel secondModel = new HttpEndpointModel("/shared",
"POST", null, null, second);
+ HttpEndpointModel sameConsumerModel = new HttpEndpointModel("/shared",
"GET", null, null, first);
+
+ assertNotEquals(firstModel, secondModel);
+ assertEquals(firstModel, sameConsumerModel);
+ assertEquals(firstModel.hashCode(), sameConsumerModel.hashCode());
+ }
+
+ @Test
+ void compareToIsConsistentWithEquals() {
+ Consumer first = mock(Consumer.class);
+ Consumer second = mock(Consumer.class);
+
+ HttpEndpointModel firstModel = new HttpEndpointModel("/shared", "GET",
null, null, first);
+ HttpEndpointModel secondModel = new HttpEndpointModel("/shared",
"POST", null, null, second);
+ HttpEndpointModel sameConsumerModel = new HttpEndpointModel("/shared",
"GET", null, null, first);
+
+ assertEquals(0, firstModel.compareTo(sameConsumerModel));
+ assertNotEquals(0, firstModel.compareTo(secondModel));
+ }
+}
diff --git
a/components/camel-platform-http/src/test/java/org/apache/camel/component/platform/http/PlatformHttpSharedPathRouteLifecycleTest.java
b/components/camel-platform-http/src/test/java/org/apache/camel/component/platform/http/PlatformHttpSharedPathRouteLifecycleTest.java
new file mode 100644
index 000000000000..10af266d1487
--- /dev/null
+++
b/components/camel-platform-http/src/test/java/org/apache/camel/component/platform/http/PlatformHttpSharedPathRouteLifecycleTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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.apache.camel.component.platform.http;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.camel.Consumer;
+import org.apache.camel.Endpoint;
+import org.apache.camel.Processor;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.platform.http.spi.PlatformHttpConsumer;
+import org.apache.camel.component.platform.http.spi.PlatformHttpEngine;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.DefaultConsumer;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+
+class PlatformHttpSharedPathRouteLifecycleTest {
+
+ @Test
+ void stoppingSecondConsumerPreservesFirstConsumerOnSamePath() throws
Exception {
+ RecordingPlatformHttpListener listener = new
RecordingPlatformHttpListener();
+
+ try (DefaultCamelContext context = new DefaultCamelContext()) {
+ PlatformHttpComponent component = createComponent(listener,
context);
+ context.addRoutes(sharedPathRoutes());
+ context.start();
+
+ assertEquals(2, component.getHttpEndpoints().size());
+ assertEquals(2, listener.registered.size());
+
+ context.getRouteController().stopRoute("shared-post");
+
+ assertEquals(1, component.getHttpEndpoints().size());
+ assertEquals(1, listener.registered.size());
+ HttpEndpointModel remaining = listener.registered.get(0);
+ assertEquals("/shared", remaining.getUri());
+ assertEquals("GET", remaining.getVerbs());
+ }
+ }
+
+ @Test
+ void stoppingFirstConsumerPreservesSecondConsumerOnSamePath() throws
Exception {
+ RecordingPlatformHttpListener listener = new
RecordingPlatformHttpListener();
+
+ try (DefaultCamelContext context = new DefaultCamelContext()) {
+ PlatformHttpComponent component = createComponent(listener,
context);
+ context.addRoutes(sharedPathRoutes());
+ context.start();
+
+ context.getRouteController().stopRoute("shared-get");
+
+ assertEquals(1, component.getHttpEndpoints().size());
+ assertEquals(1, listener.registered.size());
+ HttpEndpointModel remaining = listener.registered.get(0);
+ assertEquals("/shared", remaining.getUri());
+ assertEquals("POST", remaining.getVerbs());
+ }
+ }
+
+ @Test
+ void restartRouteAfterStopReRegistersEndpoint() throws Exception {
+ RecordingPlatformHttpListener listener = new
RecordingPlatformHttpListener();
+
+ try (DefaultCamelContext context = new DefaultCamelContext()) {
+ PlatformHttpComponent component = createComponent(listener,
context);
+ context.addRoutes(sharedPathRoutes());
+ context.start();
+
+ context.getRouteController().stopRoute("shared-post");
+ assertEquals(1, component.getHttpEndpoints().size());
+
+ context.getRouteController().startRoute("shared-post");
+ assertEquals(2, component.getHttpEndpoints().size());
+ assertEquals(2, listener.registered.size());
+ }
+ }
+
+ @Test
+ void removeHttpEndpointByUriRemovesAllConsumersOnPath() {
+ PlatformHttpComponent component = new PlatformHttpComponent();
+ Consumer getConsumer = mock(Consumer.class);
+ Consumer postConsumer = mock(Consumer.class);
+
+ component.addHttpEndpoint("/shared", "GET", null, null, getConsumer);
+ component.addHttpEndpoint("/shared", "POST", null, null, postConsumer);
+
+ assertEquals(2, component.getHttpEndpoints().size());
+
+ component.removeHttpEndpoint("/shared");
+
+ assertTrue(component.getHttpEndpoints().isEmpty());
+ }
+
+ @Test
+ void removeHttpEndpointWithNullConsumerIsIgnored() {
+ PlatformHttpComponent component = new PlatformHttpComponent();
+ Consumer routeConsumer = mock(Consumer.class);
+
+ component.addHttpEndpoint("/static", null, null, null, null);
+ component.addHttpEndpoint("/shared", "GET", null, null, routeConsumer);
+
+ component.removeHttpEndpoint((Consumer) null);
+
+ assertEquals(2, component.getHttpEndpoints().size());
+ }
+
+ private static PlatformHttpComponent
createComponent(RecordingPlatformHttpListener listener, DefaultCamelContext
context) {
+ PlatformHttpComponent component = new PlatformHttpComponent();
+ component.setEngine(new NoopEngine());
+ component.addPlatformHttpListener(listener);
+ context.addComponent("platform-http", component);
+ return component;
+ }
+
+ private static RouteBuilder sharedPathRoutes() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("platform-http:/shared?httpMethodRestrict=GET")
+ .routeId("shared-get")
+ .setBody().constant("shared-get");
+ from("platform-http:/shared?httpMethodRestrict=POST")
+ .routeId("shared-post")
+ .setBody().constant("shared-post");
+ }
+ };
+ }
+
+ private static final class RecordingPlatformHttpListener implements
PlatformHttpListener {
+ private final List<HttpEndpointModel> registered = new ArrayList<>();
+
+ @Override
+ public void registerHttpEndpoint(HttpEndpointModel model) {
+ registered.add(model);
+ }
+
+ @Override
+ public void unregisterHttpEndpoint(HttpEndpointModel model) {
+ registered.remove(model);
+ }
+ }
+
+ private static final class NoopEngine implements PlatformHttpEngine {
+ @Override
+ public PlatformHttpConsumer createConsumer(PlatformHttpEndpoint
platformHttpEndpoint, Processor processor) {
+ return new NoopPlatformHttpConsumer(platformHttpEndpoint,
processor);
+ }
+ }
+
+ private static final class NoopPlatformHttpConsumer extends
DefaultConsumer implements PlatformHttpConsumer {
+ private NoopPlatformHttpConsumer(Endpoint endpoint, Processor
processor) {
+ super(endpoint, processor);
+ }
+
+ @Override
+ public PlatformHttpEndpoint getEndpoint() {
+ return (PlatformHttpEndpoint) super.getEndpoint();
+ }
+ }
+}
diff --git
a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/DefaultRestOpenapiProcessorStrategy.java
b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/DefaultRestOpenapiProcessorStrategy.java
index 43b6056540c8..9478ecad5ba8 100644
---
a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/DefaultRestOpenapiProcessorStrategy.java
+++
b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/DefaultRestOpenapiProcessorStrategy.java
@@ -34,6 +34,7 @@ import org.apache.camel.AsyncCallback;
import org.apache.camel.AsyncProducer;
import org.apache.camel.CamelContext;
import org.apache.camel.CamelContextAware;
+import org.apache.camel.Consumer;
import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
import org.apache.camel.NamedNode;
@@ -74,7 +75,7 @@ public class DefaultRestOpenapiProcessorStrategy extends
ServiceSupport
private String component = "direct";
private String missingOperation;
private String mockIncludePattern;
- private final List<String> uris = new ArrayList<>();
+ private Consumer registeredPlatformHttpConsumer;
@Override
public void validateOpenApi(OpenAPI openAPI, String basePath,
PlatformHttpConsumerAware platformHttpConsumer)
@@ -150,8 +151,9 @@ public class DefaultRestOpenapiProcessorStrategy extends
ServiceSupport
}
}
}
- phc.addHttpEndpoint(uri, verbs, consumes, produces,
platformHttpConsumer.getPlatformHttpConsumer());
- uris.add(uri);
+ Consumer consumer =
platformHttpConsumer.getPlatformHttpConsumer();
+ phc.addHttpEndpoint(uri, verbs, consumes, produces, consumer);
+ registeredPlatformHttpConsumer = consumer;
}
}
}
@@ -452,9 +454,9 @@ public class DefaultRestOpenapiProcessorStrategy extends
ServiceSupport
if (camelContext != null) {
PlatformHttpComponent phc = (PlatformHttpComponent)
camelContext.hasComponent("platform-http");
- if (phc != null) {
- uris.forEach(phc::removeHttpEndpoint);
- uris.clear();
+ if (phc != null && registeredPlatformHttpConsumer != null) {
+ phc.removeHttpEndpoint(registeredPlatformHttpConsumer);
+ registeredPlatformHttpConsumer = null;
}
}
}
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index 2a62a71a8cb6..25d87474a783 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -1627,3 +1627,22 @@ The `backOffMaxAttempts` option now bounds the attempts
to start the delegated c
The retry task previously also carried the default five second duration of its
budget, which ended the
task before the second attempt for any `backOffDelay` at or above the default
of five seconds. A delegate
that fails to start is therefore retried for longer than before, up to
`backOffMaxAttempts` times.
+
+=== camel-platform-http - shared path endpoint registry
+
+`HttpEndpointModel` identity now includes the registered consumer reference,
so multiple consumers can
+share the same path with different HTTP methods without overwriting each other
in
+`PlatformHttpComponent#getHttpEndpoints()`.
+
+* `getHttpEndpoints()` and `getHttpManagementEndpoints()` may list multiple
entries for the same URI
+ (one per consumer). Ordering follows registration order (`LinkedHashSet`)
instead of URI sort order
+ (`TreeSet`).
+* `DefaultPlatformHttpConsumer` removes its own registration on stop via
+ `removeHttpEndpoint(Consumer)` instead of removing every endpoint on the
path.
+* `removeHttpEndpoint(String)` still removes all registrations for a URI (for
example bulk cleanup).
+ Prefer `removeHttpEndpoint(Consumer)` or `removeHttpEndpoint(String,
Consumer)` when only one
+ registration should be removed.
+* `HttpEndpointModel#compareTo` remains available and is consistent with the
consumer-aware
+ `equals`/`hashCode` implementation.
+
+Stopping one `platform-http` route on a shared path no longer unregisters
sibling consumers on that path.