You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2024/03/28 14:40:24 UTC
(camel) 10/38: CAMEL-20557: Rest DSL to use openapi spec directly
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch openapi2
in repository https://gitbox.apache.org/repos/asf/camel.git
commit 4467903c031064f3a5437a2930c81011033a0009
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Mon Mar 25 08:17:18 2024 +0100
CAMEL-20557: Rest DSL to use openapi spec directly
---
.../vertx/PlatformHttpRestOpenApiConsumerTest.java | 2 +-
...va => DefaultRestOpenapiProcessorStrategy.java} | 55 +++++--------
.../rest/openapi/RestOpenApiConsumerPath.java | 53 ++++++++++++
.../rest/openapi/RestOpenApiProcessor.java | 93 ++++++++++++++++------
.../rest/openapi/RestOpenapiProcessorStrategy.java | 27 +++++++
.../support/RestConsumerContextPathMatcher.java | 3 +-
6 files changed, 170 insertions(+), 63 deletions(-)
diff --git a/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/PlatformHttpRestOpenApiConsumerTest.java b/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/PlatformHttpRestOpenApiConsumerTest.java
index 7fb2ec61db9..ab01d018caa 100644
--- a/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/PlatformHttpRestOpenApiConsumerTest.java
+++ b/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/PlatformHttpRestOpenApiConsumerTest.java
@@ -45,7 +45,7 @@ public class PlatformHttpRestOpenApiConsumerTest {
given()
.when()
- .get("/api/v3/getPetById")
+ .get("/api/v3/pet/123")
.then()
.statusCode(200)
.body(equalTo("{\"pet\": \"tony the tiger\"}"));
diff --git a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiProcessor.java b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/DefaultRestOpenapiProcessorStrategy.java
similarity index 62%
copy from components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiProcessor.java
copy to components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/DefaultRestOpenapiProcessorStrategy.java
index bc9767eaadd..965b9625519 100644
--- a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiProcessor.java
+++ b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/DefaultRestOpenapiProcessorStrategy.java
@@ -16,30 +16,36 @@
*/
package org.apache.camel.component.rest.openapi;
-import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.Operation;
import org.apache.camel.AsyncCallback;
import org.apache.camel.AsyncProducer;
import org.apache.camel.CamelContext;
import org.apache.camel.CamelContextAware;
import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
-import org.apache.camel.Processor;
+import org.apache.camel.NonManagedService;
import org.apache.camel.spi.ProducerCache;
import org.apache.camel.support.cache.DefaultProducerCache;
-import org.apache.camel.support.processor.DelegateAsyncProcessor;
import org.apache.camel.support.service.ServiceHelper;
+import org.apache.camel.support.service.ServiceSupport;
-public class RestOpenApiProcessor extends DelegateAsyncProcessor implements CamelContextAware {
+public class DefaultRestOpenapiProcessorStrategy extends ServiceSupport
+ implements RestOpenapiProcessorStrategy, CamelContextAware, NonManagedService {
private CamelContext camelContext;
- private final OpenAPI openAPI;
- private final String basePath;
private ProducerCache producerCache;
- public RestOpenApiProcessor(OpenAPI openAPI, String basePath, Processor processor) {
- super(processor);
- this.basePath = basePath;
- this.openAPI = openAPI;
+ @Override
+ public boolean process(Operation operation, String path, Exchange exchange, AsyncCallback callback) {
+ Endpoint e = camelContext.getEndpoint("direct:" + operation.getOperationId());
+ AsyncProducer p = producerCache.acquireProducer(e);
+ return p.process(exchange, doneSync -> {
+ try {
+ producerCache.releaseProducer(e, p);
+ } finally {
+ callback.done(doneSync);
+ }
+ });
}
@Override
@@ -52,46 +58,21 @@ public class RestOpenApiProcessor extends DelegateAsyncProcessor implements Came
this.camelContext = camelContext;
}
- @Override
- public boolean process(Exchange exchange, AsyncCallback callback) {
- String path = exchange.getMessage().getHeader(Exchange.HTTP_PATH, String.class);
- if (path != null && path.startsWith(basePath)) {
- path = path.substring(basePath.length() + 1);
- }
-
- // TODO: choose processor strategy (mapping by operation id -> direct)
- // TODO: check if valid operation according to OpenApi
- // TODO: validate GET/POST etc
- // TODO: 404 and so on
- // TODO: binding
-
- Endpoint e = camelContext.getEndpoint("direct:" + path);
- AsyncProducer p = producerCache.acquireProducer(e);
- return p.process(exchange, new AsyncCallback() {
- @Override
- public void done(boolean doneSync) {
- producerCache.releaseProducer(e, p);
- callback.done(doneSync);
- }
- });
- }
-
@Override
protected void doInit() throws Exception {
- super.doInit();
+ // TODO: non-managed
producerCache = new DefaultProducerCache(this, getCamelContext(), 1000);
ServiceHelper.initService(producerCache);
}
@Override
protected void doStart() throws Exception {
- super.doStart();
ServiceHelper.startService(producerCache);
}
@Override
protected void doStop() throws Exception {
- super.doStop();
ServiceHelper.stopService(producerCache);
}
+
}
diff --git a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiConsumerPath.java b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiConsumerPath.java
new file mode 100644
index 00000000000..d94684eb9b2
--- /dev/null
+++ b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiConsumerPath.java
@@ -0,0 +1,53 @@
+/*
+ * 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.rest.openapi;
+
+import io.swagger.v3.oas.models.Operation;
+import org.apache.camel.support.RestConsumerContextPathMatcher;
+
+class RestOpenApiConsumerPath implements RestConsumerContextPathMatcher.ConsumerPath<Operation> {
+
+ private final String verb;
+ private final String path;
+ private final Operation consumer;
+
+ public RestOpenApiConsumerPath(String verb, String path, Operation consumer) {
+ this.verb = verb;
+ this.path = path;
+ this.consumer = consumer;
+ }
+
+ @Override
+ public String getRestrictMethod() {
+ return verb;
+ }
+
+ @Override
+ public String getConsumerPath() {
+ return path;
+ }
+
+ @Override
+ public Operation getConsumer() {
+ return consumer;
+ }
+
+ @Override
+ public boolean isMatchOnUriPrefix() {
+ return false;
+ }
+}
diff --git a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiProcessor.java b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiProcessor.java
index bc9767eaadd..283327a3c6a 100644
--- a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiProcessor.java
+++ b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiProcessor.java
@@ -16,16 +16,19 @@
*/
package org.apache.camel.component.rest.openapi;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.RejectedExecutionException;
+
import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.Operation;
+import io.swagger.v3.oas.models.PathItem;
import org.apache.camel.AsyncCallback;
-import org.apache.camel.AsyncProducer;
import org.apache.camel.CamelContext;
import org.apache.camel.CamelContextAware;
-import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
-import org.apache.camel.spi.ProducerCache;
-import org.apache.camel.support.cache.DefaultProducerCache;
+import org.apache.camel.support.RestConsumerContextPathMatcher;
import org.apache.camel.support.processor.DelegateAsyncProcessor;
import org.apache.camel.support.service.ServiceHelper;
@@ -34,12 +37,14 @@ public class RestOpenApiProcessor extends DelegateAsyncProcessor implements Came
private CamelContext camelContext;
private final OpenAPI openAPI;
private final String basePath;
- private ProducerCache producerCache;
+ private final List<RestConsumerContextPathMatcher.ConsumerPath<Operation>> paths = new ArrayList<>();
+ private RestOpenapiProcessorStrategy restOpenapiProcessorStrategy;
public RestOpenApiProcessor(OpenAPI openAPI, String basePath, Processor processor) {
super(processor);
this.basePath = basePath;
this.openAPI = openAPI;
+ this.restOpenapiProcessorStrategy = new DefaultRestOpenapiProcessorStrategy();
}
@Override
@@ -52,46 +57,88 @@ public class RestOpenApiProcessor extends DelegateAsyncProcessor implements Came
this.camelContext = camelContext;
}
+ public RestOpenapiProcessorStrategy getRestOpenapiProcessorStrategy() {
+ return restOpenapiProcessorStrategy;
+ }
+
+ public void setRestOpenapiProcessorStrategy(RestOpenapiProcessorStrategy restOpenapiProcessorStrategy) {
+ this.restOpenapiProcessorStrategy = restOpenapiProcessorStrategy;
+ }
+
@Override
public boolean process(Exchange exchange, AsyncCallback callback) {
- String path = exchange.getMessage().getHeader(Exchange.HTTP_PATH, String.class);
- if (path != null && path.startsWith(basePath)) {
- path = path.substring(basePath.length() + 1);
- }
-
- // TODO: choose processor strategy (mapping by operation id -> direct)
// TODO: check if valid operation according to OpenApi
// TODO: validate GET/POST etc
+ // TODO: RequestValidator
// TODO: 404 and so on
// TODO: binding
- Endpoint e = camelContext.getEndpoint("direct:" + path);
- AsyncProducer p = producerCache.acquireProducer(e);
- return p.process(exchange, new AsyncCallback() {
- @Override
- public void done(boolean doneSync) {
- producerCache.releaseProducer(e, p);
- callback.done(doneSync);
+ String path = exchange.getMessage().getHeader(Exchange.HTTP_PATH, String.class);
+ if (path != null && path.startsWith(basePath)) {
+ path = path.substring(basePath.length());
+ }
+ String verb = exchange.getMessage().getHeader(Exchange.HTTP_METHOD, String.class);
+
+ RestConsumerContextPathMatcher.ConsumerPath<Operation> m
+ = RestConsumerContextPathMatcher.matchBestPath(verb, path, paths);
+ if (m != null) {
+ Operation o = m.getConsumer();
+ return restOpenapiProcessorStrategy.process(o, path, exchange, callback);
+ }
+
+ // no operation found so it's a 404
+ exchange.setException(new RejectedExecutionException());
+ exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 404);
+ callback.done(true);
+ return true;
+ }
+
+ protected Operation asOperation(PathItem item, String verb) {
+ return switch (verb) {
+ case "GET" -> item.getGet();
+ case "DELETE" -> item.getDelete();
+ case "HEAD" -> item.getHead();
+ case "PATCH" -> item.getPatch();
+ case "OPTIONS" -> item.getOptions();
+ case "PUT" -> item.getPut();
+ case "POST" -> item.getPost();
+ default -> null;
+ };
+ }
+
+ @Override
+ protected void doBuild() throws Exception {
+ super.doBuild();
+
+ // register all openapi paths
+ for (var e : openAPI.getPaths().entrySet()) {
+ String path = e.getKey(); // path
+ for (var o : e.getValue().readOperationsMap().entrySet()) {
+ String v = o.getKey().name(); // verb
+ paths.add(new RestOpenApiConsumerPath(v, path, o.getValue()));
}
- });
+ }
+
+ CamelContextAware.trySetCamelContext(restOpenapiProcessorStrategy, getCamelContext());
+ ServiceHelper.buildService(restOpenapiProcessorStrategy);
}
@Override
protected void doInit() throws Exception {
super.doInit();
- producerCache = new DefaultProducerCache(this, getCamelContext(), 1000);
- ServiceHelper.initService(producerCache);
+ ServiceHelper.initService(restOpenapiProcessorStrategy);
}
@Override
protected void doStart() throws Exception {
super.doStart();
- ServiceHelper.startService(producerCache);
+ ServiceHelper.startService(restOpenapiProcessorStrategy);
}
@Override
protected void doStop() throws Exception {
super.doStop();
- ServiceHelper.stopService(producerCache);
+ paths.clear();
+ ServiceHelper.stopService(restOpenapiProcessorStrategy);
}
}
diff --git a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenapiProcessorStrategy.java b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenapiProcessorStrategy.java
new file mode 100644
index 00000000000..baaeac1d81e
--- /dev/null
+++ b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenapiProcessorStrategy.java
@@ -0,0 +1,27 @@
+/*
+ * 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.rest.openapi;
+
+import io.swagger.v3.oas.models.Operation;
+import org.apache.camel.AsyncCallback;
+import org.apache.camel.Exchange;
+
+public interface RestOpenapiProcessorStrategy {
+
+ boolean process(Operation operation, String path, Exchange exchange, AsyncCallback callback);
+
+}
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/RestConsumerContextPathMatcher.java b/core/camel-support/src/main/java/org/apache/camel/support/RestConsumerContextPathMatcher.java
index a5d32037997..652e13beb8b 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/RestConsumerContextPathMatcher.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/RestConsumerContextPathMatcher.java
@@ -122,8 +122,7 @@ public final class RestConsumerContextPathMatcher {
* @param consumerPaths the list of consumer context path details
* @return the best matched consumer, or <tt>null</tt> if none could be determined.
*/
- public static <
- T> ConsumerPath<T> matchBestPath(String requestMethod, String requestPath, List<ConsumerPath<T>> consumerPaths) {
+ public static <T> ConsumerPath<T> matchBestPath(String requestMethod, String requestPath, List<ConsumerPath<T>> consumerPaths) {
ConsumerPath<T> answer = null;
List<ConsumerPath<T>> candidates = new ArrayList<>();