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 598b7a140975 CAMEL-25284: marshal EIP - add allowNullBody, as
unmarshal has (#27581)
598b7a140975 is described below
commit 598b7a14097567276679399bc7819efcb5069acb
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Oct 8 20:02:08 2026 +0200
CAMEL-25284: marshal EIP - add allowNullBody, as unmarshal has (#27581)
* CAMEL-25284: marshal EIP - add allowNullBody, as unmarshal has
A null body is marshalled by the data format today, and what that gives
depends on the format: JSON gives the text null, CSV an empty text, so a
REST route that marshals a lookup that found nothing answers with null.
With allowNullBody=true the marshal EIP skips a null body and the body
stays null, as unmarshal does. The default is unchanged.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
* CAMEL-25284: marshal allowNullBody sets variableReceive to null, as
unmarshal does
Review: skipping a null body left a variableReceive with its previous value.
Also build the output stream only when there is something to marshal.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
* Regen
---------
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
Co-authored-by: Guillaume Nodet <[email protected]>
---
.../org/apache/camel/catalog/docs/marshal-eip.adoc | 51 ++++++
.../org/apache/camel/catalog/models/marshal.json | 3 +-
.../apache/camel/catalog/schemas/camel-spring.xsd | 11 ++
.../apache/camel/catalog/schemas/camel-xml-io.xsd | 11 ++
.../camel/catalog/schemas/camelYamlDsl-model.json | 11 ++
.../main/docs/modules/eips/pages/marshal-eip.adoc | 51 ++++++
.../META-INF/org/apache/camel/model/marshal.json | 3 +-
.../org/apache/camel/builder/DataFormatClause.java | 11 +-
.../org/apache/camel/model/MarshalDefinition.java | 28 ++++
.../org/apache/camel/reifier/MarshalReifier.java | 3 +-
.../camel/processor/MarshalAllowNullBodyTest.java | 104 ++++++++++++
.../apache/camel/java/out/JavaDslModelWriter.java | 1 +
.../camel/support/processor/MarshalProcessor.java | 22 ++-
.../java/org/apache/camel/xml/in/ModelParser.java | 1 +
.../java/org/apache/camel/xml/out/ModelWriter.java | 1 +
.../org/apache/camel/yaml/out/YamlModelWriter.java | 1 +
.../dsl/jbang/core/commands/ai/eip-samples.json | 4 +
.../dsl/yaml/deserializers/ModelDeserializers.java | 6 +
.../resources/schema/camelYamlDsl-canonical.json | 6 +
.../resources/schema/camelYamlDsl-model.json | 11 ++
.../generated/resources/schema/camelYamlDsl.json | 174 +++++++++++----------
21 files changed, 419 insertions(+), 95 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/marshal-eip.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/marshal-eip.adoc
index 90a501c29f47..398f85725e39 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/marshal-eip.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/marshal-eip.adoc
@@ -78,3 +78,54 @@ YAML::
uri: jms:queue:order
----
====
+
+== Allow Null Body
+
+By default, a `null` body is marshalled by the data format, and what that
gives depends on the data format:
+JSON gives the text `null`, CSV an empty text. When `null` is a normal value
of the body, for example a lookup
+that found nothing, use the option `allowNullBody` to skip the marshalling:
the body then stays `null`.
+
+[tabs]
+====
+
+Java::
++
+[source,java]
+----
+from("direct:order")
+ .to("bean:findOrder")
+ .marshal().allowNullBody().json()
+ .to("mock:result");
+----
+
+XML::
++
+[source,xml]
+----
+<route>
+ <from uri="direct:order"/>
+ <to uri="bean:findOrder"/>
+ <marshal allowNullBody="true">
+ <json/>
+ </marshal>
+ <to uri="mock:result"/>
+</route>
+----
+
+YAML::
++
+[source,yaml]
+----
+- route:
+ from:
+ uri: direct:order
+ steps:
+ - to:
+ uri: bean:findOrder
+ - marshal:
+ allowNullBody: true
+ json: {}
+ - to:
+ uri: mock:result
+----
+====
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/marshal.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/marshal.json
index 0c23a89c355d..df132a4e8531 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/marshal.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/marshal.json
@@ -19,6 +19,7 @@
"disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled",
"group": "advanced", "label": "advanced", "required": false, "type": "boolean",
"javaType": "java.lang.Boolean", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": false, "description": "Whether to disable this
EIP from the route during build time. Once an EIP has been disabled then it
cannot be enabled later at runtime." },
"dataFormatType": { "index": 4, "kind": "element", "displayName": "Data
Format Type", "group": "common", "required": true, "type": "object",
"javaType": "org.apache.camel.model.DataFormatDefinition", "oneOf": [ "asn1",
"avro", "barcode", "base64", "beanio", "bindy", "cbor", "crypto", "csv",
"custom", "dfdl", "fhirJson", "fhirXml", "flatpack", "fory", "grok",
"groovyJson", "groovyXml", "gzipDeflater", "hl7", "ical", "iso8583",
"jacksonXml", "jaxb", "json", "jsonApi", "lzf", "mimeMulti [...]
"variableSend": { "index": 5, "kind": "attribute", "displayName":
"Variable Send", "group": "common", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "To use a variable as the source for the
message body to send. This makes it handy to use variables for user data and to
easily control what data to use for sending and receiving." },
- "variableReceive": { "index": 6, "kind": "attribute", "displayName":
"Variable Receive", "group": "common", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "To use a variable to store the received
message body (only body, not headers). This makes it handy to use variables for
user data and to easily control what data to use for sending and receiving." }
+ "variableReceive": { "index": 6, "kind": "attribute", "displayName":
"Variable Receive", "group": "common", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "To use a variable to store the received
message body (only body, not headers). This makes it handy to use variables for
user data and to easily control what data to use for sending and receiving." },
+ "allowNullBody": { "index": 7, "kind": "attribute", "displayName": "Allow
Null Body", "group": "advanced", "label": "advanced", "required": false,
"type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Indicates whether a null body is skipped instead of marshalled. By default the
data format marshals the null, and what that gives depends on the data format
(JSON gives the text null). When tr [...]
}
}
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 b305ba561bbb..e438e41c1419 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
@@ -7360,6 +7360,17 @@ easily control what data to use for sending and
receiving.
<![CDATA[
To use a variable to store the received message body (only body, not headers).
This makes it handy to use variables for
user data and to easily control what data to use for sending and receiving.
+]]>
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="allowNullBody" type="xs:string">
+ <xs:annotation>
+ <xs:documentation xml:lang="en">
+<![CDATA[
+Indicates whether a null body is skipped instead of marshalled. By default the
data format marshals the null, and what
+that gives depends on the data format (JSON gives the text null). When true, a
null body is not marshalled and the body
+stays null. Default value: false
]]>
</xs:documentation>
</xs:annotation>
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-xml-io.xsd
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-xml-io.xsd
index 6ade90f22464..81f71962e73e 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-xml-io.xsd
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-xml-io.xsd
@@ -6482,6 +6482,17 @@ easily control what data to use for sending and
receiving.
<![CDATA[
To use a variable to store the received message body (only body, not headers).
This makes it handy to use variables for
user data and to easily control what data to use for sending and receiving.
+]]>
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="allowNullBody" type="xs:string">
+ <xs:annotation>
+ <xs:documentation xml:lang="en">
+<![CDATA[
+Indicates whether a null body is skipped instead of marshalled. By default the
data format marshals the null, and what
+that gives depends on the data format (JSON gives the text null). When true, a
null body is not marshalled and the body
+stays null. Default value: false
]]>
</xs:documentation>
</xs:annotation>
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camelYamlDsl-model.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camelYamlDsl-model.json
index 4fe171054c8c..ede879de9011 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camelYamlDsl-model.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camelYamlDsl-model.json
@@ -3877,6 +3877,17 @@
"kind" : "attribute",
"index" : 6,
"group" : "common"
+ }, {
+ "name" : "allowNullBody",
+ "type" : "boolean",
+ "description" : "Indicates whether a null body is skipped instead of
marshalled. By default the data format marshals the null, and what that gives
depends on the data format (JSON gives the text null). When true, a null body
is not marshalled and the body stays null.",
+ "title" : "Allow Null Body",
+ "default" : "false",
+ "displayName" : "Allow Null Body",
+ "kind" : "attribute",
+ "index" : 7,
+ "group" : "advanced",
+ "label" : "advanced"
}, {
"name" : "asn1",
"type" : "object",
diff --git
a/core/camel-core-engine/src/main/docs/modules/eips/pages/marshal-eip.adoc
b/core/camel-core-engine/src/main/docs/modules/eips/pages/marshal-eip.adoc
index 90a501c29f47..398f85725e39 100644
--- a/core/camel-core-engine/src/main/docs/modules/eips/pages/marshal-eip.adoc
+++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/marshal-eip.adoc
@@ -78,3 +78,54 @@ YAML::
uri: jms:queue:order
----
====
+
+== Allow Null Body
+
+By default, a `null` body is marshalled by the data format, and what that
gives depends on the data format:
+JSON gives the text `null`, CSV an empty text. When `null` is a normal value
of the body, for example a lookup
+that found nothing, use the option `allowNullBody` to skip the marshalling:
the body then stays `null`.
+
+[tabs]
+====
+
+Java::
++
+[source,java]
+----
+from("direct:order")
+ .to("bean:findOrder")
+ .marshal().allowNullBody().json()
+ .to("mock:result");
+----
+
+XML::
++
+[source,xml]
+----
+<route>
+ <from uri="direct:order"/>
+ <to uri="bean:findOrder"/>
+ <marshal allowNullBody="true">
+ <json/>
+ </marshal>
+ <to uri="mock:result"/>
+</route>
+----
+
+YAML::
++
+[source,yaml]
+----
+- route:
+ from:
+ uri: direct:order
+ steps:
+ - to:
+ uri: bean:findOrder
+ - marshal:
+ allowNullBody: true
+ json: {}
+ - to:
+ uri: mock:result
+----
+====
diff --git
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/marshal.json
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/marshal.json
index 0c23a89c355d..df132a4e8531 100644
---
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/marshal.json
+++
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/marshal.json
@@ -19,6 +19,7 @@
"disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled",
"group": "advanced", "label": "advanced", "required": false, "type": "boolean",
"javaType": "java.lang.Boolean", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": false, "description": "Whether to disable this
EIP from the route during build time. Once an EIP has been disabled then it
cannot be enabled later at runtime." },
"dataFormatType": { "index": 4, "kind": "element", "displayName": "Data
Format Type", "group": "common", "required": true, "type": "object",
"javaType": "org.apache.camel.model.DataFormatDefinition", "oneOf": [ "asn1",
"avro", "barcode", "base64", "beanio", "bindy", "cbor", "crypto", "csv",
"custom", "dfdl", "fhirJson", "fhirXml", "flatpack", "fory", "grok",
"groovyJson", "groovyXml", "gzipDeflater", "hl7", "ical", "iso8583",
"jacksonXml", "jaxb", "json", "jsonApi", "lzf", "mimeMulti [...]
"variableSend": { "index": 5, "kind": "attribute", "displayName":
"Variable Send", "group": "common", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "To use a variable as the source for the
message body to send. This makes it handy to use variables for user data and to
easily control what data to use for sending and receiving." },
- "variableReceive": { "index": 6, "kind": "attribute", "displayName":
"Variable Receive", "group": "common", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "To use a variable to store the received
message body (only body, not headers). This makes it handy to use variables for
user data and to easily control what data to use for sending and receiving." }
+ "variableReceive": { "index": 6, "kind": "attribute", "displayName":
"Variable Receive", "group": "common", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "To use a variable to store the received
message body (only body, not headers). This makes it handy to use variables for
user data and to easily control what data to use for sending and receiving." },
+ "allowNullBody": { "index": 7, "kind": "attribute", "displayName": "Allow
Null Body", "group": "advanced", "label": "advanced", "required": false,
"type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Indicates whether a null body is skipped instead of marshalled. By default the
data format marshals the null, and what that gives depends on the data format
(JSON gives the text null). When tr [...]
}
}
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 8fc664634ad7..05d76a824f4d 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
@@ -1581,7 +1581,7 @@ public class DataFormatClause<T extends
ProcessorDefinition<?>> {
}
/**
- * Allows {@code null} as value of a body to unmarshall.
+ * Allows {@code null} as value of a body: unmarshal and marshal skip a
{@code null} body, so it stays {@code null}.
*
* @return the builder
*/
@@ -1590,10 +1590,10 @@ public class DataFormatClause<T extends
ProcessorDefinition<?>> {
}
/**
- * Indicates whether {@code null} is allowed as value of a body to
unmarshall.
+ * Indicates whether {@code null} is allowed as value of a body: unmarshal
and marshal skip a {@code null} body, so
+ * it stays {@code null}.
*
- * @param allowNullBody {@code true} if {@code null} is allowed as value
of a body to unmarshall, {@code false}
- * otherwise
+ * @param allowNullBody {@code true} if {@code null} is allowed as value
of a body, {@code false} otherwise
* @return the builder
*/
public DataFormatClause<T> allowNullBody(boolean allowNullBody) {
@@ -1638,6 +1638,9 @@ public class DataFormatClause<T extends
ProcessorDefinition<?>> {
return processorType;
case Marshal:
MarshalDefinition marshal = new
MarshalDefinition(dataFormatType);
+ if (allowNullBody) {
+ marshal.allowNullBody(true);
+ }
marshal.setVariableReceive(variableReceive);
marshal.setVariableSend(variableSend);
processorType.addOutput(marshal);
diff --git
a/core/camel-core-model/src/main/java/org/apache/camel/model/MarshalDefinition.java
b/core/camel-core-model/src/main/java/org/apache/camel/model/MarshalDefinition.java
index 806d55f742ab..d48b784c760c 100644
---
a/core/camel-core-model/src/main/java/org/apache/camel/model/MarshalDefinition.java
+++
b/core/camel-core-model/src/main/java/org/apache/camel/model/MarshalDefinition.java
@@ -145,6 +145,12 @@ public class MarshalDefinition extends
NoOutputDefinition<MarshalDefinition> imp
@XmlAttribute
@Metadata(description = "To use a variable to store the received message
body (only body, not headers). This makes it handy to use variables for user
data and to easily control what data to use for sending and receiving.")
private String variableReceive;
+ @XmlAttribute
+ @Metadata(label = "advanced", javaType = "java.lang.Boolean", defaultValue
= "false",
+ description = "Indicates whether a null body is skipped instead
of marshalled. By default the data format marshals"
+ + " the null, and what that gives depends on the
data format (JSON gives the text null)."
+ + " When true, a null body is not marshalled and
the body stays null.")
+ private String allowNullBody;
public MarshalDefinition() {
}
@@ -153,6 +159,7 @@ public class MarshalDefinition extends
NoOutputDefinition<MarshalDefinition> imp
super(source);
this.variableSend = source.variableSend;
this.variableReceive = source.variableReceive;
+ this.allowNullBody = source.allowNullBody;
this.dataFormatType = source.dataFormatType != null ?
source.dataFormatType.copyDefinition() : null;
}
@@ -210,6 +217,14 @@ public class MarshalDefinition extends
NoOutputDefinition<MarshalDefinition> imp
this.variableReceive = variableReceive;
}
+ public String getAllowNullBody() {
+ return allowNullBody;
+ }
+
+ public void setAllowNullBody(String allowNullBody) {
+ this.allowNullBody = allowNullBody;
+ }
+
// Fluent API
//
-------------------------------------------------------------------------
@@ -238,4 +253,17 @@ public class MarshalDefinition extends
NoOutputDefinition<MarshalDefinition> imp
return this;
}
+ /**
+ * Indicates whether a {@code null} body is skipped instead of marshalled.
By default the data format marshals the
+ * {@code null}, and what that gives depends on the data format (JSON
gives the text {@code null}).
+ *
+ * @param allowNullBody {@code true} to skip marshalling a {@code null}
body, so the body stays {@code null},
+ * {@code false} to let the data format marshal it
+ * @return the builder
+ */
+ public MarshalDefinition allowNullBody(boolean allowNullBody) {
+ setAllowNullBody(Boolean.toString(allowNullBody));
+ return this;
+ }
+
}
diff --git
a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/MarshalReifier.java
b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/MarshalReifier.java
index fb4a4cc7fb07..49783ac10640 100644
---
a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/MarshalReifier.java
+++
b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/MarshalReifier.java
@@ -33,7 +33,8 @@ public class MarshalReifier extends
ProcessorReifier<MarshalDefinition> {
@Override
public Processor createProcessor() {
DataFormat dataFormat = DataFormatReifier.getDataFormat(camelContext,
definition.getDataFormatType());
- MarshalProcessor answer = new MarshalProcessor(dataFormat);
+ MarshalProcessor answer
+ = new MarshalProcessor(dataFormat, Boolean.TRUE ==
parseBoolean(definition.getAllowNullBody()));
answer.setVariableSend(parseString(definition.getVariableSend()));
answer.setVariableReceive(parseString(definition.getVariableReceive()));
answer.setDisabled(isDisabled(camelContext, definition));
diff --git
a/core/camel-core/src/test/java/org/apache/camel/processor/MarshalAllowNullBodyTest.java
b/core/camel-core/src/test/java/org/apache/camel/processor/MarshalAllowNullBodyTest.java
new file mode 100644
index 000000000000..3549a5581e3f
--- /dev/null
+++
b/core/camel-core/src/test/java/org/apache/camel/processor/MarshalAllowNullBodyTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.processor;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.spi.DataFormat;
+import org.apache.camel.support.service.ServiceSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+public class MarshalAllowNullBodyTest extends ContextTestSupport {
+
+ @Test
+ public void testNullBodyIsMarshalledByDefault() {
+ Object out = template.requestBody("direct:default", (Object) null);
+ assertEquals("null",
context.getTypeConverter().convertTo(String.class, out));
+ }
+
+ @Test
+ public void testAllowNullBodySkipsTheMarshalling() {
+ Object out = template.requestBodyAndHeader("direct:allow", null,
"foo", "bar");
+ assertNull(out);
+ }
+
+ @Test
+ public void testAllowNullBodyKeepsHeaders() throws Exception {
+ getMockEndpoint("mock:allow").expectedHeaderReceived("foo", "bar");
+ template.sendBodyAndHeader("direct:allow", null, "foo", "bar");
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testAllowNullBodyMarshalsABody() {
+ Object out = template.requestBody("direct:allow", "Camel");
+ assertEquals("Camel",
context.getTypeConverter().convertTo(String.class, out));
+ }
+
+ @Test
+ public void testAllowNullBodySetsTheReceiveVariableToNull() throws
Exception {
+ getMockEndpoint("mock:receive").expectedVariableReceived("out", null);
+ template.sendBody("direct:receive", null);
+ assertMockEndpointsSatisfied();
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ context.getRegistry().bind("myDF", new MyNullDataFormat());
+
+ from("direct:default")
+ .marshal().custom("myDF");
+
+ from("direct:allow")
+ .marshal().allowNullBody().custom("myDF")
+ .to("mock:allow");
+
+ from("direct:receive")
+ .setVariable("out", constant("stale"))
+
.marshal().allowNullBody().variableReceive("out").custom("myDF")
+ .to("mock:receive");
+ }
+ };
+ }
+
+ /**
+ * Marshals like JSON does: a null body becomes the text null.
+ */
+ private static class MyNullDataFormat extends ServiceSupport implements
DataFormat {
+
+ @Override
+ public void marshal(Exchange exchange, Object graph, OutputStream
stream) throws Exception {
+
stream.write(String.valueOf(graph).getBytes(StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public Object unmarshal(Exchange exchange, InputStream stream) {
+ return null;
+ }
+ }
+}
diff --git
a/core/camel-java-io/src/generated/java/org/apache/camel/java/out/JavaDslModelWriter.java
b/core/camel-java-io/src/generated/java/org/apache/camel/java/out/JavaDslModelWriter.java
index 7fd3b343b54d..08726f2dd734 100644
---
a/core/camel-java-io/src/generated/java/org/apache/camel/java/out/JavaDslModelWriter.java
+++
b/core/camel-java-io/src/generated/java/org/apache/camel/java/out/JavaDslModelWriter.java
@@ -2088,6 +2088,7 @@ public class JavaDslModelWriter extends
JavaDslModelWriterSupport {
doWriteProcessorDefinitionAttributes(sb, def);
doWriteAttribute(sb, "variableSend", def.getVariableSend(), null);
doWriteAttribute(sb, "variableReceive", def.getVariableReceive(),
null);
+ doWriteAttribute(sb, "allowNullBody", def.getAllowNullBody(), "false");
if (def.getDataFormatType() != null) {
switch (def.getDataFormatType().getClass().getSimpleName()) {
case "ASN1DataFormat" -> doWriteChildElement(sb, "asn1",
(ASN1DataFormat) def.getDataFormatType(), this::doWriteASN1DataFormat);
diff --git
a/core/camel-support/src/main/java/org/apache/camel/support/processor/MarshalProcessor.java
b/core/camel-support/src/main/java/org/apache/camel/support/processor/MarshalProcessor.java
index c1ce4eccf93e..3c899f4923fc 100644
---
a/core/camel-support/src/main/java/org/apache/camel/support/processor/MarshalProcessor.java
+++
b/core/camel-support/src/main/java/org/apache/camel/support/processor/MarshalProcessor.java
@@ -45,28 +45,42 @@ public class MarshalProcessor extends AsyncProcessorSupport
private String stepId;
private CamelContext camelContext;
private final DataFormat dataFormat;
+ private final boolean allowNullBody;
private boolean disabled;
private String variableSend;
private String variableReceive;
public MarshalProcessor(DataFormat dataFormat) {
+ this(dataFormat, false);
+ }
+
+ public MarshalProcessor(DataFormat dataFormat, boolean allowNullBody) {
this.dataFormat = dataFormat;
+ this.allowNullBody = allowNullBody;
}
@Override
public boolean process(Exchange exchange, AsyncCallback callback) {
ObjectHelper.notNull(dataFormat, "dataFormat");
- // if stream caching is enabled then use that so we can stream
accordingly
- // for example to overflow to disk for big streams
- OutputStreamBuilder osb = OutputStreamBuilder.withExchange(exchange);
-
Message in = exchange.getIn();
final Object originalBody = in.getBody();
Object body = originalBody;
if (variableSend != null) {
body = ExchangeHelper.getVariable(exchange, variableSend);
}
+ if (allowNullBody && body == null) {
+ // the body is null, and it is an allowed value so let's skip the
marshalling
+ if (variableReceive != null) {
+ ExchangeHelper.setVariable(exchange, variableReceive, null);
+ }
+ callback.done(true);
+ return true;
+ }
+
+ // if stream caching is enabled then use that so we can stream
accordingly
+ // for example to overflow to disk for big streams
+ OutputStreamBuilder osb = OutputStreamBuilder.withExchange(exchange);
// lets setup the out message before we invoke the dataFormat
// so that it can mutate it if necessary
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 01f8681c2472..8beb70e5816d 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
@@ -558,6 +558,7 @@ public class ModelParser extends BaseParser {
}
protected MarshalDefinition doParseMarshalDefinition() throws IOException,
XmlPullParserException {
return doParse(new MarshalDefinition(), (def, key, val) -> switch
(key) {
+ case "allowNullBody": def.setAllowNullBody(val); yield true;
case "variableReceive": def.setVariableReceive(val); yield
true;
case "variableSend": def.setVariableSend(val); yield true;
default: yield
processorDefinitionAttributeHandler().accept(def, key, val);
diff --git
a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java
b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java
index 141f9bc7fbf1..554ece49b258 100644
---
a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java
+++
b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java
@@ -1198,6 +1198,7 @@ public class ModelWriter extends BaseWriter {
doWriteProcessorDefinitionAttributes(def);
doWriteAttribute("variableSend", def.getVariableSend(), null);
doWriteAttribute("variableReceive", def.getVariableReceive(), null);
+ doWriteAttribute("allowNullBody", def.getAllowNullBody(), "false");
doWriteElement(null, def.getDataFormatType(), (n, v) -> {
switch (v.getClass().getSimpleName()) {
case "ASN1DataFormat" -> doWriteASN1DataFormat("asn1",
(ASN1DataFormat) v);
diff --git
a/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/YamlModelWriter.java
b/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/YamlModelWriter.java
index 1f82a92f49d5..195c4ebaaa0c 100644
---
a/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/YamlModelWriter.java
+++
b/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/YamlModelWriter.java
@@ -1192,6 +1192,7 @@ public class YamlModelWriter extends
YamlModelWriterSupport {
doWriteProcessorDefinitionAttributes(jo, def);
doWriteAttribute(jo, "variableSend", def.getVariableSend(), null);
doWriteAttribute(jo, "variableReceive", def.getVariableReceive(),
null);
+ doWriteAttribute(jo, "allowNullBody", def.getAllowNullBody(), "false");
if (def.getDataFormatType() != null) {
switch (def.getDataFormatType().getClass().getSimpleName()) {
case "ASN1DataFormat" -> doWriteChildElement(jo, "asn1",
(ASN1DataFormat) def.getDataFormatType(), this::doWriteASN1DataFormat);
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/org/apache/camel/dsl/jbang/core/commands/ai/eip-samples.json
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/org/apache/camel/dsl/jbang/core/commands/ai/eip-samples.json
index 0cb958258c48..00c21424972c 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/org/apache/camel/dsl/jbang/core/commands/ai/eip-samples.json
+++
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/org/apache/camel/dsl/jbang/core/commands/ai/eip-samples.json
@@ -621,6 +621,10 @@
{
"source": "marshal-eip.adoc",
"yaml": "- route:\n from:\n uri: file:inbox\/xml\n steps:\n
- unmarshal:\n jaxb:\n contextPath:
com.acme.order\n - to:\n uri: bean:validateOrder\n -
marshal:\n jaxb:\n contextPath: com.acme.order\n
- to:\n uri: jms:queue:order\n"
+ },
+ {
+ "source": "marshal-eip.adoc",
+ "yaml": "- route:\n from:\n uri: direct:order\n steps:\n
- to:\n uri: bean:findOrder\n - marshal:\n
allowNullBody: true\n json: {}\n - to:\n uri:
mock:result\n"
}
],
"messageBus": [
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 863c55758a50..cc102eea2e2e 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
@@ -9488,6 +9488,7 @@ public final class ModelDeserializers extends
YamlDeserializerSupport {
description = "Serializes the message body into a specific data
format such as JSON, XML, CSV, or Protobuf for transmission or storage",
deprecated = false,
properties = {
+ @YamlProperty(name = "allowNullBody", type = "boolean",
defaultValue = "false", description = "Indicates whether a null body is skipped
instead of marshalled. By default the data format marshals the null, and what
that gives depends on the data format (JSON gives the text null). When true, a
null body is not marshalled and the body stays null.", displayName = "Allow
Null Body"),
@YamlProperty(name = "asn1", type =
"object:org.apache.camel.model.dataformat.ASN1DataFormat", required = true,
oneOf = "dataFormatType"),
@YamlProperty(name = "avro", type =
"object:org.apache.camel.model.dataformat.AvroDataFormat", required = true,
oneOf = "dataFormatType"),
@YamlProperty(name = "barcode", type =
"object:org.apache.camel.model.dataformat.BarcodeDataFormat", required = true,
oneOf = "dataFormatType"),
@@ -9561,6 +9562,11 @@ public final class ModelDeserializers extends
YamlDeserializerSupport {
String propertyName, Node node) {
propertyKey =
org.apache.camel.util.StringHelper.dashToCamelCase(propertyKey);
switch(propertyKey) {
+ case "allowNullBody": {
+ String val = asText(node);
+ target.setAllowNullBody(val);
+ break;
+ }
case "dataFormatType": {
MappingNode val = asMappingNode(node);
setProperties(target, val);
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-canonical.json
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-canonical.json
index 1730c3808dc1..677d10d97025 100644
---
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-canonical.json
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-canonical.json
@@ -2132,6 +2132,12 @@
"type" : "object",
"additionalProperties" : false,
"properties" : {
+ "allowNullBody" : {
+ "type" : "boolean",
+ "title" : "Allow Null Body",
+ "description" : "Indicates whether a null body is skipped instead
of marshalled. By default the data format marshals the null, and what that
gives depends on the data format (JSON gives the text null). When true, a null
body is not marshalled and the body stays null.",
+ "default" : false
+ },
"asn1" : {
"$ref" :
"#/items/definitions/org.apache.camel.model.dataformat.ASN1DataFormat"
},
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-model.json
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-model.json
index 4fe171054c8c..ede879de9011 100644
---
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-model.json
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-model.json
@@ -3877,6 +3877,17 @@
"kind" : "attribute",
"index" : 6,
"group" : "common"
+ }, {
+ "name" : "allowNullBody",
+ "type" : "boolean",
+ "description" : "Indicates whether a null body is skipped instead of
marshalled. By default the data format marshals the null, and what that gives
depends on the data format (JSON gives the text null). When true, a null body
is not marshalled and the body stays null.",
+ "title" : "Allow Null Body",
+ "default" : "false",
+ "displayName" : "Allow Null Body",
+ "kind" : "attribute",
+ "index" : 7,
+ "group" : "advanced",
+ "label" : "advanced"
}, {
"name" : "asn1",
"type" : "object",
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
index 9b46684876a6..bddc9561c35f 100644
---
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
@@ -2849,6 +2849,95 @@
"description" : "Serializes the message body into a specific data
format such as JSON, XML, CSV, or Protobuf for transmission or storage",
"type" : "object",
"additionalProperties" : false,
+ "properties" : {
+ "allowNullBody" : {
+ "type" : "boolean",
+ "title" : "Allow Null Body",
+ "description" : "Indicates whether a null body is skipped instead
of marshalled. By default the data format marshals the null, and what that
gives depends on the data format (JSON gives the text null). When true, a null
body is not marshalled and the body stays null.",
+ "default" : false
+ },
+ "description" : {
+ "type" : "string",
+ "title" : "Description",
+ "description" : "The description for this node"
+ },
+ "disabled" : {
+ "type" : "boolean",
+ "title" : "Disabled",
+ "description" : "Whether to disable this EIP from the route during
build time. Once an EIP has been disabled then it cannot be enabled later at
runtime.",
+ "default" : false
+ },
+ "id" : {
+ "type" : "string",
+ "title" : "Id",
+ "description" : "The id of this node"
+ },
+ "note" : {
+ "type" : "string",
+ "title" : "Note",
+ "description" : "The note for this node"
+ },
+ "variableReceive" : {
+ "type" : "string",
+ "title" : "Variable Receive",
+ "description" : "To use a variable to store the received message
body (only body, not headers). This makes it handy to use variables for user
data and to easily control what data to use for sending and receiving."
+ },
+ "variableSend" : {
+ "type" : "string",
+ "title" : "Variable Send",
+ "description" : "To use a variable as the source for the message
body to send. This makes it handy to use variables for user data and to easily
control what data to use for sending and receiving."
+ },
+ "asn1" : { },
+ "avro" : { },
+ "barcode" : { },
+ "base64" : { },
+ "beanio" : { },
+ "bindy" : { },
+ "cbor" : { },
+ "crypto" : { },
+ "csv" : { },
+ "custom" : { },
+ "dfdl" : { },
+ "fhirJson" : { },
+ "fhirXml" : { },
+ "flatpack" : { },
+ "fory" : { },
+ "grok" : { },
+ "groovyJson" : { },
+ "groovyXml" : { },
+ "gzipDeflater" : { },
+ "hl7" : { },
+ "ical" : { },
+ "iso8583" : { },
+ "jacksonXml" : { },
+ "jaxb" : { },
+ "json" : { },
+ "jsonApi" : { },
+ "lzf" : { },
+ "mimeMultipart" : { },
+ "ocsf" : { },
+ "parquetAvro" : { },
+ "pgp" : { },
+ "pqc" : { },
+ "protobuf" : { },
+ "rss" : { },
+ "smooks" : { },
+ "soap" : { },
+ "swiftMt" : { },
+ "swiftMx" : { },
+ "syslog" : { },
+ "tarFile" : { },
+ "thrift" : { },
+ "toon" : { },
+ "ubl" : { },
+ "univocityCsv" : { },
+ "univocityFixed" : { },
+ "univocityTsv" : { },
+ "xmlSecurity" : { },
+ "yaml" : { },
+ "zipDeflater" : { },
+ "zipFile" : { }
+ },
"anyOf" : [ {
"oneOf" : [ {
"type" : "object",
@@ -3251,90 +3340,7 @@
}
}
} ]
- } ],
- "properties" : {
- "description" : {
- "type" : "string",
- "title" : "Description",
- "description" : "The description for this node"
- },
- "disabled" : {
- "type" : "boolean",
- "title" : "Disabled",
- "description" : "Whether to disable this EIP from the route during
build time. Once an EIP has been disabled then it cannot be enabled later at
runtime.",
- "default" : false
- },
- "id" : {
- "type" : "string",
- "title" : "Id",
- "description" : "The id of this node"
- },
- "note" : {
- "type" : "string",
- "title" : "Note",
- "description" : "The note for this node"
- },
- "variableReceive" : {
- "type" : "string",
- "title" : "Variable Receive",
- "description" : "To use a variable to store the received message
body (only body, not headers). This makes it handy to use variables for user
data and to easily control what data to use for sending and receiving."
- },
- "variableSend" : {
- "type" : "string",
- "title" : "Variable Send",
- "description" : "To use a variable as the source for the message
body to send. This makes it handy to use variables for user data and to easily
control what data to use for sending and receiving."
- },
- "asn1" : { },
- "avro" : { },
- "barcode" : { },
- "base64" : { },
- "beanio" : { },
- "bindy" : { },
- "cbor" : { },
- "crypto" : { },
- "csv" : { },
- "custom" : { },
- "dfdl" : { },
- "fhirJson" : { },
- "fhirXml" : { },
- "flatpack" : { },
- "fory" : { },
- "grok" : { },
- "groovyJson" : { },
- "groovyXml" : { },
- "gzipDeflater" : { },
- "hl7" : { },
- "ical" : { },
- "iso8583" : { },
- "jacksonXml" : { },
- "jaxb" : { },
- "json" : { },
- "jsonApi" : { },
- "lzf" : { },
- "mimeMultipart" : { },
- "ocsf" : { },
- "parquetAvro" : { },
- "pgp" : { },
- "pqc" : { },
- "protobuf" : { },
- "rss" : { },
- "smooks" : { },
- "soap" : { },
- "swiftMt" : { },
- "swiftMx" : { },
- "syslog" : { },
- "tarFile" : { },
- "thrift" : { },
- "toon" : { },
- "ubl" : { },
- "univocityCsv" : { },
- "univocityFixed" : { },
- "univocityTsv" : { },
- "xmlSecurity" : { },
- "yaml" : { },
- "zipDeflater" : { },
- "zipFile" : { }
- }
+ } ]
},
"org.apache.camel.model.MulticastDefinition" : {
"title" : "Multicast",