You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ja...@apache.org on 2022/10/17 06:17:43 UTC

[camel-quarkus] 01/02: Add a PackageScanClassResolver implementation that works in native mode

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

jamesnetherton pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git

commit c330078c9edad6bf173f30d22178768ed6370833
Author: James Netherton <ja...@gmail.com>
AuthorDate: Fri Oct 14 07:12:25 2022 +0100

    Add a PackageScanClassResolver implementation that works in native mode
    
    Fixes #4112
---
 .../core/deployment/CamelContextProcessor.java     |  24 +++--
 .../quarkus/core/deployment/CamelProcessor.java    |  17 ++++
 .../spi/CamelPackageScanClassBuildItem.java        |  40 ++++++++
 .../CamelPackageScanClassResolverBuildItem.java    |  36 +++++++
 .../CamelPackageScanClassResolverTest.java         | 112 +++++++++++++++++++++
 .../camel/quarkus/core/CamelContextRecorder.java   |   3 +
 .../core/CamelQuarkusPackageScanClassResolver.java |  50 +++++++++
 .../apache/camel/quarkus/core/CamelRecorder.java   |   6 ++
 .../salesforce/deployment/SalesforceProcessor.java |  11 +-
 .../component/salesforce/SalesforceTest.java       |   3 -
 10 files changed, 287 insertions(+), 15 deletions(-)

diff --git a/extensions-core/core/deployment/src/main/java/org/apache/camel/quarkus/core/deployment/CamelContextProcessor.java b/extensions-core/core/deployment/src/main/java/org/apache/camel/quarkus/core/deployment/CamelContextProcessor.java
index 9bcfc024c8..92fab75813 100644
--- a/extensions-core/core/deployment/src/main/java/org/apache/camel/quarkus/core/deployment/CamelContextProcessor.java
+++ b/extensions-core/core/deployment/src/main/java/org/apache/camel/quarkus/core/deployment/CamelContextProcessor.java
@@ -40,6 +40,7 @@ import org.apache.camel.quarkus.core.deployment.spi.CamelContextCustomizerBuildI
 import org.apache.camel.quarkus.core.deployment.spi.CamelFactoryFinderResolverBuildItem;
 import org.apache.camel.quarkus.core.deployment.spi.CamelModelJAXBContextFactoryBuildItem;
 import org.apache.camel.quarkus.core.deployment.spi.CamelModelToXMLDumperBuildItem;
+import org.apache.camel.quarkus.core.deployment.spi.CamelPackageScanClassResolverBuildItem;
 import org.apache.camel.quarkus.core.deployment.spi.CamelRegistryBuildItem;
 import org.apache.camel.quarkus.core.deployment.spi.CamelTypeConverterRegistryBuildItem;
 import org.apache.camel.quarkus.core.deployment.util.CamelSupport;
@@ -50,16 +51,17 @@ public class CamelContextProcessor {
     /**
      * This build step is responsible to assemble a {@link CamelContext} instance.
      *
-     * @param  beanContainer           a reference to a fully initialized CDI bean container
-     * @param  recorder                the recorder.
-     * @param  registry                a reference to a {@link org.apache.camel.spi.Registry}.
-     * @param  typeConverterRegistry   a reference to a {@link TypeConverterRegistry}.
-     * @param  modelJAXBContextFactory a list of known {@link ModelJAXBContextFactory}.
-     * @param  modelDumper             a list of known {@link CamelModelToXMLDumperBuildItem}.
-     * @param  factoryFinderResolver   a list of known {@link org.apache.camel.spi.FactoryFinderResolver}.
-     * @param  customizers             a list of {@link org.apache.camel.spi.CamelContextCustomizer} used to
-     *                                 customize the {@link CamelContext} at {@link ExecutionTime#STATIC_INIT}.
-     * @return                         a build item holding an instance of a {@link CamelContext}
+     * @param  beanContainer            a reference to a fully initialized CDI bean container
+     * @param  recorder                 the recorder.
+     * @param  registry                 a reference to a {@link org.apache.camel.spi.Registry}.
+     * @param  typeConverterRegistry    a reference to a {@link TypeConverterRegistry}.
+     * @param  modelJAXBContextFactory  a list of known {@link ModelJAXBContextFactory}.
+     * @param  modelDumper              a list of known {@link CamelModelToXMLDumperBuildItem}.
+     * @param  factoryFinderResolver    a list of known {@link org.apache.camel.spi.FactoryFinderResolver}.
+     * @param  customizers              a list of {@link org.apache.camel.spi.CamelContextCustomizer} used to
+     *                                  customize the {@link CamelContext} at {@link ExecutionTime#STATIC_INIT}.
+     * @param  packageScanClassResolver a reference to a {@link org.apache.camel.spi.PackageScanClassResolver}
+     * @return                          a build item holding an instance of a {@link CamelContext}
      */
     @Record(ExecutionTime.STATIC_INIT)
     @BuildStep
@@ -73,6 +75,7 @@ public class CamelContextProcessor {
             CamelFactoryFinderResolverBuildItem factoryFinderResolver,
             List<CamelContextCustomizerBuildItem> customizers,
             CamelComponentNameResolverBuildItem componentNameResolver,
+            CamelPackageScanClassResolverBuildItem packageScanClassResolver,
             CamelConfig config) {
 
         RuntimeValue<CamelContext> context = recorder.createContext(
@@ -82,6 +85,7 @@ public class CamelContextProcessor {
                 modelDumper.getValue(),
                 factoryFinderResolver.getFactoryFinderResolver(),
                 componentNameResolver.getComponentNameResolver(),
+                packageScanClassResolver.getPackageScanClassResolver(),
                 beanContainer.getValue(),
                 CamelSupport.getCamelVersion(),
                 config);
diff --git a/extensions-core/core/deployment/src/main/java/org/apache/camel/quarkus/core/deployment/CamelProcessor.java b/extensions-core/core/deployment/src/main/java/org/apache/camel/quarkus/core/deployment/CamelProcessor.java
index ab57746e1f..d8e06e3342 100644
--- a/extensions-core/core/deployment/src/main/java/org/apache/camel/quarkus/core/deployment/CamelProcessor.java
+++ b/extensions-core/core/deployment/src/main/java/org/apache/camel/quarkus/core/deployment/CamelProcessor.java
@@ -62,6 +62,8 @@ import org.apache.camel.quarkus.core.deployment.spi.CamelComponentNameResolverBu
 import org.apache.camel.quarkus.core.deployment.spi.CamelFactoryFinderResolverBuildItem;
 import org.apache.camel.quarkus.core.deployment.spi.CamelModelJAXBContextFactoryBuildItem;
 import org.apache.camel.quarkus.core.deployment.spi.CamelModelToXMLDumperBuildItem;
+import org.apache.camel.quarkus.core.deployment.spi.CamelPackageScanClassBuildItem;
+import org.apache.camel.quarkus.core.deployment.spi.CamelPackageScanClassResolverBuildItem;
 import org.apache.camel.quarkus.core.deployment.spi.CamelRoutesBuilderClassBuildItem;
 import org.apache.camel.quarkus.core.deployment.spi.CamelServiceBuildItem;
 import org.apache.camel.quarkus.core.deployment.spi.CamelServiceDestination;
@@ -423,6 +425,21 @@ class CamelProcessor {
         return new CamelComponentNameResolverBuildItem(recorder.createComponentNameResolver(componentNames));
     }
 
+    @Record(ExecutionTime.STATIC_INIT)
+    @BuildStep
+    CamelPackageScanClassResolverBuildItem packageScanClassResolver(
+            List<CamelPackageScanClassBuildItem> camelPackageScanClassBuildItems,
+            CamelRecorder recorder) {
+        Set<? extends Class<?>> packageScanClassCache = camelPackageScanClassBuildItems.stream()
+                .map(CamelPackageScanClassBuildItem::getClassNames)
+                .flatMap(Set::stream)
+                .map(className -> CamelSupport.loadClass(className, Thread.currentThread().getContextClassLoader()))
+                .collect(Collectors.toUnmodifiableSet());
+
+        return new CamelPackageScanClassResolverBuildItem(recorder.createPackageScanClassResolver(packageScanClassCache));
+
+    }
+
     @BuildStep
     NativeImageResourceBuildItem initResources() {
         return new NativeImageResourceBuildItem(
diff --git a/extensions-core/core/deployment/src/main/java/org/apache/camel/quarkus/core/deployment/spi/CamelPackageScanClassBuildItem.java b/extensions-core/core/deployment/src/main/java/org/apache/camel/quarkus/core/deployment/spi/CamelPackageScanClassBuildItem.java
new file mode 100644
index 0000000000..0039ae4db7
--- /dev/null
+++ b/extensions-core/core/deployment/src/main/java/org/apache/camel/quarkus/core/deployment/spi/CamelPackageScanClassBuildItem.java
@@ -0,0 +1,40 @@
+/*
+ * 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.quarkus.core.deployment.spi;
+
+import java.util.Set;
+
+import io.quarkus.builder.item.MultiBuildItem;
+
+/**
+ * A {@link MultiBuildItem} holding the names of a types that will be scanned by the Camel PackageScanClassResolver.
+ */
+public final class CamelPackageScanClassBuildItem extends MultiBuildItem {
+    private final Set<String> classNames;
+
+    public CamelPackageScanClassBuildItem(Set<String> classNames) {
+        this.classNames = classNames;
+    }
+
+    public CamelPackageScanClassBuildItem(String... classNames) {
+        this.classNames = Set.of(classNames);
+    }
+
+    public Set<String> getClassNames() {
+        return classNames;
+    }
+}
diff --git a/extensions-core/core/deployment/src/main/java/org/apache/camel/quarkus/core/deployment/spi/CamelPackageScanClassResolverBuildItem.java b/extensions-core/core/deployment/src/main/java/org/apache/camel/quarkus/core/deployment/spi/CamelPackageScanClassResolverBuildItem.java
new file mode 100644
index 0000000000..80cb2b3357
--- /dev/null
+++ b/extensions-core/core/deployment/src/main/java/org/apache/camel/quarkus/core/deployment/spi/CamelPackageScanClassResolverBuildItem.java
@@ -0,0 +1,36 @@
+/*
+ * 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.quarkus.core.deployment.spi;
+
+import io.quarkus.builder.item.SimpleBuildItem;
+import io.quarkus.runtime.RuntimeValue;
+import org.apache.camel.spi.PackageScanClassResolver;
+
+/**
+ * Holds the {@link PackageScanClassResolver} {@link RuntimeValue}.
+ */
+public final class CamelPackageScanClassResolverBuildItem extends SimpleBuildItem {
+    private final RuntimeValue<PackageScanClassResolver> value;
+
+    public CamelPackageScanClassResolverBuildItem(RuntimeValue<PackageScanClassResolver> value) {
+        this.value = value;
+    }
+
+    public RuntimeValue<PackageScanClassResolver> getPackageScanClassResolver() {
+        return value;
+    }
+}
diff --git a/extensions-core/core/deployment/src/test/java/org/apache/camel/quarkus/core/deployment/CamelPackageScanClassResolverTest.java b/extensions-core/core/deployment/src/test/java/org/apache/camel/quarkus/core/deployment/CamelPackageScanClassResolverTest.java
new file mode 100644
index 0000000000..25bc7c5b48
--- /dev/null
+++ b/extensions-core/core/deployment/src/test/java/org/apache/camel/quarkus/core/deployment/CamelPackageScanClassResolverTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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.quarkus.core.deployment;
+
+import java.util.Set;
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import io.quarkus.builder.BuildChainBuilder;
+import io.quarkus.builder.BuildContext;
+import io.quarkus.builder.BuildStep;
+import io.quarkus.test.QuarkusUnitTest;
+import org.apache.camel.CamelContext;
+import org.apache.camel.ExtendedCamelContext;
+import org.apache.camel.quarkus.core.deployment.spi.CamelPackageScanClassBuildItem;
+import org.apache.camel.spi.PackageScanClassResolver;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class CamelPackageScanClassResolverTest {
+
+    @RegisterExtension
+    static final QuarkusUnitTest CONFIG = new QuarkusUnitTest()
+            .addBuildChainCustomizer(new Consumer<>() {
+                @Override
+                public void accept(BuildChainBuilder buildChainBuilder) {
+                    buildChainBuilder.addBuildStep(new BuildStep() {
+                        @Override
+                        public void execute(BuildContext context) {
+                            context.produce(new CamelPackageScanClassBuildItem(Cat.class.getName()));
+                            context.produce(new CamelPackageScanClassBuildItem(Dog.class.getName()));
+                            context.produce(new CamelPackageScanClassBuildItem(Mushroom.class.getName()));
+                        }
+                    })
+                            .produces(CamelPackageScanClassBuildItem.class)
+                            .build();
+                }
+            })
+            .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class));
+
+    @Inject
+    CamelContext context;
+
+    @Test
+    public void findImplementations() {
+        ExtendedCamelContext ecc = context.adapt(ExtendedCamelContext.class);
+        PackageScanClassResolver resolver = ecc.getPackageScanClassResolver();
+        Set<Class<?>> classes = resolver.findImplementations(Animal.class, Animal.class.getPackageName());
+        assertNotNull(classes);
+        assertEquals(2, classes.size());
+        assertTrue(classes.contains(Cat.class));
+        assertTrue(classes.contains(Dog.class));
+    }
+
+    @Test
+    public void findByFilter() {
+        ExtendedCamelContext ecc = context.adapt(ExtendedCamelContext.class);
+        PackageScanClassResolver resolver = ecc.getPackageScanClassResolver();
+        Set<Class<?>> classes = resolver.findByFilter(Fungi.class::isAssignableFrom, Fungi.class.getPackageName());
+        assertNotNull(classes);
+        assertEquals(1, classes.size());
+        assertEquals(Mushroom.class, classes.iterator().next());
+    }
+
+    @Test
+    public void findAnnotated() {
+        ExtendedCamelContext ecc = context.adapt(ExtendedCamelContext.class);
+        PackageScanClassResolver resolver = ecc.getPackageScanClassResolver();
+        Set<Class<?>> classes = resolver.findAnnotated(Singleton.class, Animal.class.getPackageName());
+        assertNotNull(classes);
+        assertEquals(1, classes.size());
+        assertEquals(Cat.class, classes.iterator().next());
+    }
+
+    interface Animal {
+    }
+
+    interface Fungi {
+    }
+
+    @Singleton
+    static final class Cat implements Animal {
+    }
+
+    static final class Dog implements Animal {
+    }
+
+    static final class Mushroom implements Fungi {
+    }
+}
diff --git a/extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/core/CamelContextRecorder.java b/extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/core/CamelContextRecorder.java
index 989596519d..63487af35f 100644
--- a/extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/core/CamelContextRecorder.java
+++ b/extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/core/CamelContextRecorder.java
@@ -36,6 +36,7 @@ import org.apache.camel.spi.ComponentNameResolver;
 import org.apache.camel.spi.FactoryFinderResolver;
 import org.apache.camel.spi.ModelJAXBContextFactory;
 import org.apache.camel.spi.ModelToXMLDumper;
+import org.apache.camel.spi.PackageScanClassResolver;
 import org.apache.camel.spi.Registry;
 import org.apache.camel.spi.TypeConverterRegistry;
 
@@ -48,6 +49,7 @@ public class CamelContextRecorder {
             RuntimeValue<ModelToXMLDumper> xmlModelDumper,
             RuntimeValue<FactoryFinderResolver> factoryFinderResolver,
             RuntimeValue<ComponentNameResolver> componentNameResolver,
+            RuntimeValue<PackageScanClassResolver> packageScanClassResolver,
             BeanContainer beanContainer,
             String version,
             CamelConfig config) {
@@ -65,6 +67,7 @@ public class CamelContextRecorder {
         context.setTypeConverterRegistry(typeConverterRegistry.getValue());
         context.setLoadTypeConverters(false);
         context.setModelJAXBContextFactory(contextFactory.getValue());
+        context.setPackageScanClassResolver(packageScanClassResolver.getValue());
         context.build();
         context.setComponentNameResolver(componentNameResolver.getValue());
 
diff --git a/extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/core/CamelQuarkusPackageScanClassResolver.java b/extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/core/CamelQuarkusPackageScanClassResolver.java
new file mode 100644
index 0000000000..8d38e2c061
--- /dev/null
+++ b/extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/core/CamelQuarkusPackageScanClassResolver.java
@@ -0,0 +1,50 @@
+/*
+ * 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.quarkus.core;
+
+import java.util.Objects;
+import java.util.Set;
+
+import org.apache.camel.impl.engine.DefaultPackageScanClassResolver;
+import org.apache.camel.spi.PackageScanClassResolver;
+import org.apache.camel.spi.PackageScanFilter;
+
+/**
+ * Custom {@link PackageScanClassResolver} where classes / packages known to be scanned can be computed at build time
+ * and cached for resolution at runtime. This is primarily needed for native mode where the default implementation
+ * reliance on ClassLoader.getResources is likely to fail due to resources not being embedded within the native image.
+ */
+public class CamelQuarkusPackageScanClassResolver extends DefaultPackageScanClassResolver {
+    private final Set<? extends Class<?>> classCache;
+
+    public CamelQuarkusPackageScanClassResolver(Set<? extends Class<?>> classCache) {
+        this.classCache = Objects.requireNonNull(classCache);
+    }
+
+    @Override
+    protected void find(PackageScanFilter test, String packageName, ClassLoader loader, Set<Class<?>> classes) {
+        classCache.stream()
+                .filter(clazz -> clazz.getPackageName().replace('.', '/').equals(packageName))
+                .filter(test::matches)
+                .forEach(classes::add);
+
+        // Try to fallback on default package scanning
+        if (classes.isEmpty()) {
+            super.find(test, packageName, loader, classes);
+        }
+    }
+}
diff --git a/extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/core/CamelRecorder.java b/extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/core/CamelRecorder.java
index d1217cb673..bf5d342ab7 100644
--- a/extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/core/CamelRecorder.java
+++ b/extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/core/CamelRecorder.java
@@ -39,6 +39,7 @@ import org.apache.camel.spi.ComponentNameResolver;
 import org.apache.camel.spi.FactoryFinderResolver;
 import org.apache.camel.spi.ModelJAXBContextFactory;
 import org.apache.camel.spi.ModelToXMLDumper;
+import org.apache.camel.spi.PackageScanClassResolver;
 import org.apache.camel.spi.ReactiveExecutor;
 import org.apache.camel.spi.Registry;
 import org.apache.camel.spi.StartupStepRecorder;
@@ -202,4 +203,9 @@ public class CamelRecorder {
     public RuntimeValue<ComponentNameResolver> createComponentNameResolver(Set<String> componentNames) {
         return new RuntimeValue<>(new FastComponentNameResolver(componentNames));
     }
+
+    public RuntimeValue<PackageScanClassResolver> createPackageScanClassResolver(
+            Set<? extends Class<?>> packageScanClassCache) {
+        return new RuntimeValue<>(new CamelQuarkusPackageScanClassResolver(packageScanClassCache));
+    }
 }
diff --git a/extensions/salesforce/deployment/src/main/java/org/apache/camel/quarkus/component/salesforce/deployment/SalesforceProcessor.java b/extensions/salesforce/deployment/src/main/java/org/apache/camel/quarkus/component/salesforce/deployment/SalesforceProcessor.java
index 23cd4c90c3..f5dca11811 100644
--- a/extensions/salesforce/deployment/src/main/java/org/apache/camel/quarkus/component/salesforce/deployment/SalesforceProcessor.java
+++ b/extensions/salesforce/deployment/src/main/java/org/apache/camel/quarkus/component/salesforce/deployment/SalesforceProcessor.java
@@ -23,6 +23,7 @@ import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem;
 import io.quarkus.deployment.builditem.FeatureBuildItem;
 import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
 import org.apache.camel.component.salesforce.api.dto.AbstractDTOBase;
+import org.apache.camel.quarkus.core.deployment.spi.CamelPackageScanClassBuildItem;
 import org.apache.camel.support.jsse.KeyStoreParameters;
 import org.jboss.jandex.DotName;
 import org.jboss.jandex.IndexView;
@@ -44,7 +45,11 @@ class SalesforceProcessor {
     }
 
     @BuildStep
-    void registerForReflection(CombinedIndexBuildItem combinedIndex, BuildProducer<ReflectiveClassBuildItem> reflectiveClass) {
+    void registerForReflection(
+            CombinedIndexBuildItem combinedIndex,
+            BuildProducer<ReflectiveClassBuildItem> reflectiveClass,
+            BuildProducer<CamelPackageScanClassBuildItem> packageScanClass) {
+
         IndexView index = combinedIndex.getIndex();
 
         // NOTE: DTO classes are registered for reflection with fields and methods due to:
@@ -69,7 +74,9 @@ class SalesforceProcessor {
                 .toArray(String[]::new);
 
         reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, userDtoClasses));
-        // Register KeyStoreParameters for reflection
         reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, KeyStoreParameters.class));
+
+        // Ensure package scanning for user DTO classes can work in native mode
+        packageScanClass.produce(new CamelPackageScanClassBuildItem(userDtoClasses));
     }
 }
diff --git a/integration-tests/salesforce/src/test/java/org/apache/camel/quarkus/component/salesforce/SalesforceTest.java b/integration-tests/salesforce/src/test/java/org/apache/camel/quarkus/component/salesforce/SalesforceTest.java
index ee7e90132e..d103cf0461 100644
--- a/integration-tests/salesforce/src/test/java/org/apache/camel/quarkus/component/salesforce/SalesforceTest.java
+++ b/integration-tests/salesforce/src/test/java/org/apache/camel/quarkus/component/salesforce/SalesforceTest.java
@@ -27,7 +27,6 @@ import io.restassured.path.json.JsonPath;
 import org.apache.camel.component.salesforce.api.dto.RecentItem;
 import org.apache.camel.component.salesforce.api.dto.SObjectBasicInfo;
 import org.apache.camel.component.salesforce.api.dto.Versions;
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 
 import static org.hamcrest.Matchers.containsString;
@@ -53,7 +52,6 @@ class SalesforceTest {
     }
 
     @Test
-    @Disabled // https://github.com/apache/camel-quarkus/issues/4112
     public void testGetAccountDTO() {
         String accountId = null;
         String accountName = "Camel Quarkus Account Test: " + UUID.randomUUID();
@@ -186,7 +184,6 @@ class SalesforceTest {
     }
 
     @Test
-    @Disabled // https://github.com/apache/camel-quarkus/issues/4112
     void testAccountWithBasicInfo() {
         String accountId = null;
         String accountName = "Camel Quarkus Account Test: " + UUID.randomUUID();