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

davsclaus pushed a commit to branch allow-null
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 57dd08322daf8ffd17006c926e293f152aaff996
Author: Claus Ibsen <claus.ib...@gmail.com>
AuthorDate: Thu Apr 10 18:08:19 2025 +0200

    CAMEL-21949: camel-core - Type converter with allowNull should be a valid 
response for ConvertBodyTo
---
 .../org/apache/camel/spi/BulkTypeConverters.java   |  19 ++-
 .../converter/CamelBaseBulkConverterLoader.java    |   6 +-
 .../impl/converter/CoreTypeConverterRegistry.java  |  19 ++-
 .../converter/ConvertBodyAllowNullTest.java        | 150 +++++++++++++++++++++
 .../camel/processor/converter/ConvertBodyTest.java |   6 +-
 .../processor/converter/ConvertHeaderTest.java     |   5 +-
 .../processor/converter/ConvertVariableTest.java   |   5 +-
 .../stream/StreamCacheBulkConverterLoader.java     |   6 +-
 .../jaxp/CamelXmlJaxpBulkConverterLoader.java      |   6 +-
 .../resources/velocity/bulk-converter-loader.vm    |   6 +-
 10 files changed, 194 insertions(+), 34 deletions(-)

diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/BulkTypeConverters.java 
b/core/camel-api/src/main/java/org/apache/camel/spi/BulkTypeConverters.java
index 3c7f96d9af6..797e66dd7a2 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/BulkTypeConverters.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/BulkTypeConverters.java
@@ -55,6 +55,11 @@ public interface BulkTypeConverters extends Ordered, 
TypeConverter {
      */
     <T> T convertTo(Class<?> from, Class<T> to, Exchange exchange, Object 
value) throws TypeConversionException;
 
+    default Object allowNullConvertTo(Class<?> from, Class<?> to, Exchange 
exchange, Object value)
+            throws TypeConversionException {
+        return convertTo(from, to, exchange, value);
+    }
+
     /**
      * Tries to convert the value to the specified type, returning 
<tt>null</tt> if not possible to convert.
      * <p/>
@@ -67,7 +72,11 @@ public interface BulkTypeConverters extends Ordered, 
TypeConverter {
      */
     default <T> T tryConvertTo(Class<?> from, Class<T> to, Exchange exchange, 
Object value) throws TypeConversionException {
         try {
-            convertTo(from, to, exchange, value);
+            Object t = allowNullConvertTo(from, to, exchange, value);
+            if (t == Void.class) {
+                return null;
+            }
+            return (T) t;
         } catch (Exception e) {
             // ignore
         }
@@ -89,11 +98,13 @@ public interface BulkTypeConverters extends Ordered, 
TypeConverter {
      */
     default <T> T mandatoryConvertTo(Class<?> from, Class<T> to, Exchange 
exchange, Object value)
             throws TypeConversionException, NoTypeConversionAvailableException 
{
-        T t = convertTo(from, to, exchange, value);
-        if (t == null) {
+        Object t = allowNullConvertTo(from, to, exchange, value);
+        if (t == Void.class) {
+            return null;
+        } else if (t == null) {
             throw new NoTypeConversionAvailableException(value, to);
         } else {
-            return t;
+            return (T) t;
         }
     }
 
diff --git 
a/core/camel-base/src/generated/java/org/apache/camel/converter/CamelBaseBulkConverterLoader.java
 
b/core/camel-base/src/generated/java/org/apache/camel/converter/CamelBaseBulkConverterLoader.java
index ad8da21668a..d393ccfaec0 100644
--- 
a/core/camel-base/src/generated/java/org/apache/camel/converter/CamelBaseBulkConverterLoader.java
+++ 
b/core/camel-base/src/generated/java/org/apache/camel/converter/CamelBaseBulkConverterLoader.java
@@ -54,11 +54,7 @@ public final class CamelBaseBulkConverterLoader implements 
TypeConverterLoader,
     public <T> T convertTo(Class<?> from, Class<T> to, Exchange exchange, 
Object value) throws TypeConversionException {
         try {
             Object obj = doConvertTo(from, to, exchange, value);
-            if (obj == Void.class) {
-                return null;
-            } else {
-                return (T) obj;
-            }
+            return (T) obj;
         } catch (TypeConversionException e) {
             throw e;
         } catch (Exception e) {
diff --git 
a/core/camel-base/src/main/java/org/apache/camel/impl/converter/CoreTypeConverterRegistry.java
 
b/core/camel-base/src/main/java/org/apache/camel/impl/converter/CoreTypeConverterRegistry.java
index f37de3bf176..e6cc5048980 100644
--- 
a/core/camel-base/src/main/java/org/apache/camel/impl/converter/CoreTypeConverterRegistry.java
+++ 
b/core/camel-base/src/main/java/org/apache/camel/impl/converter/CoreTypeConverterRegistry.java
@@ -154,6 +154,9 @@ public abstract class CoreTypeConverterRegistry extends 
ServiceSupport implement
 
         if (value != null) {
             T ret = fastConvertTo(type, exchange, value);
+            if (ret == Void.class) {
+                return null;
+            }
             if (ret != null) {
                 return ret;
             }
@@ -161,7 +164,11 @@ public abstract class CoreTypeConverterRegistry extends 
ServiceSupport implement
             // NOTE: we cannot optimize any more if value is String as it may 
be time pattern and other patterns
         }
 
-        return (T) doConvertToAndStat(type, exchange, value, false);
+        Object answer = doConvertToAndStat(type, exchange, value, false);
+        if (answer == Void.class) {
+            answer = null;
+        }
+        return (T) answer;
     }
 
     private static Boolean customParseBoolean(String str) {
@@ -191,6 +198,9 @@ public abstract class CoreTypeConverterRegistry extends 
ServiceSupport implement
         }
 
         Object answer = doConvertToAndStat(type, exchange, value, false);
+        if (answer == Void.class) {
+            return null;
+        }
         if (answer == null) {
             // Could not find suitable conversion
             throw new NoTypeConversionAvailableException(value, type);
@@ -249,7 +259,11 @@ public abstract class CoreTypeConverterRegistry extends 
ServiceSupport implement
             // NOTE: we cannot optimize any more if value is String as it may 
be time pattern and other patterns
         }
 
-        return (T) doConvertToAndStat(type, exchange, value, true);
+        Object answer = doConvertToAndStat(type, exchange, value, true);
+        if (answer == Void.class) {
+            return null;
+        }
+        return (T) answer;
     }
 
     private static <T> void requireNonNullBoolean(Class<T> type, Object value, 
Object answer) {
@@ -290,7 +304,6 @@ public abstract class CoreTypeConverterRegistry extends 
ServiceSupport implement
             if (!tryConvert) {
                 statistics.incrementHit();
             }
-
             return answer;
         }
     }
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/processor/converter/ConvertBodyAllowNullTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/processor/converter/ConvertBodyAllowNullTest.java
new file mode 100644
index 00000000000..4336d94de3c
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/processor/converter/ConvertBodyAllowNullTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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.converter;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class ConvertBodyAllowNullTest extends ContextTestSupport {
+
+    // TODO: custom type converter with allow null
+
+    @Test
+    public void testConvertAllowNull() throws Exception {
+        Object val = context.getTypeConverter().convertTo(Integer.class, 
Float.NaN);
+        Assertions.assertNull(val);
+        val = context.getTypeConverter().mandatoryConvertTo(Integer.class, 
Float.NaN);
+        Assertions.assertNull(val);
+        val = context.getTypeConverter().tryConvertTo(Integer.class, 
Float.NaN);
+        Assertions.assertNull(val);
+
+        val = context.getTypeConverter().convertTo(Integer.class, 123);
+        Assertions.assertEquals(123, val);
+        val = context.getTypeConverter().mandatoryConvertTo(Integer.class, 
123);
+        Assertions.assertEquals(123, val);
+        val = context.getTypeConverter().tryConvertTo(Integer.class, 123);
+        Assertions.assertEquals(123, val);
+    }
+
+    @Test
+    public void testConvertAllowNullWithExchange() throws Exception {
+        Exchange exchange = 
context.getEndpoint("mock:result").createExchange();
+
+        Object val = context.getTypeConverter().convertTo(Integer.class, 
exchange, Float.NaN);
+        Assertions.assertNull(val);
+        val = context.getTypeConverter().mandatoryConvertTo(Integer.class, 
exchange, Float.NaN);
+        Assertions.assertNull(val);
+        val = context.getTypeConverter().tryConvertTo(Integer.class, exchange, 
Float.NaN);
+        Assertions.assertNull(val);
+
+        val = context.getTypeConverter().convertTo(Integer.class, exchange, 
123);
+        Assertions.assertEquals(123, val);
+        val = context.getTypeConverter().mandatoryConvertTo(Integer.class, 
exchange, 123);
+        Assertions.assertEquals(123, val);
+        val = context.getTypeConverter().tryConvertTo(Integer.class, exchange, 
123);
+        Assertions.assertEquals(123, val);
+    }
+
+    @Test
+    public void testConvertToAllowNullOptional() throws Exception {
+        MockEndpoint result = getMockEndpoint("mock:result");
+        result.expectedMessageCount(1);
+        result.message(0).body().isNull();
+
+        template.sendBody("direct:optional", Float.NaN);
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testConvertToAllowNull() throws Exception {
+        MockEndpoint result = getMockEndpoint("mock:result");
+        result.expectedMessageCount(1);
+        result.message(0).body().isNull();
+
+        template.sendBody("direct:mandatory", Float.NaN);
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testHeaderConvertToAllowNullOptional() throws Exception {
+        MockEndpoint result = getMockEndpoint("mock:result");
+        result.expectedMessageCount(1);
+        result.message(0).header("foo").isNull();
+
+        template.sendBodyAndHeader("direct:header-optional", "Hello World", 
"foo", Float.NaN);
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testHeaderConvertToAllowNull() throws Exception {
+        MockEndpoint result = getMockEndpoint("mock:result");
+        result.expectedMessageCount(1);
+        result.message(0).header("foo").isNull();
+
+        template.sendBodyAndHeader("direct:header-mandatory", "Hello World", 
"foo", Float.NaN);
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testVarConvertToAllowNullOptional() throws Exception {
+        MockEndpoint result = getMockEndpoint("mock:result");
+        result.expectedMessageCount(1);
+        result.message(0).variable("foo").isNull();
+
+        fluentTemplate.withVariable("foo", Float.NaN).withBody("Hello 
World").to("direct:var-optional").send();
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testVarConvertToAllowNull() throws Exception {
+        MockEndpoint result = getMockEndpoint("mock:result");
+        result.expectedMessageCount(1);
+        result.message(0).variable("foo").isNull();
+
+        fluentTemplate.withVariable("foo", Float.NaN).withBody("Hello 
World").to("direct:var-mandatory").send();
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            public void configure() {
+                context.setStreamCaching(false);
+
+                from("direct:optional").convertBodyTo(Integer.class, 
false).to("mock:result");
+                
from("direct:mandatory").convertBodyTo(Integer.class).to("mock:result");
+
+                from("direct:header-optional").convertHeaderTo("foo", 
Integer.class, false).to("mock:result");
+                from("direct:header-mandatory").convertHeaderTo("foo", 
Integer.class).to("mock:result");
+
+                from("direct:var-optional").convertVariableTo("foo", 
Integer.class, false).to("mock:result");
+                from("direct:var-mandatory").convertVariableTo("foo", 
Integer.class).to("mock:result");
+            }
+        };
+    }
+
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/processor/converter/ConvertBodyTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/processor/converter/ConvertBodyTest.java
index 56a15e1d1bc..5a7329ec87b 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/processor/converter/ConvertBodyTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/processor/converter/ConvertBodyTest.java
@@ -24,8 +24,8 @@ import java.util.Date;
 import org.apache.camel.ContextTestSupport;
 import org.apache.camel.Exchange;
 import org.apache.camel.InvalidPayloadException;
-import org.apache.camel.NoTypeConversionAvailableException;
 import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.TypeConversionException;
 import org.apache.camel.builder.ExchangeBuilder;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.component.mock.MockEndpoint;
@@ -107,10 +107,10 @@ public class ConvertBodyTest extends ContextTestSupport {
     public void testConvertToIntegerNotMandatory() throws Exception {
         // mandatory should fail
         try {
-            template.sendBody("direct:start", Double.NaN);
+            template.sendBody("direct:start", "xxx");
             fail();
         } catch (Exception e) {
-            assertIsInstanceOf(NoTypeConversionAvailableException.class, 
e.getCause().getCause());
+            assertIsInstanceOf(TypeConversionException.class, 
e.getCause().getCause());
         }
 
         // optional should cause null body
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/processor/converter/ConvertHeaderTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/processor/converter/ConvertHeaderTest.java
index 638bb35d151..db0851cf28b 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/processor/converter/ConvertHeaderTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/processor/converter/ConvertHeaderTest.java
@@ -25,6 +25,7 @@ import org.apache.camel.ContextTestSupport;
 import org.apache.camel.Exchange;
 import org.apache.camel.NoTypeConversionAvailableException;
 import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.TypeConversionException;
 import org.apache.camel.builder.ExchangeBuilder;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.component.mock.MockEndpoint;
@@ -118,10 +119,10 @@ public class ConvertHeaderTest extends ContextTestSupport 
{
     public void testConvertToIntegerNotMandatory() throws Exception {
         // mandatory should fail
         try {
-            template.sendBodyAndHeader("direct:start", null, "foo", 
Double.NaN);
+            template.sendBodyAndHeader("direct:start", null, "foo", "xxx");
             fail();
         } catch (Exception e) {
-            assertIsInstanceOf(NoTypeConversionAvailableException.class, 
e.getCause());
+            assertIsInstanceOf(TypeConversionException.class, e.getCause());
         }
 
         // optional should cause null body
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/processor/converter/ConvertVariableTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/processor/converter/ConvertVariableTest.java
index a367acd14cc..62c7113114b 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/processor/converter/ConvertVariableTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/processor/converter/ConvertVariableTest.java
@@ -26,6 +26,7 @@ import org.apache.camel.Exchange;
 import org.apache.camel.FluentProducerTemplate;
 import org.apache.camel.NoSuchVariableException;
 import org.apache.camel.NoTypeConversionAvailableException;
+import org.apache.camel.TypeConversionException;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.component.mock.MockEndpoint;
 import org.junit.jupiter.api.BeforeEach;
@@ -126,9 +127,9 @@ public class ConvertVariableTest extends ContextTestSupport 
{
     @Test
     public void testConvertToIntegerNotMandatory() throws Exception {
         // mandatory should fail
-        Exchange out = fluent.to("direct:start").withVariable("foo", 
Double.NaN).send();
+        Exchange out = fluent.to("direct:start").withVariable("foo", 
"xxx").send();
         assertTrue(out.isFailed());
-        assertIsInstanceOf(NoTypeConversionAvailableException.class, 
out.getException());
+        assertIsInstanceOf(TypeConversionException.class, out.getException());
 
         // optional should cause null body
         getMockEndpoint("mock:result").expectedMessageCount(1);
diff --git 
a/core/camel-support/src/generated/java/org/apache/camel/converter/stream/StreamCacheBulkConverterLoader.java
 
b/core/camel-support/src/generated/java/org/apache/camel/converter/stream/StreamCacheBulkConverterLoader.java
index dc652f04eeb..c3f1b386b8e 100644
--- 
a/core/camel-support/src/generated/java/org/apache/camel/converter/stream/StreamCacheBulkConverterLoader.java
+++ 
b/core/camel-support/src/generated/java/org/apache/camel/converter/stream/StreamCacheBulkConverterLoader.java
@@ -54,11 +54,7 @@ public final class StreamCacheBulkConverterLoader implements 
TypeConverterLoader
     public <T> T convertTo(Class<?> from, Class<T> to, Exchange exchange, 
Object value) throws TypeConversionException {
         try {
             Object obj = doConvertTo(from, to, exchange, value);
-            if (obj == Void.class) {
-                return null;
-            } else {
-                return (T) obj;
-            }
+            return (T) obj;
         } catch (TypeConversionException e) {
             throw e;
         } catch (Exception e) {
diff --git 
a/core/camel-xml-jaxp/src/generated/java/org/apache/camel/converter/jaxp/CamelXmlJaxpBulkConverterLoader.java
 
b/core/camel-xml-jaxp/src/generated/java/org/apache/camel/converter/jaxp/CamelXmlJaxpBulkConverterLoader.java
index 982d5f58cb6..89845e2de23 100644
--- 
a/core/camel-xml-jaxp/src/generated/java/org/apache/camel/converter/jaxp/CamelXmlJaxpBulkConverterLoader.java
+++ 
b/core/camel-xml-jaxp/src/generated/java/org/apache/camel/converter/jaxp/CamelXmlJaxpBulkConverterLoader.java
@@ -54,11 +54,7 @@ public final class CamelXmlJaxpBulkConverterLoader 
implements TypeConverterLoade
     public <T> T convertTo(Class<?> from, Class<T> to, Exchange exchange, 
Object value) throws TypeConversionException {
         try {
             Object obj = doConvertTo(from, to, exchange, value);
-            if (obj == Void.class) {
-                return null;
-            } else {
-                return (T) obj;
-            }
+            return (T) obj;
         } catch (TypeConversionException e) {
             throw e;
         } catch (Exception e) {
diff --git 
a/tooling/maven/camel-package-maven-plugin/src/main/resources/velocity/bulk-converter-loader.vm
 
b/tooling/maven/camel-package-maven-plugin/src/main/resources/velocity/bulk-converter-loader.vm
index f9b12c1781c..8c4f18190ec 100644
--- 
a/tooling/maven/camel-package-maven-plugin/src/main/resources/velocity/bulk-converter-loader.vm
+++ 
b/tooling/maven/camel-package-maven-plugin/src/main/resources/velocity/bulk-converter-loader.vm
@@ -77,11 +77,7 @@ public final class ${className} implements 
TypeConverterLoader, BulkTypeConverte
     public <T> T convertTo(Class<?> from, Class<T> to, Exchange exchange, 
Object value) throws TypeConversionException {
         try {
             Object obj = doConvertTo(from, to, exchange, value);
-            if (obj == Void.class) {
-                return null;
-            } else {
-                return (T) obj;
-            }
+            return (T) obj;
         } catch (TypeConversionException e) {
             throw e;
         } catch (Exception e) {

Reply via email to