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 6899d606d363 CAMEL-25923: camel-core - unmarshal with allowNullBody
keeps the message when the body is null (#28301)
6899d606d363 is described below
commit 6899d606d363ab6035da9765a2093cf87c59e283
Author: seshuthota <[email protected]>
AuthorDate: Fri Oct 23 13:51:56 2026 +0530
CAMEL-25923: camel-core - unmarshal with allowNullBody keeps the message
when the body is null (#28301)
unmarshal with allowNullBody=true dropped the message headers when the body
was null, because the null-body path used an empty out message. An allowed null
body now leaves the message as it is, as marshal does, and variableReceive is
still set to null.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../apache/camel/catalog/docs/unmarshal-eip.adoc | 3 +-
.../docs/modules/eips/pages/unmarshal-eip.adoc | 3 +-
.../processor/UnmarshalAllowNullBodyTest.java | 110 +++++++++++++++++++++
.../camel/processor/UnmarshalProcessorTest.java | 4 +
.../support/processor/UnmarshalProcessor.java | 54 +++++-----
5 files changed, 146 insertions(+), 28 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/unmarshal-eip.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/unmarshal-eip.adoc
index c3556e98eaa2..f8d3813eba0c 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/unmarshal-eip.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/unmarshal-eip.adoc
@@ -130,7 +130,8 @@ YAML::
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 work around 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:
+using the option `allowNullBody` as shown in the next code snippets. A `null`
body then skips the unmarshalling
+and leaves the message as it is, as `marshal` does; with `variableReceive`,
that variable is set to `null`.
[tabs]
====
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 c3556e98eaa2..f8d3813eba0c 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
@@ -130,7 +130,8 @@ YAML::
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 work around 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:
+using the option `allowNullBody` as shown in the next code snippets. A `null`
body then skips the unmarshalling
+and leaves the message as it is, as `marshal` does; with `variableReceive`,
that variable is set to `null`.
[tabs]
====
diff --git
a/core/camel-core/src/test/java/org/apache/camel/processor/UnmarshalAllowNullBodyTest.java
b/core/camel-core/src/test/java/org/apache/camel/processor/UnmarshalAllowNullBodyTest.java
new file mode 100644
index 000000000000..fd41e2509def
--- /dev/null
+++
b/core/camel-core/src/test/java/org/apache/camel/processor/UnmarshalAllowNullBodyTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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 UnmarshalAllowNullBodyTest extends ContextTestSupport {
+
+ @Test
+ public void testAllowNullBodySkipsTheUnmarshalling() {
+ 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 testAllowNullBodyUnmarshalsABody() {
+ Object out = template.requestBody("direct:allow", "Camel");
+ assertEquals("Hello Camel", out);
+ }
+
+ @Test
+ public void testAllowNullBodySetsTheReceiveVariableToNull() throws
Exception {
+ getMockEndpoint("mock:receive").expectedMessageCount(1);
+ getMockEndpoint("mock:receive").expectedHeaderReceived("foo", "bar");
+ template.sendBodyAndHeader("direct:receive", null, "foo", "bar");
+ assertMockEndpointsSatisfied();
+ // expectedVariableReceived with a null value also accepts any value,
so check the variable itself
+
assertNull(getMockEndpoint("mock:receive").getReceivedExchanges().get(0).getVariable("out"));
+ }
+
+ @Test
+ public void testAllowNullVariableLeavesTheMessage() throws Exception {
+ getMockEndpoint("mock:send").expectedBodiesReceived("Camel");
+ getMockEndpoint("mock:send").expectedHeaderReceived("foo", "bar");
+ template.sendBodyAndHeader("direct:send", "Camel", "foo", "bar");
+ assertMockEndpointsSatisfied();
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ context.getRegistry().bind("myDF", new MyHelloDataFormat());
+
+ from("direct:allow")
+ .unmarshal().allowNullBody().custom("myDF")
+ .to("mock:allow");
+
+ from("direct:receive")
+ .setVariable("out", constant("stale"))
+
.unmarshal().allowNullBody().variableReceive("out").custom("myDF")
+ .to("mock:receive");
+
+ from("direct:send")
+
.unmarshal().allowNullBody().variableSend("missing").custom("myDF")
+ .to("mock:send");
+ }
+ };
+ }
+
+ /**
+ * Unmarshals the text to a greeting, and fails on a null body, which
allowNullBody must skip.
+ */
+ private static class MyHelloDataFormat extends ServiceSupport implements
DataFormat {
+
+ @Override
+ public void marshal(Exchange exchange, Object graph, OutputStream
stream) {
+ // not used
+ }
+
+ @Override
+ public Object unmarshal(Exchange exchange, InputStream stream) throws
Exception {
+ return "Hello " + new String(stream.readAllBytes(),
StandardCharsets.UTF_8);
+ }
+ }
+}
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 bc9f71589641..57c4c9d906c1 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
@@ -90,12 +90,16 @@ public class UnmarshalProcessorTest extends TestSupport {
@Test
public void testAllowNullBody() throws Exception {
Exchange exchange = createExchangeWithBody(new DefaultCamelContext(),
null);
+ exchange.getIn().setHeader("foo", "bar");
+ Message in = exchange.getIn();
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");
+ assertSame(in, exchange.getMessage(), "UnmarshalProcessor should leave
the message as it is");
+ assertEquals("bar", exchange.getMessage().getHeader("foo"),
"UnmarshalProcessor should keep the headers");
}
@Test
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 e4cc72d3e158..056c8fb13bf0 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
@@ -80,13 +80,15 @@ public class UnmarshalProcessor extends
AsyncProcessorSupport
if (variableSend != null) {
body = ExchangeHelper.getVariable(exchange, variableSend);
}
- final Message out;
if (allowNullBody && body == null) {
- // The body is null, and it is an allowed value so let's skip
the unmarshalling
- out = exchange.getOut();
+ // the body is null, and it is an allowed value so let's skip
the unmarshalling
+ // and leave the message as it is
+ if (variableReceive != null) {
+ ExchangeHelper.setVariable(exchange, variableReceive,
null);
+ }
} else {
// lets set up the out message before we invoke the dataFormat
so that it can mutate it if necessary
- out = exchange.getOut();
+ final Message out = exchange.getOut();
out.copyFrom(in);
if (body instanceof InputStream is) {
stream = is;
@@ -94,29 +96,29 @@ public class UnmarshalProcessor extends
AsyncProcessorSupport
} else {
result = dataFormat.unmarshal(exchange, body);
}
- }
- if (result instanceof Exchange) {
- if (result != exchange) {
- // it's not allowed to return another exchange other than
the one provided to dataFormat
- throw new RuntimeCamelException(
- "The returned exchange " + result + " is not the
same as " + exchange
- + " provided to the
DataFormat");
- }
- } else if (result instanceof Message msg) {
- // result should be stored in variable instead of message body
- if (variableReceive != null) {
- Object value = msg.getBody();
- ExchangeHelper.setVariable(exchange, variableReceive,
value);
- } else {
- // the dataformat has probably set headers, attachments,
etc. so let's use it as the outbound payload
- exchange.setOut(msg);
- }
- } else {
- // result should be stored in variable instead of message body
- if (variableReceive != null) {
- ExchangeHelper.setVariable(exchange, variableReceive,
result);
+ if (result instanceof Exchange) {
+ if (result != exchange) {
+ // it's not allowed to return another exchange other
than the one provided to dataFormat
+ throw new RuntimeCamelException(
+ "The returned exchange " + result + " is not
the same as " + exchange
+ + " provided to the
DataFormat");
+ }
+ } else if (result instanceof Message msg) {
+ // result should be stored in variable instead of message
body
+ if (variableReceive != null) {
+ Object value = msg.getBody();
+ ExchangeHelper.setVariable(exchange, variableReceive,
value);
+ } else {
+ // the dataformat has probably set headers,
attachments, etc. so let's use it as the outbound payload
+ exchange.setOut(msg);
+ }
} else {
- out.setBody(result);
+ // result should be stored in variable instead of message
body
+ if (variableReceive != null) {
+ ExchangeHelper.setVariable(exchange, variableReceive,
result);
+ } else {
+ out.setBody(result);
+ }
}
}
} catch (Exception e) {