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

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

commit 375cc4998910f6d5c5a5acfae08147508edfee49
Author: Claus Ibsen <claus.ib...@gmail.com>
AuthorDate: Thu Jan 23 11:05:15 2025 +0100

    CAMEL-21359: camel-spring-boot - Add support for MainListener
---
 .../xml/SpringBootXmlCamelContextConfigurer.java   |   5 +-
 .../camel/spring/boot/CamelAutoConfiguration.java  |  47 +++++--
 .../camel/spring/boot/CamelMainRunController.java  |  13 +-
 .../boot/CamelSpringBootApplicationController.java |  26 +++-
 .../camel/spring/boot/SpringBootCamelContext.java  |  32 ++++-
 .../camel/spring/boot/CamelMainListenerTest.java   | 137 +++++++++++++++++++++
 6 files changed, 244 insertions(+), 16 deletions(-)

diff --git 
a/core/camel-spring-boot-xml/src/main/java/org/apache/camel/spring/boot/xml/SpringBootXmlCamelContextConfigurer.java
 
b/core/camel-spring-boot-xml/src/main/java/org/apache/camel/spring/boot/xml/SpringBootXmlCamelContextConfigurer.java
index 1582e5617d2..412249a8c7d 100644
--- 
a/core/camel-spring-boot-xml/src/main/java/org/apache/camel/spring/boot/xml/SpringBootXmlCamelContextConfigurer.java
+++ 
b/core/camel-spring-boot-xml/src/main/java/org/apache/camel/spring/boot/xml/SpringBootXmlCamelContextConfigurer.java
@@ -22,6 +22,7 @@ import org.apache.camel.spi.Injector;
 import org.apache.camel.spring.SpringCamelContext;
 import org.apache.camel.spring.boot.CamelAutoConfiguration;
 import org.apache.camel.spring.boot.CamelConfigurationProperties;
+import org.apache.camel.spring.boot.CamelSpringBootApplicationController;
 import org.apache.camel.spring.xml.XmlCamelContextConfigurer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -39,13 +40,15 @@ public class SpringBootXmlCamelContextConfigurer implements 
XmlCamelContextConfi
     @Override
     public void configure(ApplicationContext applicationContext, 
SpringCamelContext camelContext) {
         CamelConfigurationProperties config = 
applicationContext.getBean(CamelConfigurationProperties.class);
+        CamelSpringBootApplicationController controller = new 
CamelSpringBootApplicationController(applicationContext);
+        controller.setCamelContext(camelContext);
         Injector injector = camelContext.getInjector();
         try {
             LOG.debug("Merging XML based CamelContext with Spring Boot 
configuration properties");
             // spring boot is not capable at this phase to use an injector 
that is creating beans
             // via spring-boot itself, so use a default injector instead
             camelContext.setInjector(new DefaultInjector(camelContext));
-            CamelAutoConfiguration.doConfigureCamelContext(applicationContext, 
camelContext, config);
+            CamelAutoConfiguration.doConfigureCamelContext(applicationContext, 
camelContext, config, controller);
         } catch (Exception e) {
             throw RuntimeCamelException.wrapRuntimeCamelException(e);
         } finally {
diff --git 
a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelAutoConfiguration.java
 
b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelAutoConfiguration.java
index 0df0385db1f..fca73c19c2a 100644
--- 
a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelAutoConfiguration.java
+++ 
b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelAutoConfiguration.java
@@ -24,6 +24,7 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
 import org.apache.camel.ConsumerTemplate;
 import org.apache.camel.ContextEvents;
 import org.apache.camel.FluentProducerTemplate;
@@ -32,7 +33,9 @@ import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.clock.Clock;
 import org.apache.camel.component.properties.PropertiesComponent;
 import org.apache.camel.component.properties.PropertiesParser;
+import org.apache.camel.main.BaseMainSupport;
 import org.apache.camel.main.DefaultConfigurationConfigurer;
+import org.apache.camel.main.MainListener;
 import org.apache.camel.main.RoutesCollector;
 import org.apache.camel.model.Model;
 import org.apache.camel.spi.BeanRepository;
@@ -101,30 +104,46 @@ public class CamelAutoConfiguration {
     @Bean(destroyMethod = "")
     @ConditionalOnMissingBean(CamelContext.class)
     CamelContext camelContext(ApplicationContext applicationContext, 
CamelConfigurationProperties config,
-                              CamelBeanPostProcessor beanPostProcessor, 
StartupConditionStrategy startup) throws Exception {
+                              CamelBeanPostProcessor beanPostProcessor, 
StartupConditionStrategy startup,
+                              CamelSpringBootApplicationController controller) 
throws Exception {
         Clock clock = new ResetableClock();
         CamelContext camelContext = new 
SpringBootCamelContext(applicationContext,
-                config.getSpringboot().isWarnOnEarlyShutdown());
+                config.getSpringboot().isWarnOnEarlyShutdown(), controller);
         camelContext.getClock().add(ContextEvents.BOOT, clock);
         // bean post processor is created before CamelContext
         beanPostProcessor.setCamelContext(camelContext);
         
camelContext.getCamelContextExtension().addContextPlugin(CamelBeanPostProcessor.class,
 beanPostProcessor);
         // startup condition is created before CamelContext
         
camelContext.getCamelContextExtension().addContextPlugin(StartupConditionStrategy.class,
 startup);
-        return doConfigureCamelContext(applicationContext, camelContext, 
config);
+        return doConfigureCamelContext(applicationContext, camelContext, 
config, controller);
     }
 
     /**
      * Not to be used by Camel end users
      */
     public static CamelContext doConfigureCamelContext(ApplicationContext 
applicationContext, CamelContext camelContext,
-                                                       
CamelConfigurationProperties config) throws Exception {
+                                                       
CamelConfigurationProperties config,
+                                                       
CamelSpringBootApplicationController controller) throws Exception {
+
+        // inject camel context on controller
+        CamelContextAware.trySetCamelContext(controller, camelContext);
 
         // setup startup recorder before building context
         configureStartupRecorder(camelContext, config);
 
         camelContext.build();
 
+        var listeners = controller.getMain().getMainListeners();
+        if (!listeners.isEmpty()) {
+            for (MainListener listener : listeners) {
+                listener.beforeInitialize(controller.getMain());
+            }
+            // allow doing custom configuration before camel is started
+            for (MainListener listener : listeners) {
+                listener.beforeConfigure(controller.getMain());
+            }
+        }
+
         // initialize properties component eager
         PropertiesComponent pc = 
applicationContext.getBeanProvider(PropertiesComponent.class).getIfAvailable();
         if (pc != null) {
@@ -205,6 +224,10 @@ public class CamelAutoConfiguration {
         // and call after all properties are set
         DefaultConfigurationConfigurer.afterPropertiesSet(camelContext);
 
+        for (MainListener listener : listeners) {
+            listener.afterConfigure(controller.getMain());
+        }
+
         return camelContext;
     }
 
@@ -265,10 +288,20 @@ public class CamelAutoConfiguration {
         }
     }
 
+    /**
+     * Create controller eager
+     */
     @Bean
-    CamelSpringBootApplicationController 
applicationController(ApplicationContext applicationContext,
-                                                               CamelContext 
camelContext) {
-        return new CamelSpringBootApplicationController(applicationContext, 
camelContext);
+    CamelSpringBootApplicationController 
applicationController(ApplicationContext applicationContext) {
+        CamelSpringBootApplicationController controller = new 
CamelSpringBootApplicationController(applicationContext);
+
+        // setup main listeners eager on controller
+        final Map<String, MainListener> listeners = 
applicationContext.getBeansOfType(MainListener.class);
+        for (MainListener listener : listeners.values()) {
+            controller.getMain().addMainListener(listener);
+        }
+
+        return controller;
     }
 
     @Bean
diff --git 
a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelMainRunController.java
 
b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelMainRunController.java
index 0d3096b2875..e146adfd2d0 100644
--- 
a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelMainRunController.java
+++ 
b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelMainRunController.java
@@ -17,9 +17,12 @@
 package org.apache.camel.spring.boot;
 
 import org.apache.camel.CamelContext;
+import org.apache.camel.main.MainListener;
 import org.apache.camel.main.MainShutdownStrategy;
 import org.springframework.context.ApplicationContext;
 
+import java.util.Map;
+
 /**
  * Controller to keep the main running and perform graceful shutdown when the 
JVM is stopped.
  */
@@ -29,7 +32,15 @@ public class CamelMainRunController {
     private final Thread daemon;
 
     public CamelMainRunController(ApplicationContext applicationContext, 
CamelContext camelContext) {
-        controller = new 
CamelSpringBootApplicationController(applicationContext, camelContext);
+        controller = new 
CamelSpringBootApplicationController(applicationContext);
+        controller.setCamelContext(camelContext);
+
+        // setup main listeners eager on controller
+        final Map<String, MainListener> listeners = 
applicationContext.getBeansOfType(MainListener.class);
+        for (MainListener listener : listeners.values()) {
+            controller.getMain().addMainListener(listener);
+        }
+
         daemon = new Thread(new DaemonTask(), "CamelMainRunController");
     }
 
diff --git 
a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelSpringBootApplicationController.java
 
b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelSpringBootApplicationController.java
index cf76e97c79d..2ce9833a429 100644
--- 
a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelSpringBootApplicationController.java
+++ 
b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelSpringBootApplicationController.java
@@ -18,6 +18,7 @@ package org.apache.camel.spring.boot;
 
 import jakarta.annotation.PreDestroy;
 import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
 import org.apache.camel.ProducerTemplate;
 import org.apache.camel.main.Main;
 import org.apache.camel.main.MainShutdownStrategy;
@@ -26,14 +27,28 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.context.ApplicationContext;
 
-public class CamelSpringBootApplicationController {
+public class CamelSpringBootApplicationController implements CamelContextAware 
{
     private static final Logger LOG = 
LoggerFactory.getLogger(CamelSpringBootApplicationController.class);
 
     private final Main main;
+    private CamelContext camelContext;
 
-    public CamelSpringBootApplicationController(final ApplicationContext 
applicationContext,
-            final CamelContext context) {
-        this.main = new CamelSpringMain(applicationContext, context);
+    public CamelSpringBootApplicationController(final ApplicationContext 
applicationContext) {
+        this.main = new CamelSpringMain(applicationContext);
+    }
+
+    public Main getMain() {
+        return main;
+    }
+
+    @Override
+    public CamelContext getCamelContext() {
+        return camelContext;
+    }
+
+    @Override
+    public void setCamelContext(CamelContext camelContext) {
+        this.camelContext = camelContext;
     }
 
     public MainShutdownStrategy getMainShutdownStrategy() {
@@ -78,9 +93,8 @@ public class CamelSpringBootApplicationController {
     private static class CamelSpringMain extends Main {
         final ApplicationContext applicationContext;
 
-        public CamelSpringMain(ApplicationContext applicationContext, 
CamelContext camelContext) {
+        public CamelSpringMain(ApplicationContext applicationContext) {
             this.applicationContext = applicationContext;
-            this.camelContext = camelContext;
 
             // use a simple shutdown strategy that does not install any 
shutdown hook as spring-boot
             // as spring-boot has its own hook we use
diff --git 
a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/SpringBootCamelContext.java
 
b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/SpringBootCamelContext.java
index 9ce7cc3983d..fc66552fbac 100644
--- 
a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/SpringBootCamelContext.java
+++ 
b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/SpringBootCamelContext.java
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.spring.boot;
 
+import org.apache.camel.main.MainListener;
 import org.apache.camel.spring.SpringCamelContext;
 import org.apache.camel.util.StopWatch;
 import org.slf4j.Logger;
@@ -31,20 +32,43 @@ public class SpringBootCamelContext extends 
SpringCamelContext {
 
     private final StopWatch stopWatch = new StopWatch();
     private final boolean warnOnEarlyShutdown;
+    private final CamelSpringBootApplicationController controller;
 
-    public SpringBootCamelContext(ApplicationContext applicationContext, 
boolean warnOnEarlyShutdown) {
+    public SpringBootCamelContext(ApplicationContext applicationContext, 
boolean warnOnEarlyShutdown,
+                                  CamelSpringBootApplicationController 
controller) {
         super(applicationContext);
         this.warnOnEarlyShutdown = warnOnEarlyShutdown;
+        this.controller = controller;
     }
 
     @Override
     protected void doStart() throws Exception {
+        var listeners = controller.getMain().getMainListeners();
+        if (!listeners.isEmpty()) {
+            for (MainListener listener : listeners) {
+                listener.beforeStart(controller.getMain());
+            }
+        }
+
         stopWatch.restart();
         super.doStart();
+
+        if (!listeners.isEmpty()) {
+            for (MainListener listener : listeners) {
+                listener.afterStart(controller.getMain());
+            }
+        }
     }
 
     @Override
     protected synchronized void doStop() throws Exception {
+        var listeners = controller.getMain().getMainListeners();
+        if (!listeners.isEmpty()) {
+            for (MainListener listener : listeners) {
+                listener.beforeStop(controller.getMain());
+            }
+        }
+
         // if we are stopping very quickly then its likely because the user 
may not have either spring-boot-web
         // or enabled Camel's main controller, so lets log a WARN about this.
         long taken = stopWatch.taken();
@@ -60,5 +84,11 @@ public class SpringBootCamelContext extends 
SpringCamelContext {
             }
         }
         super.doStop();
+
+        if (!listeners.isEmpty()) {
+            for (MainListener listener : listeners) {
+                listener.afterStop(controller.getMain());
+            }
+        }
     }
 }
diff --git 
a/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/CamelMainListenerTest.java
 
b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/CamelMainListenerTest.java
new file mode 100644
index 00000000000..d4a2932edb0
--- /dev/null
+++ 
b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/CamelMainListenerTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.spring.boot;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.ProducerTemplate;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.main.BaseMainSupport;
+import org.apache.camel.main.MainListener;
+import org.apache.camel.main.MainListenerSupport;
+import org.apache.camel.test.spring.junit5.CamelSpringBootTest;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.annotation.DirtiesContext;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@DirtiesContext
+@CamelSpringBootTest
+@EnableAutoConfiguration
+@SpringBootTest
+public class CamelMainListenerTest {
+
+    private static final MyMainListener MY_MAIN_LISTENER = new 
MyMainListener();
+
+    @Configuration
+    static class Config {
+
+        @Bean
+        RouteBuilder routeBuilder() {
+            return new RouteBuilder() {
+                @Override
+                public void configure() throws Exception {
+                    from("direct:start").to("mock:result");
+                }
+            };
+        }
+
+        @Bean
+        public MainListener myMainListener() {
+            return MY_MAIN_LISTENER;
+        }
+
+    }
+
+    @Autowired
+    CamelContext camelContext;
+
+    @Autowired
+    ProducerTemplate producerTemplate;
+
+    @Test
+    public void testMainListener() throws InterruptedException {
+        MockEndpoint mockEndpoint = camelContext.getEndpoint("mock:result", 
MockEndpoint.class);
+        mockEndpoint.expectedMessageCount(1);
+
+        producerTemplate.sendBody("direct:start", "Hello World");
+
+        mockEndpoint.assertIsSatisfied();
+
+        Assertions.assertEquals(5, MY_MAIN_LISTENER.order.size());
+        Assertions.assertEquals("beforeInitialize", 
MY_MAIN_LISTENER.order.get(0));
+        Assertions.assertEquals("beforeConfigure", 
MY_MAIN_LISTENER.order.get(1));
+        Assertions.assertEquals("afterConfigure", 
MY_MAIN_LISTENER.order.get(2));
+        Assertions.assertEquals("beforeStart", MY_MAIN_LISTENER.order.get(3));
+        Assertions.assertEquals("afterStart", MY_MAIN_LISTENER.order.get(4));
+
+        // stop should trigger more listeners
+        camelContext.stop();
+
+        Assertions.assertEquals(7, MY_MAIN_LISTENER.order.size());
+        Assertions.assertEquals("beforeStop", MY_MAIN_LISTENER.order.get(5));
+        Assertions.assertEquals("afterStop", MY_MAIN_LISTENER.order.get(6));
+    }
+
+    public static class MyMainListener extends MainListenerSupport {
+
+        private final List<String> order = new ArrayList<>();
+
+        @Override
+        public void beforeInitialize(BaseMainSupport main) {
+            order.add("beforeInitialize");
+        }
+
+        @Override
+        public void beforeConfigure(BaseMainSupport main) {
+            order.add("beforeConfigure");
+        }
+
+        @Override
+        public void afterConfigure(BaseMainSupport main) {
+            order.add("afterConfigure");
+        }
+
+        @Override
+        public void beforeStart(BaseMainSupport main) {
+            order.add("beforeStart");
+        }
+
+        @Override
+        public void afterStart(BaseMainSupport main) {
+            order.add("afterStart");
+        }
+
+        @Override
+        public void beforeStop(BaseMainSupport main) {
+            order.add("beforeStop");
+        }
+
+        @Override
+        public void afterStop(BaseMainSupport main) {
+            order.add("afterStop");
+        }
+    }
+
+}

Reply via email to