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 <claus.ib...@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<>();