You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2020/02/16 15:55:34 UTC

[camel] 10/13: CAMEL-14572: camel-core - Optimize type converter for some basic convertions

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

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

commit da1e828f21d569c7994be70a0a6fb2cb56dec45b
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Sun Feb 16 14:21:32 2020 +0100

    CAMEL-14572: camel-core - Optimize type converter for some basic convertions
---
 .../component/couchdb/CouchDbComponentTest.java    |   2 +-
 .../debezium/DebeziumMySqlComponentTest.java       |   4 +-
 .../sql/stored/ProducerBatchInvalidTest.java       |   3 +-
 .../camel/spi/AnnotationScanTypeConverters.java    |  17 ++++
 .../camel/converter/TimePatternConverter.java      |   1 -
 .../impl/converter/BaseTypeConverterRegistry.java  | 102 ++++++++++++++++++---
 .../camel/impl/converter/DefaultTypeConverter.java |  44 ++++++---
 .../camel/impl/engine/AbstractCamelContext.java    |   5 +
 .../main/java/org/apache/camel/util/TimeUtils.java |  12 +--
 9 files changed, 154 insertions(+), 36 deletions(-)

diff --git a/components/camel-couchdb/src/test/java/org/apache/camel/component/couchdb/CouchDbComponentTest.java b/components/camel-couchdb/src/test/java/org/apache/camel/component/couchdb/CouchDbComponentTest.java
index 5d472a6..aca029b 100644
--- a/components/camel-couchdb/src/test/java/org/apache/camel/component/couchdb/CouchDbComponentTest.java
+++ b/components/camel-couchdb/src/test/java/org/apache/camel/component/couchdb/CouchDbComponentTest.java
@@ -46,7 +46,7 @@ public class CouchDbComponentTest extends CamelTestSupport {
         params.put("createDatabase", true);
         params.put("username", "coldplay");
         params.put("password", "chrism");
-        params.put("heartbeat", 1000);
+        params.put("heartbeat", "1000");
         params.put("style", "gothic");
         params.put("deletes", false);
         params.put("updates", false);
diff --git a/components/camel-debezium-mysql/src/test/java/org/apache/camel/component/debezium/DebeziumMySqlComponentTest.java b/components/camel-debezium-mysql/src/test/java/org/apache/camel/component/debezium/DebeziumMySqlComponentTest.java
index e4a2d91..aa434f5 100644
--- a/components/camel-debezium-mysql/src/test/java/org/apache/camel/component/debezium/DebeziumMySqlComponentTest.java
+++ b/components/camel-debezium-mysql/src/test/java/org/apache/camel/component/debezium/DebeziumMySqlComponentTest.java
@@ -37,7 +37,7 @@ public class DebeziumMySqlComponentTest {
         params.put("databaseUser", "dbz");
         params.put("databasePassword", "pwd");
         params.put("databaseServerName", "test");
-        params.put("databaseServerId", 1234);
+        params.put("databaseServerId", "1234");
         params.put("databaseHistoryFileFilename", "/db_history_file_test");
 
         final String remaining = "test_name";
@@ -60,7 +60,7 @@ public class DebeziumMySqlComponentTest {
         assertEquals("dbz", configuration.getDatabaseUser());
         assertEquals("pwd", configuration.getDatabasePassword());
         assertEquals("test", configuration.getDatabaseServerName());
-        assertEquals(1234, configuration.getDatabaseServerId());
+        assertEquals(1234L, configuration.getDatabaseServerId());
         assertEquals("/db_history_file_test", configuration.getDatabaseHistoryFileFilename());
     }
 
diff --git a/components/camel-sql/src/test/java/org/apache/camel/component/sql/stored/ProducerBatchInvalidTest.java b/components/camel-sql/src/test/java/org/apache/camel/component/sql/stored/ProducerBatchInvalidTest.java
index 37fe02c..22bed0d 100644
--- a/components/camel-sql/src/test/java/org/apache/camel/component/sql/stored/ProducerBatchInvalidTest.java
+++ b/components/camel-sql/src/test/java/org/apache/camel/component/sql/stored/ProducerBatchInvalidTest.java
@@ -19,7 +19,6 @@ package org.apache.camel.component.sql.stored;
 import org.apache.camel.FailedToCreateRouteException;
 import org.apache.camel.PropertyBindingException;
 import org.apache.camel.ResolveEndpointFailedException;
-import org.apache.camel.TypeConversionException;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.test.junit4.CamelTestSupport;
 import org.junit.After;
@@ -72,7 +71,7 @@ public class ProducerBatchInvalidTest extends CamelTestSupport {
             ResolveEndpointFailedException refe = assertIsInstanceOf(ResolveEndpointFailedException.class, e.getCause());
             PropertyBindingException pbe = assertIsInstanceOf(PropertyBindingException.class, refe.getCause());
             assertEquals("batch", pbe.getPropertyName());
-            TypeConversionException tce = assertIsInstanceOf(TypeConversionException.class, pbe.getCause());
+            assertIsInstanceOf(IllegalArgumentException.class, pbe.getCause());
             assertEquals("[true, true]", pbe.getValue().toString());
         }
     }
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/AnnotationScanTypeConverters.java b/core/camel-api/src/main/java/org/apache/camel/spi/AnnotationScanTypeConverters.java
new file mode 100644
index 0000000..41dd5fa
--- /dev/null
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/AnnotationScanTypeConverters.java
@@ -0,0 +1,17 @@
+package org.apache.camel.spi;
+
+/**
+ * A {@link org.apache.camel.TypeConverter} which is capable of annotation scanning for {@link org.apache.camel.Converter}
+ * classes and add these as type converters.
+ * <p/>
+ * This is using Camel 2.x style and its recommended to migrate to @Converter(loader = true) for fast type converter mode.
+ */
+public interface AnnotationScanTypeConverters {
+
+    /**
+     * Scan for {@link org.apache.camel.Converter} classes and add those as type converters.
+     *
+     * @throws Exception is thrown if error happened
+     */
+    void scanTypeConverters() throws Exception;
+}
diff --git a/core/camel-base/src/main/java/org/apache/camel/converter/TimePatternConverter.java b/core/camel-base/src/main/java/org/apache/camel/converter/TimePatternConverter.java
index 4ccb021..c809fed 100644
--- a/core/camel-base/src/main/java/org/apache/camel/converter/TimePatternConverter.java
+++ b/core/camel-base/src/main/java/org/apache/camel/converter/TimePatternConverter.java
@@ -21,7 +21,6 @@ import org.apache.camel.util.TimeUtils;
 
 /**
  * Converter from String syntax to milli seconds.
- * Code is copied to org.apache.camel.catalog.TimePatternConverter in camel-catalog
  */
 @Converter(generateLoader = true)
 public final class TimePatternConverter {   
diff --git a/core/camel-base/src/main/java/org/apache/camel/impl/converter/BaseTypeConverterRegistry.java b/core/camel-base/src/main/java/org/apache/camel/impl/converter/BaseTypeConverterRegistry.java
index 6ef8380..0fbff03 100644
--- a/core/camel-base/src/main/java/org/apache/camel/impl/converter/BaseTypeConverterRegistry.java
+++ b/core/camel-base/src/main/java/org/apache/camel/impl/converter/BaseTypeConverterRegistry.java
@@ -48,6 +48,7 @@ import org.apache.camel.TypeConverterExists;
 import org.apache.camel.TypeConverterExistsException;
 import org.apache.camel.TypeConverterLoaderException;
 import org.apache.camel.TypeConverters;
+import org.apache.camel.converter.ObjectConverter;
 import org.apache.camel.spi.CamelLogger;
 import org.apache.camel.spi.FactoryFinder;
 import org.apache.camel.spi.Injector;
@@ -126,6 +127,46 @@ public abstract class BaseTypeConverterRegistry extends ServiceSupport implement
     @SuppressWarnings("unchecked")
     @Override
     public <T> T convertTo(Class<T> type, Exchange exchange, Object value) {
+        // optimize for a few common conversions
+        if (value != null) {
+            if (type.isInstance(value)) {
+                // same instance
+                return (T) value;
+            }
+            if (type == boolean.class) {
+                // primitive boolean which must return a value so throw exception if not possible
+                Object answer = ObjectConverter.toBoolean(value);
+                if (answer == null) {
+                    throw new IllegalArgumentException("Cannot convert type: " + value.getClass().getName() + " to boolean");
+                }
+                return (T) answer;
+            } else if (type == Boolean.class && (value instanceof String)) {
+                // String -> Boolean
+                String str = (String) value;
+                if ("true".equalsIgnoreCase(str)) {
+                    return (T) Boolean.TRUE;
+                } else if ("false".equalsIgnoreCase(str)) {
+                    return (T) Boolean.FALSE;
+                }
+            } else if (type.isPrimitive()) {
+                // okay its a wrapper -> primitive then return as-is for some common types
+                Class cls = value.getClass();
+                if (cls == Integer.class || cls == Long.class) {
+                    return (T) value;
+                }
+            } else if (type == String.class) {
+                // okay its a primitive -> string then return as-is for some common types
+                Class cls = value.getClass();
+                if (cls.isPrimitive()
+                        || cls == Boolean.class || cls == boolean.class
+                        || cls == Integer.class || cls == int.class
+                        || cls == Long.class || cls == long.class) {
+                    return (T) value.toString();
+                }
+            }
+            // NOTE: we cannot optimize any more if value is String as it may be time pattern and other patterns
+        }
+
         return (T) doConvertTo(type, exchange, value, false, false);
     }
 
@@ -137,6 +178,46 @@ public abstract class BaseTypeConverterRegistry extends ServiceSupport implement
     @SuppressWarnings("unchecked")
     @Override
     public <T> T mandatoryConvertTo(Class<T> type, Exchange exchange, Object value) throws NoTypeConversionAvailableException {
+        // optimize for a few common conversions
+        if (value != null) {
+            if (type.isInstance(value)) {
+                // same instance
+                return (T) value;
+            }
+            if (type == boolean.class) {
+                // primitive boolean which must return a value so throw exception if not possible
+                Object answer = ObjectConverter.toBoolean(value);
+                if (answer == null) {
+                    throw new IllegalArgumentException("Cannot convert type: " + value.getClass().getName() + " to boolean");
+                }
+                return (T) answer;
+            } else if (type == Boolean.class && (value instanceof String)) {
+                // String -> Boolean
+                String str = (String) value;
+                if ("true".equalsIgnoreCase(str)) {
+                    return (T) Boolean.TRUE;
+                } else if ("false".equalsIgnoreCase(str)) {
+                    return (T) Boolean.FALSE;
+                }
+            } else if (type.isPrimitive()) {
+                // okay its a wrapper -> primitive then return as-is for some common types
+                Class cls = value.getClass();
+                if (cls == Integer.class || cls == Long.class) {
+                    return (T) value;
+                }
+            } else if (type == String.class) {
+                // okay its a primitive -> string then return as-is for some common types
+                Class cls = value.getClass();
+                if (cls.isPrimitive()
+                        || cls == Boolean.class || cls == boolean.class
+                        || cls == Integer.class || cls == int.class
+                        || cls == Long.class || cls == long.class) {
+                    return (T) value.toString();
+                }
+            }
+            // NOTE: we cannot optimize any more if value is String as it may be time pattern and other patterns
+        }
+
         Object answer = doConvertTo(type, exchange, value, true, false);
         if (answer == null) {
             // Could not find suitable conversion
@@ -156,7 +237,8 @@ public abstract class BaseTypeConverterRegistry extends ServiceSupport implement
         return (T) doConvertTo(type, exchange, value, false, true);
     }
 
-    protected Object doConvertTo(final Class<?> type, final Exchange exchange, final Object value, final boolean mandatory, final boolean tryConvert) {
+    protected Object doConvertTo(final Class<?> type, final Exchange exchange, final Object value,
+                                 final boolean mandatory, final boolean tryConvert) {
         Object answer;
         try {
             answer = doConvertTo(type, exchange, value, tryConvert);
@@ -192,7 +274,8 @@ public abstract class BaseTypeConverterRegistry extends ServiceSupport implement
         }
     }
 
-    protected Object doConvertTo(final Class<?> type, final Exchange exchange, final Object value, final boolean tryConvert) throws Exception {
+    protected Object doConvertTo(final Class<?> type, final Exchange exchange, final Object value,
+                                 final boolean tryConvert) throws Exception {
         boolean trace = LOG.isTraceEnabled();
         boolean statisticsEnabled = statistics.isStatisticsEnabled();
 
@@ -411,7 +494,8 @@ public abstract class BaseTypeConverterRegistry extends ServiceSupport implement
         }
     }
 
-    private void addCoreFallbackTypeConverterToList(TypeConverter typeConverter, boolean canPromote, List<FallbackTypeConverter> converters) {
+    private void addCoreFallbackTypeConverterToList(TypeConverter typeConverter, boolean canPromote, List<
+            FallbackTypeConverter> converters) {
         LOG.trace("Adding core fallback type converter: {} which can promote: {}", typeConverter, canPromote);
 
         // add in top of fallback as the toString() fallback will nearly always be able to convert
@@ -496,7 +580,7 @@ public abstract class BaseTypeConverterRegistry extends ServiceSupport implement
                 TypeConverter converter = typeMappings.getFirst(
                         toType::isAssignableFrom,
                         // skip Object based we do them last
-                    from -> !from.equals(Object.class) && from.isAssignableFrom(fromType));
+                        from -> !from.equals(Object.class) && from.isAssignableFrom(fromType));
                 if (converter != null) {
                     return converter;
                 }
@@ -653,7 +737,8 @@ public abstract class BaseTypeConverterRegistry extends ServiceSupport implement
         }
     }
 
-    protected TypeConversionException createTypeConversionException(Exchange exchange, Class<?> type, Object value, Throwable cause) {
+    protected TypeConversionException createTypeConversionException(Exchange exchange, Class<?> type, Object
+            value, Throwable cause) {
         if (cause instanceof TypeConversionException) {
             if (((TypeConversionException) cause).getToType() == type) {
                 return (TypeConversionException) cause;
@@ -708,7 +793,6 @@ public abstract class BaseTypeConverterRegistry extends ServiceSupport implement
         if (resolver == null && camelContext != null) {
             resolver = camelContext.adapt(ExtendedCamelContext.class).getPackageScanClassResolver();
         }
-        initTypeConverterLoaders();
 
         List<FallbackTypeConverter> fallbacks = new ArrayList<>();
         // add to string first as it will then be last in the last as to string can nearly
@@ -728,12 +812,6 @@ public abstract class BaseTypeConverterRegistry extends ServiceSupport implement
         fallbackConverters.addAll(fallbacks);
     }
 
-    protected void initTypeConverterLoaders() {
-        if (resolver != null) {
-            typeConverterLoaders.add(new AnnotationTypeConverterLoader(resolver));
-        }
-    }
-
     @Override
     protected void doStart() throws Exception {
         // noop
diff --git a/core/camel-base/src/main/java/org/apache/camel/impl/converter/DefaultTypeConverter.java b/core/camel-base/src/main/java/org/apache/camel/impl/converter/DefaultTypeConverter.java
index e3f5f57..086af3c 100644
--- a/core/camel-base/src/main/java/org/apache/camel/impl/converter/DefaultTypeConverter.java
+++ b/core/camel-base/src/main/java/org/apache/camel/impl/converter/DefaultTypeConverter.java
@@ -17,6 +17,7 @@
 package org.apache.camel.impl.converter;
 
 import org.apache.camel.CamelContext;
+import org.apache.camel.spi.AnnotationScanTypeConverters;
 import org.apache.camel.spi.FactoryFinder;
 import org.apache.camel.spi.Injector;
 import org.apache.camel.spi.PackageScanClassResolver;
@@ -31,10 +32,11 @@ import org.slf4j.LoggerFactory;
  * <p/>
  * This implementation will load type converters up-front on startup.
  */
-public class DefaultTypeConverter extends BaseTypeConverterRegistry {
+public class DefaultTypeConverter extends BaseTypeConverterRegistry implements AnnotationScanTypeConverters {
 
     private static final Logger LOG = LoggerFactory.getLogger(DefaultTypeConverter.class);
 
+    private volatile boolean loadTypeConvertersDone;
     private final boolean loadTypeConverters;
 
     public DefaultTypeConverter(PackageScanClassResolver resolver, Injector injector,
@@ -73,8 +75,35 @@ public class DefaultTypeConverter extends BaseTypeConverterRegistry {
         // core type converters is always loaded which does not use any classpath scanning and therefore is fast
         loadCoreAndFastTypeConverters();
 
+        String time = TimeUtils.printDuration(watch.taken());
+        LOG.debug("Loaded {} type converters in {}", typeMappings.size(), time);
+
+        if (!loadTypeConvertersDone && isLoadTypeConverters()) {
+            scanTypeConverters();
+        }
+    }
+
+    private boolean isLoadTypeConverters() {
+        boolean load = loadTypeConverters;
+        if (camelContext != null) {
+            // camel context can override
+            load = camelContext.isLoadTypeConverters();
+        }
+        return load;
+    }
+
+    @Override
+    public void scanTypeConverters() throws Exception {
+        StopWatch watch = new StopWatch();
+
         // we are using backwards compatible legacy mode to detect additional converters
-        if (loadTypeConverters) {
+        if (!loadTypeConvertersDone) {
+            loadTypeConvertersDone = true;
+
+            if (resolver != null) {
+                typeConverterLoaders.add(new AnnotationTypeConverterLoader(resolver));
+            }
+
             int fast = typeMappings.size();
             // load type converters up front
             loadTypeConverters();
@@ -93,15 +122,6 @@ public class DefaultTypeConverter extends BaseTypeConverterRegistry {
         }
 
         String time = TimeUtils.printDuration(watch.taken());
-        LOG.debug("Loaded {} type converters in {}", typeMappings.size(), time);
+        LOG.debug("Scanned {} type converters in {}", typeMappings.size(), time);
     }
-
-    @Override
-    protected void initTypeConverterLoaders() {
-        // only use Camel 2.x annotation based loaders if enabled
-        if (resolver != null && loadTypeConverters)  {
-            typeConverterLoaders.add(new FastAnnotationTypeConverterLoader(resolver));
-        }
-    }
-
 }
diff --git a/core/camel-base/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java b/core/camel-base/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
index fe96711..3473ef4 100644
--- a/core/camel-base/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
+++ b/core/camel-base/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
@@ -80,6 +80,7 @@ import org.apache.camel.catalog.RuntimeCamelCatalog;
 import org.apache.camel.impl.transformer.TransformerKey;
 import org.apache.camel.impl.validator.ValidatorKey;
 import org.apache.camel.spi.AnnotationBasedProcessorFactory;
+import org.apache.camel.spi.AnnotationScanTypeConverters;
 import org.apache.camel.spi.AsyncProcessorAwaitManager;
 import org.apache.camel.spi.BeanIntrospection;
 import org.apache.camel.spi.BeanProcessorFactory;
@@ -2512,6 +2513,10 @@ public abstract class AbstractCamelContext extends ServiceSupport implements Ext
     }
 
     protected void doStartCamel() throws Exception {
+        // ensure additional type converters is loaded
+        if (loadTypeConverters && typeConverter instanceof AnnotationScanTypeConverters) {
+            ((AnnotationScanTypeConverters) typeConverter).scanTypeConverters();
+        }
 
         // custom properties may use property placeholders so resolve those
         // early on
diff --git a/core/camel-util/src/main/java/org/apache/camel/util/TimeUtils.java b/core/camel-util/src/main/java/org/apache/camel/util/TimeUtils.java
index d6edf36..2773cc3 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/TimeUtils.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/TimeUtils.java
@@ -99,7 +99,7 @@ public final class TimeUtils {
             }
         }
         if (digit) {
-            return Long.valueOf(source);
+            return Long.parseLong(source);
         }
 
         long milliseconds = 0;
@@ -112,17 +112,17 @@ public final class TimeUtils {
         if (matcher.find()) {
             // Note: This will also be used for regular numeric strings.
             //       This String -> long converter will be used for all strings.
-            milliseconds = Long.valueOf(source);
+            milliseconds = Long.parseLong(source);
         } else {
             matcher = createMatcher(HOUR_REGEX_PATTERN, source);
             if (matcher.find()) {
-                milliseconds = milliseconds + (3600000 * Long.valueOf(matcher.group(1)));
+                milliseconds = milliseconds + (3600000 * Long.parseLong(matcher.group(1)));
                 foundFlag = true;
             }
 
             matcher = createMatcher(MINUTES_REGEX_PATTERN, source);
             if (matcher.find()) {
-                long minutes = Long.valueOf(matcher.group(1));
+                long minutes = Long.parseLong(matcher.group(1));
                 if ((minutes > 59) && foundFlag) {
                     throw new IllegalArgumentException("Minutes should contain a valid value between 0 and 59: " + source);
                 }
@@ -132,7 +132,7 @@ public final class TimeUtils {
 
             matcher = createMatcher(SECONDS_REGEX_PATTERN, source);
             if (matcher.find()) {
-                long seconds = Long.valueOf(matcher.group(1));
+                long seconds = Long.parseLong(matcher.group(1));
                 if ((seconds > 59) && foundFlag) {
                     throw new IllegalArgumentException("Seconds should contain a valid value between 0 and 59: " + source);
                 }
@@ -143,7 +143,7 @@ public final class TimeUtils {
             // No pattern matched... initiating fallback check and conversion (if required).
             // The source at this point may contain illegal values or special characters
             if (!foundFlag) {
-                milliseconds = Long.valueOf(source);
+                milliseconds = Long.parseLong(source);
             }
         }