This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch binding4 in repository https://gitbox.apache.org/repos/asf/camel.git
commit 9882096ad67f359c7ff3f7b2240ae1a4eae9775c Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Thu Apr 4 14:41:01 2024 +0200 CAMEL-20557: Rest DSL to use openapi spec directly --- .../platform/http/PlatformHttpConsumer.java | 24 ++++++++---- .../platform/http/PlatformHttpEndpoint.java | 2 +- .../DefaultRestOpenapiProcessorStrategy.java | 14 ++++--- .../rest/openapi/RestOpenApiEndpoint.java | 10 +++-- .../rest/openapi/RestOpenApiProcessor.java | 44 +++++++++++++--------- .../rest/openapi/RestOpenapiProcessorStrategy.java | 8 ++-- .../camel/support/processor/RestBindingAdvice.java | 5 +-- 7 files changed, 67 insertions(+), 40 deletions(-) diff --git a/components/camel-platform-http/src/main/java/org/apache/camel/component/platform/http/PlatformHttpConsumer.java b/components/camel-platform-http/src/main/java/org/apache/camel/component/platform/http/PlatformHttpConsumer.java index c2020081876..bfc357db814 100644 --- a/components/camel-platform-http/src/main/java/org/apache/camel/component/platform/http/PlatformHttpConsumer.java +++ b/components/camel-platform-http/src/main/java/org/apache/camel/component/platform/http/PlatformHttpConsumer.java @@ -26,7 +26,7 @@ import org.apache.camel.support.service.ServiceHelper; public class PlatformHttpConsumer extends DefaultConsumer implements Suspendable, SuspendableService { - private Consumer delegatedConsumer; + private Consumer platformHttpConsumer; private boolean register = true; public PlatformHttpConsumer(Endpoint endpoint, Processor processor) { @@ -50,19 +50,29 @@ public class PlatformHttpConsumer extends DefaultConsumer implements Suspendable this.register = register; } + public Consumer getDelegtePlatformHttpConsumer() { + return platformHttpConsumer; + } + @Override protected void doInit() throws Exception { + platformHttpConsumer = getEndpoint().createPlatformHttpConsumer(getProcessor()); + configurePlatformHttpConsumer(platformHttpConsumer); super.doInit(); - delegatedConsumer = getEndpoint().createDelegateConsumer(getProcessor()); + ServiceHelper.initService(platformHttpConsumer); + } + + protected void configurePlatformHttpConsumer(Consumer platformHttpConsumer) { + // noop } @Override protected void doStart() throws Exception { super.doStart(); - ServiceHelper.startService(delegatedConsumer); + ServiceHelper.startService(platformHttpConsumer); if (register) { getComponent().addHttpEndpoint(getEndpoint().getPath(), getEndpoint().getHttpMethodRestrict(), - getEndpoint().getConsumes(), getEndpoint().getProduces(), delegatedConsumer); + getEndpoint().getConsumes(), getEndpoint().getProduces(), platformHttpConsumer); } } @@ -72,18 +82,18 @@ public class PlatformHttpConsumer extends DefaultConsumer implements Suspendable if (register) { getComponent().removeHttpEndpoint(getEndpoint().getPath()); } - ServiceHelper.stopAndShutdownServices(delegatedConsumer); + ServiceHelper.stopAndShutdownServices(platformHttpConsumer); } @Override protected void doResume() throws Exception { - ServiceHelper.resumeService(delegatedConsumer); + ServiceHelper.resumeService(platformHttpConsumer); super.doResume(); } @Override protected void doSuspend() throws Exception { - ServiceHelper.suspendService(delegatedConsumer); + ServiceHelper.suspendService(platformHttpConsumer); super.doSuspend(); } diff --git a/components/camel-platform-http/src/main/java/org/apache/camel/component/platform/http/PlatformHttpEndpoint.java b/components/camel-platform-http/src/main/java/org/apache/camel/component/platform/http/PlatformHttpEndpoint.java index f2b43c9474a..dc940c1f73e 100644 --- a/components/camel-platform-http/src/main/java/org/apache/camel/component/platform/http/PlatformHttpEndpoint.java +++ b/components/camel-platform-http/src/main/java/org/apache/camel/component/platform/http/PlatformHttpEndpoint.java @@ -107,7 +107,7 @@ public class PlatformHttpEndpoint extends DefaultEndpoint implements AsyncEndpoi return consumer; } - protected Consumer createDelegateConsumer(Processor processor) throws Exception { + protected Consumer createPlatformHttpConsumer(Processor processor) throws Exception { Consumer consumer = getOrCreateEngine().createConsumer(this, processor); configureConsumer(consumer); return consumer; 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 2648b8084f2..bd027e97b35 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 @@ -36,6 +36,7 @@ import org.apache.camel.NonManagedService; import org.apache.camel.Route; import org.apache.camel.RuntimeCamelException; import org.apache.camel.component.platform.http.PlatformHttpComponent; +import org.apache.camel.component.platform.http.PlatformHttpConsumer; import org.apache.camel.spi.PackageScanResourceResolver; import org.apache.camel.spi.ProducerCache; import org.apache.camel.spi.Resource; @@ -66,7 +67,7 @@ public class DefaultRestOpenapiProcessorStrategy extends ServiceSupport private final List<String> uris = new ArrayList<>(); @Override - public void validateOpenApi(OpenAPI openAPI) throws Exception { + public void validateOpenApi(OpenAPI openAPI, PlatformHttpConsumer platformHttpConsumer) throws Exception { List<String> ids = new ArrayList<>(); for (var e : openAPI.getPaths().entrySet()) { for (var o : e.getValue().readOperations()) { @@ -133,7 +134,7 @@ public class DefaultRestOpenapiProcessorStrategy extends ServiceSupport } } } - phc.addHttpEndpoint(uri, verbs, consumes, produces, null); + phc.addHttpEndpoint(uri, verbs, consumes, produces, platformHttpConsumer.getDelegtePlatformHttpConsumer()); uris.add(uri); } } @@ -167,17 +168,20 @@ public class DefaultRestOpenapiProcessorStrategy extends ServiceSupport RestBindingAdvice binding, Exchange exchange, AsyncCallback callback) { - if ("mock".equalsIgnoreCase(missingOperation)) { + if ("mock".equalsIgnoreCase(missingOperation) || "ignore".equalsIgnoreCase(missingOperation)) { // check if there is a route Endpoint e = camelContext.hasEndpoint(component + ":" + operation.getOperationId()); if (e == null) { - // no route then try to load mock data as the answer - loadMockData(operation, path, exchange); + if ("mock".equalsIgnoreCase(missingOperation)) { + // no route then try to load mock data as the answer + loadMockData(operation, path, exchange); + } callback.done(true); return true; } } + // there is a route so process Map<String, Object> state; try { state = binding.before(exchange); 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 d955bee0773..fa49ae7dc4b 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 @@ -57,6 +57,7 @@ import org.apache.camel.ExchangePattern; import org.apache.camel.NoSuchBeanException; import org.apache.camel.Processor; import org.apache.camel.Producer; +import org.apache.camel.component.platform.http.PlatformHttpConsumer; import org.apache.camel.component.rest.openapi.validator.DefaultRequestValidator; import org.apache.camel.component.rest.openapi.validator.RequestValidator; import org.apache.camel.component.rest.openapi.validator.RestOpenApiOperation; @@ -208,12 +209,13 @@ public final class RestOpenApiEndpoint extends DefaultEndpoint { public Consumer createConsumer(final Processor processor) throws Exception { OpenAPI doc = loadSpecificationFrom(getCamelContext(), specificationUri); String path = determineBasePath(doc); - Processor target = new RestOpenApiProcessor(this, doc, path, apiContextPath, processor, restOpenapiProcessorStrategy); + RestOpenApiProcessor target + = new RestOpenApiProcessor(this, doc, path, apiContextPath, processor, restOpenapiProcessorStrategy); CamelContextAware.trySetCamelContext(target, getCamelContext()); return createConsumerFor(path, target); } - protected Consumer createConsumerFor(String basePath, Processor processor) throws Exception { + protected Consumer createConsumerFor(String basePath, RestOpenApiProcessor processor) throws Exception { RestOpenApiConsumerFactory factory = null; String cname = null; if (getConsumerComponentName() != null) { @@ -294,7 +296,9 @@ public final class RestOpenApiEndpoint extends DefaultEndpoint { if (factory != null) { RestConfiguration config = CamelContextHelper.getRestConfiguration(getCamelContext(), cname); Map<String, Object> copy = new HashMap<>(parameters); // defensive copy of the parameters - Consumer consumer = factory.createConsumer(getCamelContext(), processor, basePath, config, copy); + PlatformHttpConsumer consumer + = (PlatformHttpConsumer) factory.createConsumer(getCamelContext(), processor, basePath, config, copy); + processor.setPlatformHttpConsumer(consumer); configureConsumer(consumer); return consumer; } else { 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 1e18999ca41..888cea1fa25 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 @@ -27,11 +27,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; import java.util.stream.Collectors; -import jakarta.xml.bind.annotation.XmlRootElement; - -import javax.annotation.processing.Generated; - -import com.fasterxml.jackson.annotation.JsonTypeName; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.media.Content; @@ -43,6 +38,7 @@ import org.apache.camel.CamelContextAware; import org.apache.camel.Exchange; import org.apache.camel.Processor; import org.apache.camel.StartupStep; +import org.apache.camel.component.platform.http.PlatformHttpConsumer; import org.apache.camel.http.base.HttpHelper; import org.apache.camel.spi.DataType; import org.apache.camel.spi.DataTypeAware; @@ -59,6 +55,7 @@ import org.apache.camel.support.processor.RestBindingAdviceFactory; import org.apache.camel.support.processor.RestBindingConfiguration; import org.apache.camel.support.service.ServiceHelper; import org.apache.camel.util.ObjectHelper; +import org.apache.camel.util.URISupport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -80,6 +77,7 @@ public class RestOpenApiProcessor extends DelegateAsyncProcessor implements Came private final RestOpenapiProcessorStrategy restOpenapiProcessorStrategy; private final AtomicBoolean packageScanInit = new AtomicBoolean(); private final Set<Class<?>> scannedClasses = new HashSet<>(); + private PlatformHttpConsumer platformHttpConsumer; public RestOpenApiProcessor(RestOpenApiEndpoint endpoint, OpenAPI openAPI, String basePath, String apiContextPath, Processor processor, RestOpenapiProcessorStrategy restOpenapiProcessorStrategy) { @@ -102,16 +100,28 @@ public class RestOpenApiProcessor extends DelegateAsyncProcessor implements Came this.camelContext = camelContext; } + public PlatformHttpConsumer getPlatformHttpConsumer() { + return platformHttpConsumer; + } + + public void setPlatformHttpConsumer(PlatformHttpConsumer platformHttpConsumer) { + this.platformHttpConsumer = platformHttpConsumer; + } + @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()); + // use HTTP_URI as this works for all runtimes + String uri = exchange.getMessage().getHeader(Exchange.HTTP_URI, String.class); + if (uri != null) { + uri = URISupport.stripQuery(uri); + } + if (uri != null && uri.startsWith(basePath)) { + uri = uri.substring(basePath.length()); } String verb = exchange.getMessage().getHeader(Exchange.HTTP_METHOD, String.class); RestConsumerContextPathMatcher.ConsumerPath<Operation> m - = RestConsumerContextPathMatcher.matchBestPath(verb, path, paths); + = RestConsumerContextPathMatcher.matchBestPath(verb, uri, paths); if (m instanceof RestOpenApiConsumerPath rcp) { Operation o = rcp.getConsumer(); @@ -120,7 +130,7 @@ public class RestOpenApiProcessor extends DelegateAsyncProcessor implements Came RestConfiguration.RestBindingMode bindingMode = config.getBindingMode(); // map path-parameters from operation to camel headers - HttpHelper.evalPlaceholders(exchange.getMessage().getHeaders(), path, rcp.getConsumerPath()); + HttpHelper.evalPlaceholders(exchange.getMessage().getHeaders(), uri, rcp.getConsumerPath()); // we have found the op to call, but if validation is enabled then we need // to validate the incoming request first @@ -130,17 +140,17 @@ public class RestOpenApiProcessor extends DelegateAsyncProcessor implements Came } // process the incoming request - return restOpenapiProcessorStrategy.process(o, path, rcp.getBinding(), exchange, callback); + return restOpenapiProcessorStrategy.process(o, uri, rcp.getBinding(), exchange, callback); } // is it the api-context path - if (path != null && path.equals(apiContextPath)) { + if (uri != null && uri.equals(apiContextPath)) { return restOpenapiProcessorStrategy.processApiSpecification(endpoint.getSpecificationUri(), exchange, callback); } // okay we cannot process this requires so return either 404 or 405. // to know if its 405 then we need to check if any other HTTP method would have a consumer for the "same" request - final String contextPath = path; + final String contextPath = uri; List<String> allow = METHODS.stream() .filter(v -> RestConsumerContextPathMatcher.matchBestPath(v, contextPath, paths) != null).toList(); if (allow.isEmpty()) { @@ -499,10 +509,8 @@ public class RestOpenApiProcessor extends DelegateAsyncProcessor implements Came "OpenAPI binding classes package scan"); String[] pcks = base.split(","); PackageScanClassResolver resolver = PluginHelper.getPackageScanClassResolver(camelContext); - // discover POJO generated classes for JSon/XML - scannedClasses.addAll(resolver.findAnnotated(Generated.class, pcks)); - scannedClasses.addAll(resolver.findAnnotated(JsonTypeName.class, pcks)); - scannedClasses.addAll(resolver.findAnnotated(XmlRootElement.class, pcks)); + // just add all classes as the POJOs can be generated with all kind of tools and with and without annotations + scannedClasses.addAll(resolver.findImplementations(Object.class, pcks)); if (!scannedClasses.isEmpty()) { LOG.info("Binding package scan found {} classes in packages: {}", scannedClasses.size(), base); } @@ -532,7 +540,7 @@ public class RestOpenApiProcessor extends DelegateAsyncProcessor implements Came ServiceHelper.initService(restOpenapiProcessorStrategy); // validate openapi contract - restOpenapiProcessorStrategy.validateOpenApi(openAPI); + restOpenapiProcessorStrategy.validateOpenApi(openAPI, platformHttpConsumer); } @Override 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 index 90455836228..7c01fdfb107 100644 --- 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 @@ -20,6 +20,7 @@ import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; import org.apache.camel.AsyncCallback; import org.apache.camel.Exchange; +import org.apache.camel.component.platform.http.PlatformHttpConsumer; import org.apache.camel.support.processor.RestBindingAdvice; /** @@ -54,10 +55,11 @@ public interface RestOpenapiProcessorStrategy { /** * Validates the OpenAPI specification on startup * - * @param openAPI the openapi specification - * @throws Exception is thrown if validation error on startup + * @param openAPI the openapi specification + * @param platformHttpConsumer the platform http consumer + * @throws Exception is thrown if validation error on startup */ - default void validateOpenApi(OpenAPI openAPI) throws Exception { + default void validateOpenApi(OpenAPI openAPI, PlatformHttpConsumer platformHttpConsumer) throws Exception { // noop } diff --git a/core/camel-support/src/main/java/org/apache/camel/support/processor/RestBindingAdvice.java b/core/camel-support/src/main/java/org/apache/camel/support/processor/RestBindingAdvice.java index a548f486525..085a6c7d50f 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/processor/RestBindingAdvice.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/processor/RestBindingAdvice.java @@ -236,15 +236,14 @@ public class RestBindingAdvice extends ServiceSupport implements CamelInternalPr } String body = null; - if (exchange.getIn().getBody() != null) { - + if (ObjectHelper.isNotEmpty(exchange.getIn().getBody())) { // okay we have a binding mode, so need to check for empty body as that can cause the marshaller to fail // as they assume a non-empty body if (isXml || isJson) { // we have binding enabled, so we need to know if there body is empty or not // so force reading the body as a String which we can work with body = MessageHelper.extractBodyAsString(exchange.getIn()); - if (body != null) { + if (ObjectHelper.isNotEmpty(body)) { if (exchange.getIn() instanceof DataTypeAware) { ((DataTypeAware) exchange.getIn()).setBody(body, new DataType(isJson ? "json" : "xml")); } else {