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"); + } + } + +}