You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by nf...@apache.org on 2022/03/02 13:25:43 UTC

[camel] branch main updated: CAMEL-17720: Add option to allow unmarshalling null body (#7073)

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

nfilotto 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 f2a3566  CAMEL-17720: Add option to allow unmarshalling null body (#7073)
f2a3566 is described below

commit f2a35666b9c396c788dd005f5ff585f328966d6d
Author: Nicolas Filotto <es...@users.noreply.github.com>
AuthorDate: Wed Mar 2 14:25:05 2022 +0100

    CAMEL-17720: Add option to allow unmarshalling null body (#7073)
    
    ## Motivation
    
    It is common to unmarshal data to json via Jackson, and the DataFormat we have will then fail if the message body is empty. We should add an option that can be turned on to allow null body and ideally propose the same option for all Dataformats.
    
    ## Modifications
    
    * Add the new option `allowNullBody` at unmarshal EIP level to be able to apply it for all existing DataFormat
    * Skip the call to the method `unmarshal` if `allowNullBody` is `true` and the body is `null`
    * Add `allowNullBody` to the Java DSL
---
 .../org/apache/camel/catalog/models/unmarshal.json |  1 +
 .../apache/camel/catalog/schemas/camel-spring.xsd  |  8 ++++
 .../jackson/JacksonJsonDataFormatTest.java         |  2 +
 .../component/jackson/JacksonMarshalTest.java      | 13 +++++++
 .../component/jackson/JacksonObjectMapperTest.java |  3 ++
 .../jackson/SpringJacksonAllowNullBodyTest.java    | 42 +++++++++++++++++++++
 .../jackson/SpringJacksonAllowNullBodyTest.xml     | 41 ++++++++++++++++++++
 .../docs/modules/eips/pages/unmarshal-eip.adoc     | 19 ++++++++++
 .../org/apache/camel/model/unmarshal.json          |  1 +
 .../org/apache/camel/builder/DataFormatClause.java | 24 +++++++++++-
 .../apache/camel/model/ProcessorDefinition.java    | 44 ++++++++++++++++++++--
 .../apache/camel/model/UnmarshalDefinition.java    | 29 ++++++++++++++
 .../org/apache/camel/reifier/UnmarshalReifier.java |  2 +-
 .../java/org/apache/camel/model/XmlParseTest.java  |  8 ++++
 .../camel/processor/UnmarshalProcessorTest.java    | 11 ++++++
 ...uteWithTidyMarkupDataFormatAndAllowNullBody.xml | 28 ++++++++++++++
 .../support/processor/UnmarshalProcessor.java      | 23 ++++++++---
 .../java/org/apache/camel/xml/in/ModelParser.java  |  9 ++++-
 .../dsl/yaml/deserializers/ModelDeserializers.java |  6 +++
 .../src/generated/resources/camel-yaml-dsl.json    |  3 ++
 .../src/generated/resources/camelYamlDsl.json      |  3 ++
 .../org/apache/camel/dsl/yaml/UnmarshalTest.groovy | 44 ++++++++++++++++++++++
 22 files changed, 352 insertions(+), 12 deletions(-)

diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/unmarshal.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/unmarshal.json
index f8a9f6e..639d3ca 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/unmarshal.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/unmarshal.json
@@ -13,6 +13,7 @@
   },
   "properties": {
     "dataFormatType": { "kind": "element", "displayName": "Data Format Type", "required": true, "type": "object", "javaType": "org.apache.camel.model.DataFormatDefinition", "oneOf": [ "any23", "asn1", "avro", "barcode", "base64", "beanio", "bindy", "cbor", "crypto", "csv", "custom", "fhirJson", "fhirXml", "flatpack", "grok", "gzipDeflater", "hl7", "ical", "jacksonXml", "jaxb", "json", "jsonApi", "lzf", "mimeMultipart", "pgp", "protobuf", "rss", "soap", "syslog", "tarFile", "thrift", "tid [...]
+    "allowNullBody": { "kind": "attribute", "displayName": "Allow Null Body", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Indicates whether null is allowed as value of a body to unmarshall." },
     "id": { "kind": "attribute", "displayName": "Id", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the id of this node" },
     "description": { "kind": "element", "displayName": "Description", "required": false, "type": "object", "javaType": "org.apache.camel.model.DescriptionDefinition", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the description of this node" }
   }
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
index 2e09f09..b600499 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
@@ -12306,6 +12306,14 @@ Sets a reference to use for lookup the policy in the registry.
             <xs:element ref="tns:zipFile"/>
           </xs:choice>
         </xs:sequence>
+        <xs:attribute name="allowNullBody" type="xs:string">
+          <xs:annotation>
+            <xs:documentation xml:lang="en"><![CDATA[
+Indicates whether null is allowed as value of a body to unmarshall. Default
+value: false
+            ]]></xs:documentation>
+          </xs:annotation>
+        </xs:attribute>
       </xs:extension>
     </xs:complexContent>
   </xs:complexType>
diff --git a/components/camel-jackson/src/test/java/org/apache/camel/component/jackson/JacksonJsonDataFormatTest.java b/components/camel-jackson/src/test/java/org/apache/camel/component/jackson/JacksonJsonDataFormatTest.java
index 09af454..0a579be 100644
--- a/components/camel-jackson/src/test/java/org/apache/camel/component/jackson/JacksonJsonDataFormatTest.java
+++ b/components/camel-jackson/src/test/java/org/apache/camel/component/jackson/JacksonJsonDataFormatTest.java
@@ -35,6 +35,8 @@ public class JacksonJsonDataFormatTest extends JacksonMarshalTest {
 
                 from("direct:inPojo").marshal().json(JsonLibrary.Jackson);
                 from("direct:backPojo").unmarshal().json(JsonLibrary.Jackson, TestPojo.class).to("mock:reversePojo");
+
+                from("direct:nullBody").unmarshal().allowNullBody().json().to("mock:nullBody");
             }
         };
     }
diff --git a/components/camel-jackson/src/test/java/org/apache/camel/component/jackson/JacksonMarshalTest.java b/components/camel-jackson/src/test/java/org/apache/camel/component/jackson/JacksonMarshalTest.java
index 55ba407..e56e6b7 100644
--- a/components/camel-jackson/src/test/java/org/apache/camel/component/jackson/JacksonMarshalTest.java
+++ b/components/camel-jackson/src/test/java/org/apache/camel/component/jackson/JacksonMarshalTest.java
@@ -86,6 +86,16 @@ public class JacksonMarshalTest extends CamelTestSupport {
         mock.assertIsSatisfied();
     }
 
+    @Test
+    public void testUnmarshalNullBody() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:nullBody");
+        mock.expectedMessageCount(1);
+        mock.message(0).body().isNull();
+
+        template.sendBody("direct:nullBody", null);
+        mock.assertIsSatisfied();
+    }
+
     @Override
     protected RouteBuilder createRouteBuilder() throws Exception {
         return new RouteBuilder() {
@@ -107,6 +117,9 @@ public class JacksonMarshalTest extends CamelTestSupport {
 
                 from("direct:inPojo").marshal(formatPojo);
                 from("direct:backPojo").unmarshal(formatPojo).to("mock:reversePojo");
+
+                JacksonDataFormat allowNullBodyDataFormat = new JacksonDataFormat();
+                from("direct:nullBody").unmarshal(allowNullBodyDataFormat, true).to("mock:nullBody");
             }
         };
     }
diff --git a/components/camel-jackson/src/test/java/org/apache/camel/component/jackson/JacksonObjectMapperTest.java b/components/camel-jackson/src/test/java/org/apache/camel/component/jackson/JacksonObjectMapperTest.java
index 6b6f55d..e68b78f 100644
--- a/components/camel-jackson/src/test/java/org/apache/camel/component/jackson/JacksonObjectMapperTest.java
+++ b/components/camel-jackson/src/test/java/org/apache/camel/component/jackson/JacksonObjectMapperTest.java
@@ -44,6 +44,9 @@ public class JacksonObjectMapperTest extends JacksonMarshalTest {
 
                 from("direct:inPojo").marshal(formatPojo);
                 from("direct:backPojo").unmarshal(formatPojo).to("mock:reversePojo");
+
+                JacksonDataFormat allowNullBodyDataFormat = new JacksonDataFormat();
+                from("direct:nullBody").unmarshal(allowNullBodyDataFormat, true).to("mock:nullBody");
             }
         };
     }
diff --git a/components/camel-jackson/src/test/java/org/apache/camel/component/jackson/SpringJacksonAllowNullBodyTest.java b/components/camel-jackson/src/test/java/org/apache/camel/component/jackson/SpringJacksonAllowNullBodyTest.java
new file mode 100644
index 0000000..106bbbb
--- /dev/null
+++ b/components/camel-jackson/src/test/java/org/apache/camel/component/jackson/SpringJacksonAllowNullBodyTest.java
@@ -0,0 +1,42 @@
+/*
+ * 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.jackson;
+
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.spring.junit5.CamelSpringTestSupport;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.support.AbstractXmlApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+
+class SpringJacksonAllowNullBodyTest extends CamelSpringTestSupport {
+
+    @Test
+    void testUnmarshalNullBody() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:result");
+        mock.expectedMessageCount(1);
+        mock.message(0).body().isNull();
+
+        template.sendBody("direct:in", null);
+        mock.assertIsSatisfied();
+    }
+
+    @Override
+    protected AbstractXmlApplicationContext createApplicationContext() {
+        return new ClassPathXmlApplicationContext("org/apache/camel/component/jackson/SpringJacksonAllowNullBodyTest.xml");
+    }
+
+}
diff --git a/components/camel-jackson/src/test/resources/org/apache/camel/component/jackson/SpringJacksonAllowNullBodyTest.xml b/components/camel-jackson/src/test/resources/org/apache/camel/component/jackson/SpringJacksonAllowNullBodyTest.xml
new file mode 100644
index 0000000..fc08f27
--- /dev/null
+++ b/components/camel-jackson/src/test/resources/org/apache/camel/component/jackson/SpringJacksonAllowNullBodyTest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="
+       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+       http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
+    ">
+
+    <camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
+
+        <dataFormats>
+            <json id="jack" library="Jackson"/>
+        </dataFormats>
+
+        <route>
+            <from uri="direct:in"/>
+            <unmarshal allowNullBody="true"><custom ref="jack"/></unmarshal>
+            <to uri="mock:result"/>
+        </route>
+
+    </camelContext>
+
+</beans>
\ No newline at end of file
diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/unmarshal-eip.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/unmarshal-eip.adoc
index 26ab78f..05f069b 100644
--- a/core/camel-core-engine/src/main/docs/modules/eips/pages/unmarshal-eip.adoc
+++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/unmarshal-eip.adoc
@@ -47,3 +47,22 @@ And in XML:
 </route>
 ----
 
+== Allow Null Body
+
+Sometimes, there are situations where `null` can be a normal value for the body of a message but `null` by default is not an accepted value to unmarshal. To workaround that, it is possible to allow `null` as value to a body to unmarshall using the option `allowNullBody` as shown in the next code snippets:
+
+[source,java]
+----
+// Beginning of the route
+  .unmarshal().allowNullBody().jaxb()
+// End of the route
+----
+
+And in XML:
+
+[source,xml]
+----
+<!-- Beginning of the route -->
+  <unmarshal allowNullBody="true"><jaxb/></unmarshal>
+<!-- End of the route -->
+----
diff --git a/core/camel-core-model/src/generated/resources/org/apache/camel/model/unmarshal.json b/core/camel-core-model/src/generated/resources/org/apache/camel/model/unmarshal.json
index f8a9f6e..639d3ca 100644
--- a/core/camel-core-model/src/generated/resources/org/apache/camel/model/unmarshal.json
+++ b/core/camel-core-model/src/generated/resources/org/apache/camel/model/unmarshal.json
@@ -13,6 +13,7 @@
   },
   "properties": {
     "dataFormatType": { "kind": "element", "displayName": "Data Format Type", "required": true, "type": "object", "javaType": "org.apache.camel.model.DataFormatDefinition", "oneOf": [ "any23", "asn1", "avro", "barcode", "base64", "beanio", "bindy", "cbor", "crypto", "csv", "custom", "fhirJson", "fhirXml", "flatpack", "grok", "gzipDeflater", "hl7", "ical", "jacksonXml", "jaxb", "json", "jsonApi", "lzf", "mimeMultipart", "pgp", "protobuf", "rss", "soap", "syslog", "tarFile", "thrift", "tid [...]
+    "allowNullBody": { "kind": "attribute", "displayName": "Allow Null Body", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Indicates whether null is allowed as value of a body to unmarshall." },
     "id": { "kind": "attribute", "displayName": "Id", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the id of this node" },
     "description": { "kind": "element", "displayName": "Description", "required": false, "type": "object", "javaType": "org.apache.camel.model.DescriptionDefinition", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the description of this node" }
   }
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/builder/DataFormatClause.java b/core/camel-core-model/src/main/java/org/apache/camel/builder/DataFormatClause.java
index 9707aa0b..1692971 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/builder/DataFormatClause.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/builder/DataFormatClause.java
@@ -73,6 +73,7 @@ import org.apache.camel.support.jsse.KeyStoreParameters;
 public class DataFormatClause<T extends ProcessorDefinition<?>> {
     private final T processorType;
     private final Operation operation;
+    private boolean allowNullBody;
 
     /**
      * {@link org.apache.camel.spi.DataFormat} operations.
@@ -1378,11 +1379,32 @@ public class DataFormatClause<T extends ProcessorDefinition<?>> {
         return dataFormat(fhirXmlDataFormat);
     }
 
+    /**
+     * Allows {@code null} as value of a body to unmarshall.
+     *
+     * @return the builder
+     */
+    public DataFormatClause<T> allowNullBody() {
+        return allowNullBody(true);
+    }
+
+    /**
+     * Indicates whether {@code null} is allowed as value of a body to unmarshall.
+     *
+     * @param  allowNullBody {@code true} if {@code null} is allowed as value of a body to unmarshall, {@code false}
+     *                       otherwise
+     * @return               the builder
+     */
+    public DataFormatClause<T> allowNullBody(boolean allowNullBody) {
+        this.allowNullBody = allowNullBody;
+        return this;
+    }
+
     @SuppressWarnings("unchecked")
     private T dataFormat(DataFormatDefinition dataFormatType) {
         switch (operation) {
             case Unmarshal:
-                return (T) processorType.unmarshal(dataFormatType);
+                return (T) processorType.unmarshal(dataFormatType, allowNullBody);
             case Marshal:
                 return (T) processorType.marshal(dataFormatType);
             default:
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinition.java
index f6f2b19..611eed2 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinition.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinition.java
@@ -3671,7 +3671,20 @@ public abstract class ProcessorDefinition<Type extends ProcessorDefinition<Type>
      * @return                the builder
      */
     public Type unmarshal(DataFormatDefinition dataFormatType) {
-        addOutput(new UnmarshalDefinition(dataFormatType));
+        return unmarshal(dataFormatType, false);
+    }
+
+    /**
+     * <a href="http://camel.apache.org/data-format.html">DataFormat:</a> Unmarshals the in body using the specified
+     * {@link DataFormat} and sets the output on the out message body.
+     *
+     * @param  dataFormatType the dataformat
+     * @param  allowNullBody  {@code true} if {@code null} is allowed as value of a body to unmarshall, {@code false}
+     *                        otherwise
+     * @return                the builder
+     */
+    public Type unmarshal(DataFormatDefinition dataFormatType, boolean allowNullBody) {
+        addOutput(new UnmarshalDefinition(dataFormatType).allowNullBody(allowNullBody));
         return asType();
     }
 
@@ -3683,7 +3696,20 @@ public abstract class ProcessorDefinition<Type extends ProcessorDefinition<Type>
      * @return            the builder
      */
     public Type unmarshal(DataFormat dataFormat) {
-        return unmarshal(new DataFormatDefinition(dataFormat));
+        return unmarshal(dataFormat, false);
+    }
+
+    /**
+     * <a href="http://camel.apache.org/data-format.html">DataFormat:</a> Unmarshals the in body using the specified
+     * {@link DataFormat} and sets the output on the out message body.
+     *
+     * @param  dataFormat    the dataformat
+     * @param  allowNullBody {@code true} if {@code null} is allowed as value of a body to unmarshall, {@code false}
+     *                       otherwise
+     * @return               the builder
+     */
+    public Type unmarshal(DataFormat dataFormat, boolean allowNullBody) {
+        return unmarshal(new DataFormatDefinition(dataFormat), allowNullBody);
     }
 
     /**
@@ -3695,7 +3721,19 @@ public abstract class ProcessorDefinition<Type extends ProcessorDefinition<Type>
      * @return             the builder
      */
     public Type unmarshal(String dataTypeRef) {
-        return unmarshal(new CustomDataFormat(dataTypeRef));
+        return unmarshal(dataTypeRef, false);
+    }
+
+    /**
+     * <a href="http://camel.apache.org/data-format.html">DataFormat:</a> Unmarshals the in body using the specified
+     * {@link DataFormat} reference in the {@link org.apache.camel.spi.Registry} and sets the output on the out message
+     * body.
+     *
+     * @param  dataTypeRef reference to a {@link DataFormat} to lookup in the registry
+     * @return             the builder
+     */
+    public Type unmarshal(String dataTypeRef, boolean allowNullBody) {
+        return unmarshal(new CustomDataFormat(dataTypeRef), allowNullBody);
     }
 
     /**
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/UnmarshalDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/UnmarshalDefinition.java
index 738b26a..002e770 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/UnmarshalDefinition.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/UnmarshalDefinition.java
@@ -18,6 +18,7 @@ package org.apache.camel.model;
 
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlElements;
 import javax.xml.bind.annotation.XmlRootElement;
@@ -114,6 +115,9 @@ public class UnmarshalDefinition extends NoOutputDefinition<UnmarshalDefinition>
             @XmlElement(name = "zipDeflater", type = ZipDeflaterDataFormat.class),
             @XmlElement(name = "zipFile", type = ZipFileDataFormat.class) })
     private DataFormatDefinition dataFormatType;
+    @XmlAttribute
+    @Metadata(javaType = "java.lang.Boolean", defaultValue = "false")
+    private String allowNullBody;
 
     public UnmarshalDefinition() {
     }
@@ -158,4 +162,29 @@ public class UnmarshalDefinition extends NoOutputDefinition<UnmarshalDefinition>
         this.dataFormatType = dataFormatType;
     }
 
+    public String getAllowNullBody() {
+        return allowNullBody;
+    }
+
+    /**
+     * Indicates whether {@code null} is allowed as value of a body to unmarshall.
+     */
+    public void setAllowNullBody(String allowNullBody) {
+        this.allowNullBody = allowNullBody;
+    }
+
+    // Fluent API
+    // -------------------------------------------------------------------------
+
+    /**
+     * Indicates whether {@code null} is allowed as value of a body to unmarshall.
+     *
+     * @param  allowNullBody {@code true} if {@code null} is allowed as value of a body to unmarshall, {@code false}
+     *                       otherwise
+     * @return               the builder
+     */
+    public UnmarshalDefinition allowNullBody(boolean allowNullBody) {
+        setAllowNullBody(Boolean.toString(allowNullBody));
+        return this;
+    }
 }
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/UnmarshalReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/UnmarshalReifier.java
index cb41528..025fed2 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/UnmarshalReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/UnmarshalReifier.java
@@ -33,6 +33,6 @@ public class UnmarshalReifier extends ProcessorReifier<UnmarshalDefinition> {
     @Override
     public Processor createProcessor() {
         DataFormat dataFormat = DataFormatReifier.getDataFormat(camelContext, definition.getDataFormatType());
-        return new UnmarshalProcessor(dataFormat);
+        return new UnmarshalProcessor(dataFormat, Boolean.TRUE == parseBoolean(definition.getAllowNullBody()));
     }
 }
diff --git a/core/camel-core/src/test/java/org/apache/camel/model/XmlParseTest.java b/core/camel-core/src/test/java/org/apache/camel/model/XmlParseTest.java
index 3127f09..a94b2ec 100644
--- a/core/camel-core/src/test/java/org/apache/camel/model/XmlParseTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/model/XmlParseTest.java
@@ -289,6 +289,14 @@ public class XmlParseTest extends XmlTestSupport {
     }
 
     @Test
+    public void testParseTidyMarkupDataFormatAndAllowNullBody() throws Exception {
+        RouteDefinition route = assertOneRoute("routeWithTidyMarkupDataFormatAndAllowNullBody.xml");
+        assertFrom(route, "seda:a");
+        UnmarshalDefinition unmarshal = assertNthProcessorInstanceOf(UnmarshalDefinition.class, route, 0);
+        assertEquals("true", unmarshal.getAllowNullBody(), "The unmarshaller should allow null body");
+    }
+
+    @Test
     public void testParseRSSDataFormat() throws Exception {
         RouteDefinition route = assertOneRoute("routeWithRSSDataFormat.xml");
         assertFrom(route, "seda:a");
diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/UnmarshalProcessorTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/UnmarshalProcessorTest.java
index 9c0010a..6aaa18f 100644
--- a/core/camel-core/src/test/java/org/apache/camel/processor/UnmarshalProcessorTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/processor/UnmarshalProcessorTest.java
@@ -86,6 +86,17 @@ public class UnmarshalProcessorTest extends TestSupport {
                 "UnmarshalProcessor did not make use of the returned object being returned while unmarshalling");
     }
 
+    @Test
+    public void testAllowNullBody() throws Exception {
+        Exchange exchange = createExchangeWithBody(new DefaultCamelContext(), null);
+        Processor processor = new UnmarshalProcessor(new MyDataFormat(exchange), true);
+
+        processor.process(exchange);
+
+        assertNull(exchange.getMessage().getBody(), "UnmarshalProcessor should allow null body");
+        assertNull(exchange.getException(), "UnmarshalProcessor should allow null body");
+    }
+
     private static class MyDataFormat extends ServiceSupport implements DataFormat {
 
         private final Object object;
diff --git a/core/camel-core/src/test/resources/org/apache/camel/model/routeWithTidyMarkupDataFormatAndAllowNullBody.xml b/core/camel-core/src/test/resources/org/apache/camel/model/routeWithTidyMarkupDataFormatAndAllowNullBody.xml
new file mode 100644
index 0000000..ebc42aa
--- /dev/null
+++ b/core/camel-core/src/test/resources/org/apache/camel/model/routeWithTidyMarkupDataFormatAndAllowNullBody.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<routes id="camel" xmlns="http://camel.apache.org/schema/spring">
+    <route>
+        <from uri="seda:a"/>
+        <unmarshal allowNullBody="true">
+            <tidyMarkup/>
+        </unmarshal>
+        <to uri="seda:b"/>
+    </route>
+</routes>
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/processor/UnmarshalProcessor.java b/core/camel-support/src/main/java/org/apache/camel/support/processor/UnmarshalProcessor.java
index d05fccb..f171d14 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/processor/UnmarshalProcessor.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/processor/UnmarshalProcessor.java
@@ -43,9 +43,15 @@ public class UnmarshalProcessor extends AsyncProcessorSupport implements Traceab
     private String routeId;
     private CamelContext camelContext;
     private final DataFormat dataFormat;
+    private final boolean allowNullBody;
 
     public UnmarshalProcessor(DataFormat dataFormat) {
+        this(dataFormat, false);
+    }
+
+    public UnmarshalProcessor(DataFormat dataFormat, boolean allowNullBody) {
         this.dataFormat = dataFormat;
+        this.allowNullBody = allowNullBody;
     }
 
     @Override
@@ -55,13 +61,20 @@ public class UnmarshalProcessor extends AsyncProcessorSupport implements Traceab
         InputStream stream = null;
         Object result = null;
         try {
-            stream = exchange.getIn().getMandatoryBody(InputStream.class);
+            final Message in = exchange.getIn();
+            final Message out;
+            if (allowNullBody && in.getBody() == null) {
+                // The body is null, and it is an allowed value so let's skip the unmarshalling
+                out = exchange.getOut();
+            } else {
+                stream = in.getMandatoryBody(InputStream.class);
 
-            // lets setup the out message before we invoke the dataFormat so that it can mutate it if necessary
-            Message out = exchange.getOut();
-            out.copyFrom(exchange.getIn());
+                // lets set up the out message before we invoke the dataFormat so that it can mutate it if necessary
+                out = exchange.getOut();
+                out.copyFrom(in);
 
-            result = dataFormat.unmarshal(exchange, stream);
+                result = dataFormat.unmarshal(exchange, stream);
+            }
             if (result instanceof Exchange) {
                 if (result != exchange) {
                     // it's not allowed to return another exchange other than the one provided to dataFormat
diff --git a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
index b8f457b..5b9047b 100644
--- a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
+++ b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
@@ -1535,8 +1535,13 @@ public class ModelParser extends BaseParser {
             processorDefinitionAttributeHandler(), outputDefinitionElementHandler(), noValueHandler());
     }
     protected UnmarshalDefinition doParseUnmarshalDefinition() throws IOException, XmlPullParserException {
-        return doParse(new UnmarshalDefinition(),
-            processorDefinitionAttributeHandler(), (def, key) -> {
+        return doParse(new UnmarshalDefinition(), (def, key, val) -> {
+            if ("allowNullBody".equals(key)) {
+                def.setAllowNullBody(val);
+                return true;
+            }
+            return processorDefinitionAttributeHandler().accept(def, key, val);
+        }, (def, key) -> {
             DataFormatDefinition v = doParseDataFormatDefinitionRef(key);
             if (v != null) { 
                 def.setDataFormatType(v);
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
index 47930c5..7d9cc24 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
@@ -16830,6 +16830,7 @@ public final class ModelDeserializers extends YamlDeserializerSupport {
             order = org.apache.camel.dsl.yaml.common.YamlDeserializerResolver.ORDER_LOWEST - 1,
             nodes = "unmarshal",
             properties = {
+                    @YamlProperty(name = "allow-null-body", type = "boolean"),
                     @YamlProperty(name = "any23", type = "object:org.apache.camel.model.dataformat.Any23DataFormat"),
                     @YamlProperty(name = "asn1", type = "object:org.apache.camel.model.dataformat.ASN1DataFormat"),
                     @YamlProperty(name = "avro", type = "object:org.apache.camel.model.dataformat.AvroDataFormat"),
@@ -16889,6 +16890,11 @@ public final class ModelDeserializers extends YamlDeserializerSupport {
         protected boolean setProperty(UnmarshalDefinition target, String propertyKey,
                 String propertyName, Node node) {
             switch(propertyKey) {
+                case "allow-null-body": {
+                    String val = asText(node);
+                    target.setAllowNullBody(val);
+                    break;
+                }
                 case "data-format-type": {
                     MappingNode val = asMappingNode(node);
                     setProperties(target, val);
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camel-yaml-dsl.json b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camel-yaml-dsl.json
index bfa5e8b..ac4fdc1 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camel-yaml-dsl.json
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camel-yaml-dsl.json
@@ -3319,6 +3319,9 @@
       "org.apache.camel.model.UnmarshalDefinition" : {
         "type" : "object",
         "properties" : {
+          "allow-null-body" : {
+            "type" : "boolean"
+          },
           "any23" : {
             "$ref" : "#/items/definitions/org.apache.camel.model.dataformat.Any23DataFormat"
           },
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camelYamlDsl.json b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camelYamlDsl.json
index 5a4dac2..cf4c823 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camelYamlDsl.json
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camelYamlDsl.json
@@ -3220,6 +3220,9 @@
       "org.apache.camel.model.UnmarshalDefinition" : {
         "type" : "object",
         "properties" : {
+          "allowNullBody" : {
+            "type" : "boolean"
+          },
           "any23" : {
             "$ref" : "#/items/definitions/org.apache.camel.model.dataformat.Any23DataFormat"
           },
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/UnmarshalTest.groovy b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/UnmarshalTest.groovy
index 51de342..7bc0380 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/UnmarshalTest.groovy
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/UnmarshalTest.groovy
@@ -75,4 +75,48 @@ class UnmarshalTest extends YamlTestSupport {
                 'gson', 'gson', 'jackson', 'jackson'
             ]
     }
+
+    def "unmarshal definition with allow null body (#resource.location, #expected)"(Resource resource, String expected) {
+        when:
+            context.routesLoader.loadRoutes(resource)
+        then:
+            with(context.routeDefinitions[0].outputs[0], UnmarshalDefinition) {
+                allowNullBody == expected
+            }
+        where:
+            resource << [
+                asResource('allow-null-body-set-to-true', '''
+                    - from:
+                        uri: "direct:start"
+                        steps:
+                          - unmarshal:
+                             allow-null-body: true
+                             json:
+                               library: Gson
+                          - to: "mock:result"
+                    '''),
+                asResource('allow-null-body-set-to-false', '''
+                    - from:
+                        uri: "direct:start"
+                        steps:
+                          - unmarshal:
+                             allow-null-body: false
+                             json:
+                               library: Gson
+                          - to: "mock:result"
+                    '''),
+                asResource('allow-null-body-not-set', '''
+               - from:
+                        uri: "direct:start"
+                        steps:
+                          - unmarshal:
+                             json:
+                               library: Gson
+                          - to: "mock:result"
+                    ''')
+            ]
+        expected << [
+                'true', 'false', null
+        ]
+    }
 }