This is an automated email from the ASF dual-hosted git repository.

davsclaus 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 e6cff4529fe CAMEL-20623: camel-rest-openapi - Use client validator 
from camel-core instead of duplicate code. Add client request validator parse 
as json if content-type is json and body is required. (#14386)
e6cff4529fe is described below

commit e6cff4529fe997a29fcd05dc5832623a2adf056b
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Jun 5 12:12:40 2024 +0200

    CAMEL-20623: camel-rest-openapi - Use client validator from camel-core 
instead of duplicate code. Add client request validator parse as json if 
content-type is json and body is required. (#14386)
---
 .../rest/openapi/RestOpenApiProcessor.java         | 216 ---------------------
 .../camel/support/processor/RestBindingAdvice.java |   7 +
 .../processor/RestBindingAdviceFactory.java        |  31 ++-
 3 files changed, 21 insertions(+), 233 deletions(-)

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 d3c5c1de632..29b6bd222b4 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
@@ -20,11 +20,9 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Function;
 import java.util.stream.Collectors;
 
 import io.swagger.v3.oas.models.OpenAPI;
@@ -40,13 +38,9 @@ import org.apache.camel.Processor;
 import org.apache.camel.StartupStep;
 import org.apache.camel.component.platform.http.spi.PlatformHttpConsumerAware;
 import org.apache.camel.http.base.HttpHelper;
-import org.apache.camel.spi.DataType;
-import org.apache.camel.spi.DataTypeAware;
 import org.apache.camel.spi.PackageScanClassResolver;
 import org.apache.camel.spi.RestConfiguration;
 import org.apache.camel.spi.StartupStepRecorder;
-import org.apache.camel.support.ExchangeHelper;
-import org.apache.camel.support.MessageHelper;
 import org.apache.camel.support.PluginHelper;
 import org.apache.camel.support.RestConsumerContextPathMatcher;
 import org.apache.camel.support.processor.DelegateAsyncProcessor;
@@ -54,13 +48,10 @@ import org.apache.camel.support.processor.RestBindingAdvice;
 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;
 
-import static 
org.apache.camel.support.http.RestUtil.isValidOrAcceptedContentType;
-
 public class RestOpenApiProcessor extends DelegateAsyncProcessor implements 
CamelContextAware {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(RestOpenApiProcessor.class);
@@ -132,13 +123,6 @@ public class RestOpenApiProcessor extends 
DelegateAsyncProcessor implements Came
             // map path-parameters from operation to camel headers
             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
-            if (endpoint.isClientRequestValidation() && 
isInvalidClientRequest(exchange, callback, o, bindingMode)) {
-                // okay some validation error so return true
-                return true;
-            }
-
             // process the incoming request
             return restOpenapiProcessorStrategy.process(o, verb, uri, 
rcp.getBinding(), exchange, callback);
         }
@@ -165,206 +149,6 @@ public class RestOpenApiProcessor extends 
DelegateAsyncProcessor implements Came
         return true;
     }
 
-    /**
-     * Checks if the incoming request is invalid (has some error) according to 
the OpenAPI operation that is intended to
-     * be invoked.
-     *
-     * @return true if some validation error and should stop routing
-     */
-    protected boolean isInvalidClientRequest(
-            Exchange exchange, AsyncCallback callback, Operation o, 
RestConfiguration.RestBindingMode bindingMode) {
-
-        // this code is similar to logic in camel-core (RestBindingAdvice) for 
rest-dsl with code-first approach
-
-        boolean isXml = false;
-        boolean isJson = false;
-        String contentType = ExchangeHelper.getContentType(exchange);
-        if (contentType != null) {
-            isXml = contentType.toLowerCase(Locale.ENGLISH).contains("xml");
-            isJson = contentType.toLowerCase(Locale.ENGLISH).contains("json");
-        }
-        String accept = exchange.getMessage().getHeader("Accept", 
String.class);
-
-        String consumes = endpoint.getConsumes();
-        String produces = endpoint.getProduces();
-        // the operation may have specific information what it can consume
-        if (o.getRequestBody() != null) {
-            Content c = o.getRequestBody().getContent();
-            if (c != null) {
-                consumes = 
c.keySet().stream().sorted().collect(Collectors.joining(","));
-            }
-        }
-        // the operation may have specific information what it can produce
-        if (o.getResponses() != null) {
-            for (var a : o.getResponses().values()) {
-                Content c = a.getContent();
-                if (c != null) {
-                    produces = 
c.keySet().stream().sorted().collect(Collectors.joining(","));
-                }
-            }
-        }
-        // if content type could not tell us if it was json or xml, then 
fallback to if the binding was configured with
-        // that information in the consumes
-        if (!isXml && !isJson) {
-            isXml = consumes != null && 
consumes.toLowerCase(Locale.ENGLISH).contains("xml");
-            isJson = consumes != null && 
consumes.toLowerCase(Locale.ENGLISH).contains("json");
-        }
-
-        // set data type if in use
-        if (exchange.getContext().isUseDataType()) {
-            if (exchange.getIn() instanceof DataTypeAware && (isJson || 
isXml)) {
-                ((DataTypeAware) exchange.getIn()).setDataType(new 
DataType(isJson ? "json" : "xml"));
-            }
-        }
-
-        // check if the content-type is accepted according to consumes
-        if (!isValidOrAcceptedContentType(consumes, contentType)) {
-            LOG.trace("Consuming content type does not match contentType 
header {}. Stopping routing.", contentType);
-            // the content-type is not something we can process so its a 
HTTP_ERROR 415
-            exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 415);
-            // set empty response body as http error code indicate the problem
-            exchange.getMessage().setBody(null);
-            // stop routing and return
-            exchange.setRouteStop(true);
-            callback.done(true);
-            return true;
-        }
-        // check if what is produces is accepted by the client
-        if (!isValidOrAcceptedContentType(produces, accept)) {
-            LOG.trace("Produced content type does not match accept header {}. 
Stopping routing.", contentType);
-            // the response type is not accepted by the client so its a 
HTTP_ERROR 406
-            exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 406);
-            // set empty response body as http error code indicate the problem
-            exchange.getMessage().setBody(null);
-            // stop routing and return
-            exchange.setRouteStop(true);
-            callback.done(true);
-            return true;
-        }
-
-        // only allow xml/json if the binding mode allows that
-        isXml &= bindingMode.equals(RestConfiguration.RestBindingMode.auto)
-                || bindingMode.equals(RestConfiguration.RestBindingMode.xml)
-                || 
bindingMode.equals(RestConfiguration.RestBindingMode.json_xml);
-        isJson &= bindingMode.equals(RestConfiguration.RestBindingMode.auto)
-                || bindingMode.equals(RestConfiguration.RestBindingMode.json)
-                || 
bindingMode.equals(RestConfiguration.RestBindingMode.json_xml);
-
-        // if we do not yet know if its xml or json, then use the binding mode 
to know the mode
-        if (!isJson && !isXml) {
-            isXml = bindingMode.equals(RestConfiguration.RestBindingMode.auto)
-                    || 
bindingMode.equals(RestConfiguration.RestBindingMode.xml)
-                    || 
bindingMode.equals(RestConfiguration.RestBindingMode.json_xml);
-            isJson = bindingMode.equals(RestConfiguration.RestBindingMode.auto)
-                    || 
bindingMode.equals(RestConfiguration.RestBindingMode.json)
-                    || 
bindingMode.equals(RestConfiguration.RestBindingMode.json_xml);
-        }
-        boolean requiredBody = false;
-        if (o.getRequestBody() != null) {
-            requiredBody = Boolean.TRUE == o.getRequestBody().getRequired();
-        }
-        if (requiredBody) {
-            String body = null;
-            if (exchange.getIn().getBody() != null) {
-                // 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 (exchange.getIn() instanceof DataTypeAware) {
-                            ((DataTypeAware) exchange.getIn()).setBody(body, 
new DataType(isJson ? "json" : "xml"));
-                        } else {
-                            exchange.getIn().setBody(body);
-                        }
-                        if (isXml && isJson) {
-                            // we have still not determined between xml or 
json, so check the body if its xml based or not
-                            isXml = body.startsWith("<");
-                            isJson = !isXml;
-                        }
-                    }
-                }
-            }
-            // the body is required so we need to know if we have a body or not
-            // so force reading the body as a String which we can work with
-            if (body == null) {
-                body = MessageHelper.extractBodyAsString(exchange.getIn());
-                if (ObjectHelper.isNotEmpty(body)) {
-                    exchange.getIn().setBody(body);
-                }
-            }
-            if (ObjectHelper.isEmpty(body)) {
-                // this is a bad request, the client did not include a message 
body
-                exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 
400);
-                exchange.getMessage().setBody("The request body is missing.");
-                // stop routing and return
-                exchange.setRouteStop(true);
-                callback.done(true);
-                return true;
-            }
-        }
-        Map<String, Parameter> requiredQueryParameters = null;
-        if (o.getParameters() != null) {
-            requiredQueryParameters = o.getParameters().stream()
-                    .filter(p -> "query".equals(p.getIn()))
-                    .filter(p -> Boolean.TRUE == p.getRequired())
-                    .collect(Collectors.toMap(Parameter::getName, 
Function.identity()));
-        }
-        if (requiredQueryParameters != null
-                && 
!exchange.getIn().getHeaders().keySet().containsAll(requiredQueryParameters.keySet()))
 {
-            // this is a bad request, the client did not include some required 
query parameters
-            exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 400);
-            exchange.getMessage().setBody("Some of the required query 
parameters are missing.");
-            // stop routing and return
-            exchange.setRouteStop(true);
-            callback.done(true);
-            return true;
-        }
-        Map<String, Parameter> requiredHeaders = null;
-        if (o.getParameters() != null) {
-            requiredHeaders = o.getParameters().stream()
-                    .filter(p -> "header".equals(p.getIn()))
-                    .filter(p -> Boolean.TRUE == p.getRequired())
-                    .collect(Collectors.toMap(Parameter::getName, 
Function.identity()));
-        }
-        if (requiredHeaders != null && 
!exchange.getIn().getHeaders().keySet().containsAll(requiredHeaders.keySet())) {
-            // this is a bad request, the client did not include some required 
http headers
-            exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 400);
-            exchange.getMessage().setBody("Some of the required HTTP headers 
are missing.");
-            // stop routing and return
-            exchange.setRouteStop(true);
-            callback.done(true);
-            return true;
-        }
-        Map<String, List> allowedValues = null;
-        if (o.getParameters() != null) {
-            allowedValues = o.getParameters().stream()
-                    .filter(p -> "query".equals(p.getIn()))
-                    .filter(p -> p.getSchema() != null)
-                    .filter(p -> p.getSchema().getEnum() != null)
-                    .collect(Collectors.toMap(Parameter::getName, e -> 
e.getSchema().getEnum()));
-        }
-        if (allowedValues != null) {
-            for (var e : allowedValues.entrySet()) {
-                String k = e.getKey();
-                Object v = exchange.getMessage().getHeader(k);
-                if (v != null) {
-                    if (e.getValue().stream().noneMatch(v::equals)) {
-                        // this is a bad request, the client did not include 
some required query parameters
-                        
exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 400);
-                        exchange.getMessage().setBody("Some of the query 
parameters or HTTP headers has a not-allowed value.");
-                        // stop routing and return
-                        exchange.setRouteStop(true);
-                        callback.done(true);
-                        return true;
-                    }
-                }
-            }
-        }
-        return false;
-    }
-
     @Override
     protected void doBuild() throws Exception {
         super.doBuild();
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 4e1d9933f6e..24a9752433e 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
@@ -291,6 +291,13 @@ public class RestBindingAdvice extends ServiceSupport 
implements CamelInternalPr
                     exchange.setRouteStop(true);
                     return;
                 }
+                // special check if binding mode is off and then incoming body 
is json based
+                // then we still want to ensure the body can be parsed as json
+                if (bindingMode.equals("off") && !isXml) {
+                    if (isValidOrAcceptedContentType("application/json", 
contentType)) {
+                        isJson = true;
+                    }
+                }
             }
             if (requiredQueryParameters != null
                     && 
!exchange.getIn().getHeaders().keySet().containsAll(requiredQueryParameters)) {
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/processor/RestBindingAdviceFactory.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/processor/RestBindingAdviceFactory.java
index 9792fff6f30..4a909f83027 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/support/processor/RestBindingAdviceFactory.java
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/processor/RestBindingAdviceFactory.java
@@ -42,23 +42,12 @@ public class RestBindingAdviceFactory {
     public static RestBindingAdvice build(CamelContext camelContext, 
RestBindingConfiguration bc) throws Exception {
         String mode = bc.getBindingMode();
 
-        if ("off".equals(mode)) {
-            // binding mode is off, so create off mode binding processor
-            return new RestBindingAdvice(
-                    camelContext, null, null, null, null,
-                    bc.getConsumes(), bc.getProduces(), mode, 
bc.isSkipBindingOnErrorCode(), bc.isClientRequestValidation(),
-                    bc.isEnableCORS(),
-                    bc.isEnableNoContentResponse(), bc.getCorsHeaders(),
-                    bc.getQueryDefaultValues(), bc.getQueryAllowedValues(), 
bc.isRequiredBody(),
-                    bc.getRequiredQueryParameters(),
-                    bc.getRequiredHeaders());
-        }
-
         // setup json data format
         RestConfiguration config = camelContext.getRestConfiguration();
         DataFormat json = null;
         DataFormat outJson = null;
-        if (mode.contains("json") || "auto".equals(mode)) {
+        // include json if we have client request validator as we need a json 
parser
+        if (mode.contains("json") || "auto".equals(mode) || 
bc.isClientRequestValidation()) {
             String name = config.getJsonDataFormat();
             if (name != null) {
                 // must only be a name, not refer to an existing instance
@@ -70,10 +59,18 @@ public class RestBindingAdviceFactory {
             } else {
                 name = "jackson";
             }
-            // this will create a new instance as the name was not already
-            // pre-created
-            json = camelContext.createDataFormat(name);
-            outJson = camelContext.createDataFormat(name);
+            boolean optional = "off".equals(mode) && 
bc.isClientRequestValidation();
+            // this will create a new instance as the name was not already 
pre-created
+            if (optional) {
+                try {
+                    json = camelContext.createDataFormat(name);
+                } catch (IllegalArgumentException e) {
+                    // ignore
+                }
+            } else {
+                json = camelContext.createDataFormat(name);
+                outJson = camelContext.createDataFormat(name);
+            }
 
             if (json != null) {
                 setupJson(camelContext, config,

Reply via email to