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 4f1c8337a09 CAMEL-19898: camel-core - Optimize StreamCaching strategy to skip con… (#18191) 4f1c8337a09 is described below commit 4f1c8337a096fbb7f5d032cff273bda68d8b15bd Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Tue May 27 14:54:30 2025 +0200 CAMEL-19898: camel-core - Optimize StreamCaching strategy to skip con… (#18191) * CAMEL-19898: camel-core - Optimize StreamCaching strategy to skip conversion for well known types and do fast lookup of dedicated StreamCache converters, to bypass regular type converter. --- .../apache/camel/spi/TypeConverterRegistry.java | 13 +++-- .../impl/engine/DefaultStreamCachingStrategy.java | 60 +++++++++++++++++----- .../impl/converter/CoreTypeConverterRegistry.java | 13 +++++ ...TypeConverterRegistryStatisticsEnabledTest.java | 4 +- .../ManagedTypeConverterRegistryTest.java | 4 +- .../ROOT/pages/camel-4x-upgrade-guide-4_13.adoc | 5 ++ 6 files changed, 80 insertions(+), 19 deletions(-) diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/TypeConverterRegistry.java b/core/camel-api/src/main/java/org/apache/camel/spi/TypeConverterRegistry.java index 979a37acfe3..58667314a9a 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/TypeConverterRegistry.java +++ b/core/camel-api/src/main/java/org/apache/camel/spi/TypeConverterRegistry.java @@ -16,6 +16,7 @@ */ package org.apache.camel.spi; +import java.util.Map; import java.util.function.LongConsumer; import java.util.function.LongSupplier; @@ -125,6 +126,14 @@ public interface TypeConverterRegistry extends StaticService, CamelContextAware */ TypeConverter lookup(Class<?> toType, Class<?> fromType); + /** + * Lookup the type converters that can convert to a given type + * + * @param toType the type to convert to + * @return the type converters that can convert from + */ + Map<Class<?>, TypeConverter> lookup(Class<?> toType); + /** * Sets the injector to be used for creating new instances during type conversions. * @@ -189,8 +198,6 @@ public interface TypeConverterRegistry extends StaticService, CamelContextAware * @param typeConvertible A type convertible pair * @param typeConverter The type converter to associate with the type convertible pair */ - default void addConverter(TypeConvertible<?, ?> typeConvertible, TypeConverter typeConverter) { - - } + void addConverter(TypeConvertible<?, ?> typeConvertible, TypeConverter typeConverter); } diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultStreamCachingStrategy.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultStreamCachingStrategy.java index d22e9aa0712..c335a76eb5e 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultStreamCachingStrategy.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultStreamCachingStrategy.java @@ -33,6 +33,7 @@ import org.apache.camel.CamelContextAware; import org.apache.camel.Exchange; import org.apache.camel.Message; import org.apache.camel.StreamCache; +import org.apache.camel.TypeConverter; import org.apache.camel.spi.StreamCachingStrategy; import org.apache.camel.support.TempDirHelper; import org.apache.camel.support.service.ServiceSupport; @@ -46,12 +47,17 @@ import org.slf4j.LoggerFactory; */ public class DefaultStreamCachingStrategy extends ServiceSupport implements CamelContextAware, StreamCachingStrategy { + // stream cache type converters + private record CoreConverter(Class<?> from, TypeConverter converter) { + } + private static final Logger LOG = LoggerFactory.getLogger(DefaultStreamCachingStrategy.class); private CamelContext camelContext; private boolean enabled; private String allowClassNames; private String denyClassNames; + private final Collection<CoreConverter> coreConverters = new ArrayList<>(); private Collection<Class<?>> allowClasses; private Collection<Class<?>> denyClasses; private boolean spoolEnabled; @@ -269,15 +275,33 @@ public class DefaultStreamCachingStrategy extends ServiceSupport implements Came StreamCache cache = null; // try convert to stream cache if (body != null) { + // fast skip some common types that should never be converted + if (body instanceof String) { + return null; + } else if (body instanceof byte[]) { + return null; + } else if (body instanceof Boolean) { + return null; + } else if (body instanceof Number) { + return null; + } + Class<?> type = body.getClass(); + if (type.isPrimitive() || type.isEnum()) { + return null; + } + boolean allowed = allowClasses == null && denyClasses == null; if (!allowed) { - allowed = checkAllowDenyList(body); + allowed = checkAllowDenyList(type); } if (allowed) { - if (exchange != null) { - cache = camelContext.getTypeConverter().convertTo(StreamCache.class, exchange, body); - } else { - cache = camelContext.getTypeConverter().convertTo(StreamCache.class, body); + TypeConverter tc = lookupTypeConverter(type); + if (tc != null) { + if (exchange != null) { + cache = tc.convertTo(StreamCache.class, exchange, body); + } else { + cache = tc.convertTo(StreamCache.class, body); + } } } } @@ -292,22 +316,30 @@ public class DefaultStreamCachingStrategy extends ServiceSupport implements Came return cache; } - private boolean checkAllowDenyList(Object body) { + private TypeConverter lookupTypeConverter(Class<?> type) { + for (var tc : coreConverters) { + if (tc.from().isAssignableFrom(type)) { + return tc.converter(); + } + } + return null; + } + + private boolean checkAllowDenyList(Class<?> type) { boolean allowed; - Class<?> source = body.getClass(); if (denyClasses != null && allowClasses != null) { // deny takes precedence - allowed = !isAssignableFrom(source, denyClasses); + allowed = !isAssignableFrom(type, denyClasses); if (allowed) { - allowed = isAssignableFrom(source, allowClasses); + allowed = isAssignableFrom(type, allowClasses); } } else if (denyClasses != null) { - allowed = !isAssignableFrom(source, denyClasses); + allowed = !isAssignableFrom(type, denyClasses); } else { - allowed = isAssignableFrom(source, allowClasses); + allowed = isAssignableFrom(type, allowClasses); } if (LOG.isTraceEnabled()) { - LOG.trace("Cache stream from class: {} is {}", source, allowed ? "allowed" : "denied"); + LOG.trace("Cache stream from class: {} is {}", type, allowed ? "allowed" : "denied"); } return allowed; } @@ -340,6 +372,10 @@ public class DefaultStreamCachingStrategy extends ServiceSupport implements Came return; } + // find core type converters that can convert to StreamCache + var set = getCamelContext().getTypeConverterRegistry().lookup(StreamCache.class).entrySet(); + set.forEach(e -> coreConverters.add(new CoreConverter(e.getKey(), e.getValue()))); + if (allowClassNames != null) { if (allowClasses == null) { allowClasses = new ArrayList<>(); 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 e6cc5048980..47494e5efc3 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 @@ -16,6 +16,7 @@ */ package org.apache.camel.impl.converter; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -565,6 +566,18 @@ public abstract class CoreTypeConverterRegistry extends ServiceSupport implement return doLookup(toType, fromType); } + @Override + public Map<Class<?>, TypeConverter> lookup(Class<?> toType) { + Map<Class<?>, TypeConverter> answer = new LinkedHashMap<>(); + for (var e : converters.entrySet()) { + Class<?> target = e.getKey().getTo(); + if (target == toType) { + answer.put(e.getKey().getFrom(), e.getValue()); + } + } + return answer; + } + @Deprecated(since = "4.0.0") protected TypeConverter getOrFindTypeConverter(Class<?> toType, Class<?> fromType) { TypeConvertible<?, ?> typeConvertible = new TypeConvertible<>(fromType, toType); diff --git a/core/camel-core/src/test/java/org/apache/camel/impl/TypeConverterRegistryStatisticsEnabledTest.java b/core/camel-core/src/test/java/org/apache/camel/impl/TypeConverterRegistryStatisticsEnabledTest.java index 0a9dec58add..25a71a1b1fa 100644 --- a/core/camel-core/src/test/java/org/apache/camel/impl/TypeConverterRegistryStatisticsEnabledTest.java +++ b/core/camel-core/src/test/java/org/apache/camel/impl/TypeConverterRegistryStatisticsEnabledTest.java @@ -48,7 +48,7 @@ public class TypeConverterRegistryStatisticsEnabledTest extends ContextTestSuppo long failed = reg.getStatistics().getFailedCounter(); assertEquals(0, (int) failed); long miss = reg.getStatistics().getMissCounter(); - assertEquals(4, (int) miss); // stream caching misses + assertEquals(0, (int) miss); assertThrows(Exception.class, () -> template.sendBody("direct:start", "foo"), "Should have thrown exception"); @@ -57,7 +57,7 @@ public class TypeConverterRegistryStatisticsEnabledTest extends ContextTestSuppo failed = reg.getStatistics().getFailedCounter(); assertEquals(1, (int) failed); miss = reg.getStatistics().getMissCounter(); - assertEquals(5, (int) miss); // stream caching misses + assertEquals(0, (int) miss); // reset reg.getStatistics().reset(); diff --git a/core/camel-management/src/test/java/org/apache/camel/management/ManagedTypeConverterRegistryTest.java b/core/camel-management/src/test/java/org/apache/camel/management/ManagedTypeConverterRegistryTest.java index 5f91f325353..979cb676be0 100644 --- a/core/camel-management/src/test/java/org/apache/camel/management/ManagedTypeConverterRegistryTest.java +++ b/core/camel-management/src/test/java/org/apache/camel/management/ManagedTypeConverterRegistryTest.java @@ -80,7 +80,7 @@ public class ManagedTypeConverterRegistryTest extends ManagementTestSupport { Long failed = (Long) mbeanServer.getAttribute(name, "FailedCounter"); assertEquals(0, failed.intValue()); Long miss = (Long) mbeanServer.getAttribute(name, "MissCounter"); - assertEquals(2, miss.intValue()); // stream caching misses + assertEquals(0, miss.intValue()); // reset mbeanServer.invoke(name, "resetTypeConversionCounters", null, null); @@ -96,7 +96,7 @@ public class ManagedTypeConverterRegistryTest extends ManagementTestSupport { failed = (Long) mbeanServer.getAttribute(name, "FailedCounter"); assertEquals(1, failed.intValue()); miss = (Long) mbeanServer.getAttribute(name, "MissCounter"); - assertEquals(1, miss.intValue()); // stream caching misses + assertEquals(0, miss.intValue()); // reset mbeanServer.invoke(name, "resetTypeConversionCounters", null, null); diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_13.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_13.adoc index 0049ee16f5a..d417a21ba1e 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_13.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_13.adoc @@ -6,6 +6,11 @@ from both 4.0 to 4.1 and 4.1 to 4.2. == Upgrading Camel 4.12 to 4.13 +=== camel-core + +Added a 2nd `lookup` method to `org.apache.camel.spi.TypeConverterRegistry` and changed the `addConverter` to no longer have +an empty default noop implementation in the interface. + === camel-http Renamed class `org.apache.camel.component.http.BasicAuthenticationHttpClientConfigurer` to `org.apache.camel.component.http.DefaultAuthenticationHttpClientConfigurer`.