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

ggrzybek 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 74a60dc355f [CAMEL-18189] Read <spring:beans> from XML files 
(camel-xml-io) and populate Camel registry
74a60dc355f is described below

commit 74a60dc355f762b5b9a25c38a3229862d83da39a
Author: Grzegorz Grzybek <gr.grzy...@gmail.com>
AuthorDate: Tue Jun 6 13:54:37 2023 +0200

    [CAMEL-18189] Read <spring:beans> from XML files (camel-xml-io) and 
populate Camel registry
---
 .../apache/camel/model/app/BeansDefinition.java    |   3 +-
 .../org/apache/camel/main/BaseMainSupport.java     |  33 +++-
 .../org/apache/camel/main/RoutesConfigurer.java    |  10 +-
 .../apache/camel/xml/io/util/XmlStreamInfo.java    |   4 +
 .../java/org/apache/camel/xml/in/BaseParser.java   |   3 +-
 dsl/camel-kamelet-main/pom.xml                     |  25 +++
 .../java/org/apache/camel/main/KameletMain.java    | 129 +++++++++++--
 .../org/apache/camel/main/KameletMainTest.java     |  73 ++++++++
 .../test/java/org/apache/camel/main/app/Bean1.java |  31 ++++
 .../test/java/org/apache/camel/main/app/Bean2.java |  31 ++++
 .../java/org/apache/camel/main/app/Greeter.java    |  49 +++++
 .../org/apache/camel/main/app/GreeterMessage.java  |  31 ++++
 .../org/apache/camel/main/xml/spring-camel1.xml    |  57 ++++++
 .../camel/dsl/xml/io/XmlRoutesBuilderLoader.java   | 205 +++++++++++++++------
 .../apache/camel/dsl/xml/io/XmlLoadAppTest.java    |  13 +-
 15 files changed, 605 insertions(+), 92 deletions(-)

diff --git 
a/core/camel-core-model/src/main/java/org/apache/camel/model/app/BeansDefinition.java
 
b/core/camel-core-model/src/main/java/org/apache/camel/model/app/BeansDefinition.java
index fbb76e81f9d..d44e3e459dd 100644
--- 
a/core/camel-core-model/src/main/java/org/apache/camel/model/app/BeansDefinition.java
+++ 
b/core/camel-core-model/src/main/java/org/apache/camel/model/app/BeansDefinition.java
@@ -75,7 +75,8 @@ public class BeansDefinition {
 
     // this is the only way I found to generate usable Schema without imports, 
while allowing elements
     // from different namespaces
-    @ExternalSchemaElement(names = { "bean", "alias" }, namespace = 
"http://www.springframework.org/schema/beans";,
+    @ExternalSchemaElement(names = { "beans", "bean", "alias" },
+                           namespace = 
"http://www.springframework.org/schema/beans";,
                            documentElement = "beans")
     @XmlAnyElement
     private List<Element> springBeans = new ArrayList<>();
diff --git 
a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java 
b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
index 0c8d16c3c2a..e6a12db2d85 100644
--- a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
+++ b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
@@ -59,6 +59,7 @@ import org.apache.camel.spi.Language;
 import org.apache.camel.spi.PackageScanClassResolver;
 import org.apache.camel.spi.PeriodTaskScheduler;
 import org.apache.camel.spi.PropertiesComponent;
+import org.apache.camel.spi.Registry;
 import org.apache.camel.spi.RouteTemplateParameterSource;
 import org.apache.camel.spi.RoutesLoader;
 import org.apache.camel.spi.StartupStepRecorder;
@@ -443,10 +444,12 @@ public abstract class BaseMainSupport extends BaseService 
{
                         
mainConfigurationProperties.setRoutesIncludePattern(value);
                         return null;
                     });
-            // eager load properties from modeline by scanning DSL sources and 
gather properties for auto configuration
-            if (camelContext.isModeline() || 
mainConfigurationProperties.isModeline()) {
-                modelineRoutes(camelContext);
+            if (mainConfigurationProperties.isModeline()) {
+                camelContext.setModeline(true);
             }
+            // eager load properties from modeline by scanning DSL sources and 
gather properties for auto configuration
+            // also load other non-route related configuration (e.g., beans)
+            modelineRoutes(camelContext);
 
             autoConfigurationMainConfiguration(camelContext, 
mainConfigurationProperties, autoConfiguredProperties);
         }
@@ -885,6 +888,17 @@ public abstract class BaseMainSupport extends BaseService {
 
         // configure the common/default options
         DefaultConfigurationConfigurer.configure(camelContext, config);
+
+        // org.apache.camel.spring.boot.CamelAutoConfiguration 
(camel-spring-boot) also calls the methods
+        // on DefaultConfigurationConfigurer, but the CamelContext being 
configured is already
+        // org.apache.camel.spring.boot.SpringBootCamelContext, which has 
access to Spring's ApplicationContext.
+        // That's why DefaultConfigurationConfigurer.afterConfigure() can 
alter CamelContext using beans from
+        // Spring's ApplicationContext.
+        // so here, before configuring Camel Context, we can process the 
registry and let Main implementations
+        // decide how to do it
+        Registry registry = 
camelContext.getCamelContextExtension().getRegistry();
+        postProcessCamelRegistry(camelContext, config, registry);
+
         // lookup and configure SPI beans
         DefaultConfigurationConfigurer.afterConfigure(camelContext);
 
@@ -1132,6 +1146,19 @@ public abstract class BaseMainSupport extends 
BaseService {
         DefaultConfigurationConfigurer.afterPropertiesSet(camelContext);
     }
 
+    /**
+     * Main implementation may do some additional configuration of the {@link 
Registry} before it's used to
+     * (re)configure Camel context.
+     *
+     * @param camelContext
+     * @param config
+     * @param registry
+     */
+    protected void postProcessCamelRegistry(
+            CamelContext camelContext, MainConfigurationProperties config,
+            Registry registry) {
+    }
+
     private void setRouteTemplateProperties(
             CamelContext camelContext, OrderedLocationProperties 
routeTemplateProperties,
             OrderedLocationProperties autoConfiguredProperties)
diff --git 
a/core/camel-main/src/main/java/org/apache/camel/main/RoutesConfigurer.java 
b/core/camel-main/src/main/java/org/apache/camel/main/RoutesConfigurer.java
index ac2e1ff0a5f..36558c59395 100644
--- a/core/camel-main/src/main/java/org/apache/camel/main/RoutesConfigurer.java
+++ b/core/camel-main/src/main/java/org/apache/camel/main/RoutesConfigurer.java
@@ -302,12 +302,14 @@ public class RoutesConfigurer {
 
     protected void doConfigureModeline(CamelContext camelContext, 
Collection<Resource> resources, boolean optional)
             throws Exception {
-        ModelineFactory factory = 
PluginHelper.getModelineFactory(camelContext);
         RoutesLoader loader = PluginHelper.getRoutesLoader(camelContext);
 
-        for (Resource resource : resources) {
-            LOG.debug("Parsing modeline: {}", resource);
-            factory.parseModeline(resource);
+        if (camelContext.isModeline()) {
+            ModelineFactory factory = 
PluginHelper.getModelineFactory(camelContext);
+            for (Resource resource : resources) {
+                LOG.debug("Parsing modeline: {}", resource);
+                factory.parseModeline(resource);
+            }
         }
         // the resource may also have additional configurations which we need 
to detect via pre-parsing
         for (Resource resource : resources) {
diff --git 
a/core/camel-xml-io-util/src/main/java/org/apache/camel/xml/io/util/XmlStreamInfo.java
 
b/core/camel-xml-io-util/src/main/java/org/apache/camel/xml/io/util/XmlStreamInfo.java
index d6c8543fa44..0c2c9159295 100644
--- 
a/core/camel-xml-io-util/src/main/java/org/apache/camel/xml/io/util/XmlStreamInfo.java
+++ 
b/core/camel-xml-io-util/src/main/java/org/apache/camel/xml/io/util/XmlStreamInfo.java
@@ -58,6 +58,10 @@ public class XmlStreamInfo {
         return problem;
     }
 
+    public void setProblem(Throwable problem) {
+        this.problem = problem;
+    }
+
     public String getRootElementName() {
         return rootElementName;
     }
diff --git 
a/core/camel-xml-io/src/main/java/org/apache/camel/xml/in/BaseParser.java 
b/core/camel-xml-io/src/main/java/org/apache/camel/xml/in/BaseParser.java
index d0c4db88240..b4a0b9701b1 100644
--- a/core/camel-xml-io/src/main/java/org/apache/camel/xml/in/BaseParser.java
+++ b/core/camel-xml-io/src/main/java/org/apache/camel/xml/in/BaseParser.java
@@ -386,7 +386,8 @@ public class BaseParser {
 
     protected AttributeHandler<Element> domAttributeHandler() {
         return (el, name, value) -> {
-            el.setAttribute(name, value);
+            // for now, handle only XMLs where schema declares 
attributeFormDefault="unqualified"
+            el.setAttributeNS(null, name, value);
             return true;
         };
     }
diff --git a/dsl/camel-kamelet-main/pom.xml b/dsl/camel-kamelet-main/pom.xml
index d346887edb6..2cba8b8dbf4 100644
--- a/dsl/camel-kamelet-main/pom.xml
+++ b/dsl/camel-kamelet-main/pom.xml
@@ -125,6 +125,31 @@
             <artifactId>camel-groovy-dsl</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-xml-io</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-xml-io-dsl</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-direct</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-mock</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-rest</artifactId>
+            <scope>test</scope>
+        </dependency>
 
         <!-- Entire Maven downloading/resolution support is made using 
camel-tooling-maven -->
         <dependency>
diff --git 
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java 
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
index 1f320594f86..e72f650bb03 100644
--- 
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
+++ 
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
@@ -16,12 +16,20 @@
  */
 package org.apache.camel.main;
 
+import java.io.ByteArrayInputStream;
 import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.function.Supplier;
+
+import org.w3c.dom.Document;
 
 import org.apache.camel.CamelContext;
 import org.apache.camel.ManagementStatisticsLevel;
@@ -68,6 +76,10 @@ import org.apache.camel.support.DefaultContextReloadStrategy;
 import org.apache.camel.support.PluginHelper;
 import org.apache.camel.support.RouteOnDemandReloadStrategy;
 import org.apache.camel.support.service.ServiceHelper;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
+import org.springframework.core.io.AbstractResource;
 
 /**
  * A Main class for booting up Camel with Kamelet in standalone mode.
@@ -443,27 +455,31 @@ public class KameletMain extends MainCommandLineSupport {
 
             KnownDependenciesResolver known = new 
KnownDependenciesResolver(answer);
             known.loadKnownDependencies();
-            DependencyDownloaderPropertyBindingListener listener
-                    = new DependencyDownloaderPropertyBindingListener(answer, 
known);
-            answer.getCamelContextExtension().getRegistry()
-                    
.bind(DependencyDownloaderPropertyBindingListener.class.getSimpleName(), 
listener);
-            
answer.getCamelContextExtension().getRegistry().bind(DependencyDownloaderStrategy.class.getSimpleName(),
-                    new DependencyDownloaderStrategy(answer));
-            answer.setClassResolver(new 
DependencyDownloaderClassResolver(answer, known));
-            
answer.getCamelContextExtension().addContextPlugin(ComponentResolver.class,
-                    new DependencyDownloaderComponentResolver(answer, stub));
-            
answer.getCamelContextExtension().addContextPlugin(UriFactoryResolver.class,
-                    new DependencyDownloaderUriFactoryResolver(answer));
-            
answer.getCamelContextExtension().addContextPlugin(DataFormatResolver.class,
-                    new DependencyDownloaderDataFormatResolver(answer));
-            
answer.getCamelContextExtension().addContextPlugin(LanguageResolver.class,
-                    new DependencyDownloaderLanguageResolver(answer));
-            
answer.getCamelContextExtension().addContextPlugin(ResourceLoader.class,
-                    new DependencyDownloaderResourceLoader(answer));
+            if (download) {
+                DependencyDownloaderPropertyBindingListener listener
+                        = new 
DependencyDownloaderPropertyBindingListener(answer, known);
+                answer.getCamelContextExtension().getRegistry()
+                        
.bind(DependencyDownloaderPropertyBindingListener.class.getSimpleName(), 
listener);
+                
answer.getCamelContextExtension().getRegistry().bind(DependencyDownloaderStrategy.class.getSimpleName(),
+                        new DependencyDownloaderStrategy(answer));
+                answer.setClassResolver(new 
DependencyDownloaderClassResolver(answer, known));
+                
answer.getCamelContextExtension().addContextPlugin(ComponentResolver.class,
+                        new DependencyDownloaderComponentResolver(answer, 
stub));
+                
answer.getCamelContextExtension().addContextPlugin(UriFactoryResolver.class,
+                        new DependencyDownloaderUriFactoryResolver(answer));
+                
answer.getCamelContextExtension().addContextPlugin(DataFormatResolver.class,
+                        new DependencyDownloaderDataFormatResolver(answer));
+                
answer.getCamelContextExtension().addContextPlugin(LanguageResolver.class,
+                        new DependencyDownloaderLanguageResolver(answer));
+                
answer.getCamelContextExtension().addContextPlugin(ResourceLoader.class,
+                        new DependencyDownloaderResourceLoader(answer));
+            }
             answer.setInjector(new KameletMainInjector(answer.getInjector(), 
stub));
-            answer.addService(new DependencyDownloaderKamelet(answer));
-            
answer.getCamelContextExtension().getRegistry().bind(DownloadModelineParser.class.getSimpleName(),
-                    new DownloadModelineParser(answer));
+            if (download) {
+                answer.addService(new DependencyDownloaderKamelet(answer));
+                
answer.getCamelContextExtension().getRegistry().bind(DownloadModelineParser.class.getSimpleName(),
+                        new DownloadModelineParser(answer));
+            }
 
             // reloader
             String sourceDir = 
getInitialProperties().getProperty("camel.jbang.sourceDir");
@@ -577,6 +593,79 @@ public class KameletMain extends MainCommandLineSupport {
         return sb.toString();
     }
 
+    @Override
+    protected void postProcessCamelRegistry(CamelContext camelContext, 
MainConfigurationProperties config, Registry registry) {
+        // camel-kamelet-main has access to Spring libraries, so we can grab 
XML documents representing
+        // actual Spring Beans and read them using Spring's BeanFactory to 
populate Camel registry
+        final Map<String, Document> xmls = new TreeMap<>();
+
+        Map<String, Document> springBeansDocs = 
registry.findByTypeWithName(Document.class);
+        if (springBeansDocs != null) {
+            springBeansDocs.forEach((id, doc) -> {
+                if (id.startsWith("spring-document:")) {
+                    xmls.put(id, doc);
+                }
+            });
+        }
+
+        // we _could_ create something like 
org.apache.camel.spring.spi.ApplicationContextBeanRepository, but
+        // wrapping DefaultListableBeanFactory and use it as one of the
+        // org.apache.camel.support.DefaultRegistry.repositories, but for now 
let's use it to populate
+        // Spring registry and then copy the beans (whether the scope is)
+        final DefaultListableBeanFactory beanFactory = new 
DefaultListableBeanFactory();
+        beanFactory.setAllowCircularReferences(true); // for now
+
+        // register some existing beans (the list may change)
+        Set<String> infraBeanNames = Set.of("CamelContext", 
"MainConfiguration");
+        beanFactory.registerSingleton("CamelContext", camelContext);
+        beanFactory.registerSingleton("MainConfiguration", config);
+        // ...
+
+        // instead of generating an MX parser for spring-beans.xsd and use it 
to read the docs, we can simply
+        // pass w3c Documents directly to Spring
+        final XmlBeanDefinitionReader reader = new 
XmlBeanDefinitionReader(beanFactory);
+        xmls.forEach((id, doc) -> {
+            reader.registerBeanDefinitions(doc, new AbstractResource() {
+                @Override
+                public String getDescription() {
+                    return id;
+                }
+
+                @Override
+                public InputStream getInputStream() throws IOException {
+                    return new ByteArrayInputStream(new byte[0]);
+                }
+            });
+        });
+
+        // for full interaction between Spring ApplicationContext and its 
BeanFactory see
+        // 
org.springframework.context.support.AbstractApplicationContext.refresh()
+        // see 
org.springframework.context.support.AbstractApplicationContext.prepareBeanFactory()
 to check
+        // which extra/infra beans are added
+
+        beanFactory.freezeConfiguration();
+        beanFactory.preInstantiateSingletons();
+
+        for (String name : beanFactory.getBeanDefinitionNames()) {
+            if (infraBeanNames.contains(name)) {
+                continue;
+            }
+            BeanDefinition def = beanFactory.getBeanDefinition(name);
+            if (def.isSingleton()) {
+                // just grab the singleton and put into registry
+                registry.bind(name, beanFactory.getBean(name));
+            } else {
+                // rely on the bean factory to implement prototype scope
+                registry.bind(name, (Supplier<Object>) () -> 
beanFactory.getBean(name));
+            }
+        }
+    }
+
+    @Override
+    protected void doShutdown() throws Exception {
+        // TODO: manage BeanFactory as a field and clear the beans here
+    }
+
     private static String getPid() {
         return String.valueOf(ProcessHandle.current().pid());
     }
diff --git 
a/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/KameletMainTest.java
 
b/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/KameletMainTest.java
new file mode 100644
index 00000000000..33257f6c3f2
--- /dev/null
+++ 
b/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/KameletMainTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.main;
+
+import java.util.function.BiConsumer;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.main.app.Bean1;
+import org.apache.camel.main.app.Bean2;
+import org.apache.camel.support.ShortUuidGenerator;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+public class KameletMainTest {
+
+    @Test
+    public void testRouteWithSpringProcessor() throws Exception {
+        doTestMain("classpath:org/apache/camel/main/xml/spring-camel1.xml", 
(main, camelContext) -> {
+            try {
+                MockEndpoint endpoint = 
camelContext.getEndpoint("mock:finish", MockEndpoint.class);
+                endpoint.expectedBodiesReceived("Hello World (2147483647)");
+
+                main.getCamelTemplate().sendBody("direct:start", "I'm World");
+
+                endpoint.assertIsSatisfied();
+
+                assertTrue(camelContext.getUuidGenerator() instanceof 
ShortUuidGenerator);
+
+                Bean1 bean1 = main.lookupByType(Bean1.class).get("bean1");
+                Bean2 bean2 = bean1.getBean();
+                assertSame(bean1, bean2.getBean());
+            } catch (Exception e) {
+                fail(e.getMessage());
+            }
+        });
+    }
+
+    protected void doTestMain(String includes, BiConsumer<KameletMain, 
CamelContext> consumer) throws Exception {
+        KameletMain main = new KameletMain();
+
+        main.setDownload(false);
+        main.configure().withRoutesIncludePattern(includes);
+        main.configure().withAutoConfigurationEnabled(true);
+        main.start();
+
+        CamelContext camelContext = main.getCamelContext();
+        assertNotNull(camelContext);
+
+        consumer.accept(main, camelContext);
+
+        main.stop();
+    }
+
+}
diff --git 
a/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/app/Bean1.java 
b/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/app/Bean1.java
new file mode 100644
index 00000000000..ddcfda8d373
--- /dev/null
+++ b/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/app/Bean1.java
@@ -0,0 +1,31 @@
+/*
+ * 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.main.app;
+
+public class Bean1 {
+
+    private Bean2 bean;
+
+    public void setBean(Bean2 bean) {
+        this.bean = bean;
+    }
+
+    public Bean2 getBean() {
+        return bean;
+    }
+
+}
diff --git 
a/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/app/Bean2.java 
b/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/app/Bean2.java
new file mode 100644
index 00000000000..97845c9a4ca
--- /dev/null
+++ b/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/app/Bean2.java
@@ -0,0 +1,31 @@
+/*
+ * 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.main.app;
+
+public class Bean2 {
+
+    private Bean1 bean;
+
+    public void setBean(Bean1 bean) {
+        this.bean = bean;
+    }
+
+    public Bean1 getBean() {
+        return bean;
+    }
+
+}
diff --git 
a/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/app/Greeter.java 
b/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/app/Greeter.java
new file mode 100644
index 00000000000..1ec8ef91036
--- /dev/null
+++ 
b/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/app/Greeter.java
@@ -0,0 +1,49 @@
+/*
+ * 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.main.app;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.Processor;
+import org.apache.camel.util.StringHelper;
+
+public class Greeter implements Processor {
+
+    private Bean1 bean;
+
+    private GreeterMessage message;
+
+    private Integer number;
+
+    public void setMessage(GreeterMessage message) {
+        this.message = message;
+    }
+
+    public void setNumber(Integer number) {
+        this.number = number;
+    }
+
+    public void setBean(Bean1 bean) {
+        this.bean = bean;
+    }
+
+    @Override
+    public void process(Exchange exchange) throws Exception {
+        String msg = exchange.getIn().getBody(String.class);
+        exchange.getIn().setBody(message.getMsg() + " " + 
StringHelper.after(msg, "I'm ") + " (" + number + ")");
+    }
+
+}
diff --git 
a/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/app/GreeterMessage.java
 
b/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/app/GreeterMessage.java
new file mode 100644
index 00000000000..11d4e759967
--- /dev/null
+++ 
b/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/app/GreeterMessage.java
@@ -0,0 +1,31 @@
+/*
+ * 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.main.app;
+
+public class GreeterMessage {
+
+    private String msg;
+
+    public String getMsg() {
+        return msg;
+    }
+
+    public void setMsg(String msg) {
+        this.msg = msg;
+    }
+
+}
diff --git 
a/dsl/camel-kamelet-main/src/test/resources/org/apache/camel/main/xml/spring-camel1.xml
 
b/dsl/camel-kamelet-main/src/test/resources/org/apache/camel/main/xml/spring-camel1.xml
new file mode 100644
index 00000000000..887aca50431
--- /dev/null
+++ 
b/dsl/camel-kamelet-main/src/test/resources/org/apache/camel/main/xml/spring-camel1.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<camel>
+
+    <beans xmlns="http://www.springframework.org/schema/beans"; 
xmlns:util="http://www.springframework.org/schema/util";>
+
+        <util:constant id="max" static-field="java.lang.Integer.MAX_VALUE" />
+
+        <bean id="bean1" class="org.apache.camel.main.app.Bean1">
+            <property name="bean" ref="bean2"/>
+        </bean>
+        <bean id="bean2" class="org.apache.camel.main.app.Bean2">
+            <property name="bean" ref="bean1"/>
+        </bean>
+
+        <bean id="messageString" class="java.lang.String">
+            <constructor-arg index="0" value="Hello"/>
+        </bean>
+
+        <bean id="greeter" class="org.apache.camel.main.app.Greeter">
+            <description>Real Spring Bean</description>
+            <property name="bean" ref="bean1"/>
+            <property name="number" ref="max"/>
+            <property name="message">
+                <bean class="org.apache.camel.main.app.GreeterMessage">
+                    <property name="msg" ref="messageString"/>
+                </bean>
+            </property>
+        </bean>
+
+        <bean class="org.apache.camel.support.ShortUuidGenerator"/>
+    </beans>
+
+    <route id="r1">
+        <from uri="direct:start"/>
+        <bean ref="greeter"/>
+        <to uri="mock:finish"/>
+    </route>
+
+</camel>
diff --git 
a/dsl/camel-xml-io-dsl/src/main/java/org/apache/camel/dsl/xml/io/XmlRoutesBuilderLoader.java
 
b/dsl/camel-xml-io-dsl/src/main/java/org/apache/camel/dsl/xml/io/XmlRoutesBuilderLoader.java
index 1709a018e1d..b7731c717ed 100644
--- 
a/dsl/camel-xml-io-dsl/src/main/java/org/apache/camel/dsl/xml/io/XmlRoutesBuilderLoader.java
+++ 
b/dsl/camel-xml-io-dsl/src/main/java/org/apache/camel/dsl/xml/io/XmlRoutesBuilderLoader.java
@@ -16,9 +16,16 @@
  */
 package org.apache.camel.dsl.xml.io;
 
+import java.io.IOException;
 import java.util.ArrayList;
+import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.w3c.dom.Document;
 
 import org.apache.camel.BindToRegistry;
 import org.apache.camel.CamelContextAware;
@@ -60,6 +67,12 @@ public class XmlRoutesBuilderLoader extends 
RouteBuilderLoaderSupport {
     public static final String NAMESPACE = 
"http://camel.apache.org/schema/spring";;
     private static final List<String> NAMESPACES = List.of("", NAMESPACE);
 
+    private final Map<String, Resource> resourceCache = new 
ConcurrentHashMap<>();
+    private final Map<String, XmlStreamInfo> xmlInfoCache = new 
ConcurrentHashMap<>();
+    private final Map<String, BeansDefinition> camelAppCache = new 
ConcurrentHashMap<>();
+
+    private final AtomicInteger counter = new AtomicInteger(0);
+
     public XmlRoutesBuilderLoader() {
         super(EXTENSION);
     }
@@ -68,15 +81,28 @@ public class XmlRoutesBuilderLoader extends 
RouteBuilderLoaderSupport {
         super(extension);
     }
 
+    @Override
+    public void preParseRoute(Resource resource) throws Exception {
+        // preparsing is done at early stage, so we have a chance to load 
additional beans and populate
+        // Camel registry
+        XmlStreamInfo xmlInfo = xmlInfo(resource);
+        if (xmlInfo.isValid()) {
+            String root = xmlInfo.getRootElementName();
+            if ("beans".equals(root) || "camel".equals(root)) {
+                new ModelParser(resource, xmlInfo.getRootElementNamespace())
+                        .parseBeansDefinition()
+                        .ifPresent(bd -> {
+                            registerBeans(resource, bd);
+                            camelAppCache.put(resource.getLocation(), bd);
+                        });
+            }
+        }
+    }
+
     @Override
     public RouteBuilder doLoadRouteBuilder(Resource input) throws Exception {
-        final Resource resource = new CachedResource(input);
-        // instead of parsing the document NxM times (for each namespace x 
root element combination),
-        // we preparse it using XmlStreamDetector and then parse it fully 
knowing what's inside.
-        // we could even do better, by passing already preparsed information 
through config file, but
-        // it's getting complicated when using multiple files.
-        XmlStreamDetector detector = new 
XmlStreamDetector(resource.getInputStream());
-        XmlStreamInfo xmlInfo = detector.information();
+        final Resource resource = resource(input);
+        XmlStreamInfo xmlInfo = xmlInfo(input);
         if (!xmlInfo.isValid()) {
             // should be valid, because we checked it before
             LOG.warn("Invalid XML document: {}", 
xmlInfo.getProblem().getMessage());
@@ -86,11 +112,18 @@ public class XmlRoutesBuilderLoader extends 
RouteBuilderLoaderSupport {
         return new RouteConfigurationBuilder() {
             @Override
             public void configure() throws Exception {
+                String resourceLocation = input.getLocation();
                 switch (xmlInfo.getRootElementName()) {
-                    case "beans", "camel" ->
-                        new ModelParser(resource, 
xmlInfo.getRootElementNamespace())
-                                .parseBeansDefinition()
-                                .ifPresent(this::allInOne);
+                    case "beans", "camel" -> {
+                        BeansDefinition def = 
camelAppCache.get(resourceLocation);
+                        if (def != null) {
+                            configureCamel(def);
+                        } else {
+                            new ModelParser(resource, 
xmlInfo.getRootElementNamespace())
+                                    .parseBeansDefinition()
+                                    .ifPresent(this::configureCamel);
+                        }
+                    }
                     case "routeTemplate", "routeTemplates" ->
                         new ModelParser(resource, 
xmlInfo.getRootElementNamespace())
                                 .parseRouteTemplatesDefinition()
@@ -110,6 +143,12 @@ public class XmlRoutesBuilderLoader extends 
RouteBuilderLoaderSupport {
                     default -> {
                     }
                 }
+
+                // knowing this is the last time an XML may have been parsed, 
we can clear the cache
+                // (route may get reloaded later)
+                resourceCache.remove(resourceLocation);
+                xmlInfoCache.remove(resourceLocation);
+                camelAppCache.remove(resourceLocation);
             }
 
             @Override
@@ -124,55 +163,11 @@ public class XmlRoutesBuilderLoader extends 
RouteBuilderLoaderSupport {
                 }
             }
 
-            private void allInOne(BeansDefinition app) {
-                // selected elements which can be found in camel-spring-xml's 
<camelContext>
-
-                List<String> packagesToScan = new ArrayList<>();
-                app.getComponentScanning().forEach(cs -> {
-                    packagesToScan.add(cs.getBasePackage());
-                });
-                if (!packagesToScan.isEmpty()) {
-                    Registry registry = getCamelContext().getRegistry();
-                    if (registry != null) {
-                        PackageScanClassResolver scanner
-                                = 
getCamelContext().getCamelContextExtension().getContextPlugin(PackageScanClassResolver.class);
-                        Injector injector = getCamelContext().getInjector();
-                        if (scanner != null && injector != null) {
-                            for (String pkg : packagesToScan) {
-                                Set<Class<?>> classes = 
scanner.findAnnotated(BindToRegistry.class, pkg);
-                                for (Class<?> c : classes) {
-                                    // should:
-                                    // - call 
org.apache.camel.spi.CamelBeanPostProcessor.postProcessBeforeInitialization
-                                    // - call 
org.apache.camel.spi.CamelBeanPostProcessor.postProcessAfterInitialization
-                                    // - bind to registry if 
@org.apache.camel.BindToRegistry is present
-                                    injector.newInstance(c, true);
-                                }
-                            }
-                        }
-                    }
-                }
-
-                for (RegistryBeanDefinition bean : app.getBeans()) {
-                    String type = bean.getType();
-                    String name = bean.getName();
-                    if (name == null || "".equals(name.trim())) {
-                        name = type;
-                    }
-                    if (type != null && !type.startsWith("#")) {
-                        type = "#class:" + type;
-                        try {
-                            final Object target = 
PropertyBindingSupport.resolveBean(getCamelContext(), type);
-
-                            if (bean.getProperties() != null && 
!bean.getProperties().isEmpty()) {
-                                
PropertyBindingSupport.setPropertiesOnTarget(getCamelContext(), target, 
bean.getProperties());
-                            }
-                            getCamelContext().getRegistry().unbind(name);
-                            getCamelContext().getRegistry().bind(name, target);
-                        } catch (Exception e) {
-                            LOG.warn("Problem creating bean {}", type, e);
-                        }
-                    }
-                }
+            private void configureCamel(BeansDefinition app) {
+                // we have access to beans and spring beans, but these are 
already processed
+                // in preParseRoute() and possibly registered in
+                // 
org.apache.camel.main.BaseMainSupport.postProcessCamelRegistry() (if given Main 
implementation
+                // decides to do so)
 
                 app.getRests().forEach(r -> {
                     List<RestDefinition> list = new ArrayList<>();
@@ -236,4 +231,94 @@ public class XmlRoutesBuilderLoader extends 
RouteBuilderLoaderSupport {
             }
         };
     }
+
+    @Override
+    protected void doStop() throws Exception {
+        resourceCache.clear();
+        xmlInfoCache.clear();
+        camelAppCache.clear();
+    }
+
+    private Resource resource(Resource resource) {
+        return resourceCache.computeIfAbsent(resource.getLocation(), l -> new 
CachedResource(resource));
+    }
+
+    private XmlStreamInfo xmlInfo(Resource resource) {
+        return xmlInfoCache.computeIfAbsent(resource.getLocation(), l -> {
+            try {
+                // instead of parsing the document NxM times (for each 
namespace x root element combination),
+                // we preparse it using XmlStreamDetector and then parse it 
fully knowing what's inside.
+                // we could even do better, by passing already preparsed 
information through config file, but
+                // it's getting complicated when using multiple files.
+                XmlStreamDetector detector = new 
XmlStreamDetector(resource.getInputStream());
+                return detector.information();
+            } catch (IOException e) {
+                XmlStreamInfo invalid = new XmlStreamInfo();
+                invalid.setProblem(e);
+                return invalid;
+            }
+        });
+    }
+
+    private void registerBeans(Resource resource, BeansDefinition app) {
+        // <component-scan> - discover and register beans directly with Camel 
injection
+        Set<String> packagesToScan = new LinkedHashSet<>();
+        app.getComponentScanning().forEach(cs -> {
+            packagesToScan.add(cs.getBasePackage());
+        });
+        if (!packagesToScan.isEmpty()) {
+            Registry registry = getCamelContext().getRegistry();
+            if (registry != null) {
+                PackageScanClassResolver scanner
+                        = 
getCamelContext().getCamelContextExtension().getContextPlugin(PackageScanClassResolver.class);
+                Injector injector = getCamelContext().getInjector();
+                if (scanner != null && injector != null) {
+                    for (String pkg : packagesToScan) {
+                        Set<Class<?>> classes = 
scanner.findAnnotated(BindToRegistry.class, pkg);
+                        for (Class<?> c : classes) {
+                            // should:
+                            // - call 
org.apache.camel.spi.CamelBeanPostProcessor.postProcessBeforeInitialization
+                            // - call 
org.apache.camel.spi.CamelBeanPostProcessor.postProcessAfterInitialization
+                            // - bind to registry if 
@org.apache.camel.BindToRegistry is present
+                            injector.newInstance(c, true);
+                        }
+                    }
+                }
+            }
+        }
+
+        // <bean>s - register Camel beans directly with Camel injection
+        for (RegistryBeanDefinition bean : app.getBeans()) {
+            String type = bean.getType();
+            String name = bean.getName();
+            if (name == null || "".equals(name.trim())) {
+                name = type;
+            }
+            if (type != null && !type.startsWith("#")) {
+                type = "#class:" + type;
+                try {
+                    final Object target = 
PropertyBindingSupport.resolveBean(getCamelContext(), type);
+
+                    if (bean.getProperties() != null && 
!bean.getProperties().isEmpty()) {
+                        
PropertyBindingSupport.setPropertiesOnTarget(getCamelContext(), target, 
bean.getProperties());
+                    }
+                    getCamelContext().getRegistry().unbind(name);
+                    getCamelContext().getRegistry().bind(name, target);
+                } catch (Exception e) {
+                    LOG.warn("Problem creating bean {}", type, e);
+                }
+            }
+        }
+
+        // <s:bean>, <s:beans> and <s:alias> elements - all the elements in 
single BeansDefinition have
+        // one parent org.w3c.dom.Document - and this is what we collect from 
each resource
+        if (!app.getSpringBeans().isEmpty()) {
+            Document doc = app.getSpringBeans().get(0).getOwnerDocument();
+            // bind as Document, to be picked up later - bean id allows nice 
sorting
+            // (can also be single ID - documents will get collected in 
LinkedHashMap, so we'll be fine)
+            String id = String.format("spring-document:%05d:%s", 
counter.incrementAndGet(), resource.getLocation());
+            getCamelContext().getRegistry().bind(id, doc);
+        }
+    }
+
 }
diff --git 
a/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlLoadAppTest.java
 
b/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlLoadAppTest.java
index ddfe78459ea..867e086a69c 100644
--- 
a/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlLoadAppTest.java
+++ 
b/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlLoadAppTest.java
@@ -20,6 +20,7 @@ import org.apache.camel.component.mock.MockEndpoint;
 import org.apache.camel.dsl.xml.io.beans.GreeterMessage;
 import org.apache.camel.impl.DefaultCamelContext;
 import org.apache.camel.spi.Resource;
+import org.apache.camel.spi.RoutesLoader;
 import org.apache.camel.support.PluginHelper;
 import org.junit.jupiter.api.Test;
 
@@ -43,7 +44,9 @@ public class XmlLoadAppTest {
                 Resource resource = 
PluginHelper.getResourceLoader(context).resolveResource(
                         "/org/apache/camel/dsl/xml/io/" + r);
 
-                PluginHelper.getRoutesLoader(context).loadRoutes(resource);
+                RoutesLoader routesLoader = 
PluginHelper.getRoutesLoader(context);
+                routesLoader.preParseRoute(resource, false);
+                routesLoader.loadRoutes(resource);
             }
 
             assertNotNull(context.getRoute("r1"), "Loaded r1 route should be 
there");
@@ -77,7 +80,9 @@ public class XmlLoadAppTest {
             Resource resource = 
PluginHelper.getResourceLoader(context).resolveResource(
                     "/org/apache/camel/dsl/xml/io/camel-app3.xml");
 
-            PluginHelper.getRoutesLoader(context).loadRoutes(resource);
+            RoutesLoader routesLoader = PluginHelper.getRoutesLoader(context);
+            routesLoader.preParseRoute(resource, false);
+            routesLoader.loadRoutes(resource);
 
             assertNotNull(context.getRoute("r3"), "Loaded r3 route should be 
there");
             assertEquals(1, context.getRoutes().size());
@@ -101,7 +106,9 @@ public class XmlLoadAppTest {
             Resource resource = 
PluginHelper.getResourceLoader(context).resolveResource(
                     "/org/apache/camel/dsl/xml/io/camel-app4.xml");
 
-            PluginHelper.getRoutesLoader(context).loadRoutes(resource);
+            RoutesLoader routesLoader = PluginHelper.getRoutesLoader(context);
+            routesLoader.preParseRoute(resource, false);
+            routesLoader.loadRoutes(resource);
 
             assertNotNull(context.getRoute("r4"), "Loaded r4 route should be 
there");
             assertEquals(1, context.getRoutes().size());


Reply via email to