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<>();