This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-spring-boot.git
The following commit(s) were added to refs/heads/main by this push: new ab83d59c377 CAMEL-21353: camel-core - Add possibility to set some condition for Camel to wait during startup before continuing ab83d59c377 is described below commit ab83d59c377abdf977a03b0944e408837c4af758 Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Sat Oct 19 11:19:04 2024 +0200 CAMEL-21353: camel-core - Add possibility to set some condition for Camel to wait during startup before continuing --- .../src/main/docs/spring-boot.json | 51 +++++++++ .../camel/spring/boot/CamelAutoConfiguration.java | 42 ++++++-- ...melStartupConditionConfigurationProperties.java | 119 +++++++++++++++++++++ .../spring/boot/CamelStartupConditionEnvTest.java | 90 ++++++++++++++++ 4 files changed, 295 insertions(+), 7 deletions(-) diff --git a/core/camel-spring-boot/src/main/docs/spring-boot.json b/core/camel-spring-boot/src/main/docs/spring-boot.json index 0150e215c0c..b357c22774f 100644 --- a/core/camel-spring-boot/src/main/docs/spring-boot.json +++ b/core/camel-spring-boot/src/main/docs/spring-boot.json @@ -121,6 +121,11 @@ "type": "org.apache.camel.spring.boot.security.CamelSSLConfigurationProperties", "sourceType": "org.apache.camel.spring.boot.security.CamelSSLConfigurationProperties" }, + { + "name": "camel.startupcondition", + "type": "org.apache.camel.spring.boot.CamelStartupConditionConfigurationProperties", + "sourceType": "org.apache.camel.spring.boot.CamelStartupConditionConfigurationProperties" + }, { "name": "camel.threadpool", "type": "org.apache.camel.spring.boot.threadpool.CamelThreadPoolConfigurationProperties", @@ -1439,6 +1444,52 @@ "description": "The optional trust manager configuration for creating the TrustManager used in constructing an SSLContext.", "sourceType": "org.apache.camel.spring.boot.security.CamelSSLConfigurationProperties" }, + { + "name": "camel.startupcondition.custom-class-names", + "type": "java.lang.String", + "description": "A list of custom class names (FQN). Multiple classes can be separated by comma.", + "sourceType": "org.apache.camel.spring.boot.CamelStartupConditionConfigurationProperties" + }, + { + "name": "camel.startupcondition.enabled", + "type": "java.lang.Boolean", + "description": "To enable using startup conditions", + "sourceType": "org.apache.camel.spring.boot.CamelStartupConditionConfigurationProperties", + "defaultValue": false + }, + { + "name": "camel.startupcondition.environment-variable-exists", + "type": "java.lang.String", + "description": "Wait for an environment variable with the given name to exists before continuing", + "sourceType": "org.apache.camel.spring.boot.CamelStartupConditionConfigurationProperties" + }, + { + "name": "camel.startupcondition.file-exists", + "type": "java.lang.String", + "description": "Wait for a file with the given name to exists before continuing", + "sourceType": "org.apache.camel.spring.boot.CamelStartupConditionConfigurationProperties" + }, + { + "name": "camel.startupcondition.interval", + "type": "java.lang.Integer", + "description": "Interval in millis between checking conditions.", + "sourceType": "org.apache.camel.spring.boot.CamelStartupConditionConfigurationProperties", + "defaultValue": 500 + }, + { + "name": "camel.startupcondition.on-timeout", + "type": "java.lang.String", + "description": "What action, to do on timeout. fail = do not startup, and throw an exception causing camel to fail stop = do not startup, and stop camel ignore = log a WARN and continue to startup", + "sourceType": "org.apache.camel.spring.boot.CamelStartupConditionConfigurationProperties", + "defaultValue": "stop" + }, + { + "name": "camel.startupcondition.timeout", + "type": "java.lang.Integer", + "description": "Total timeout (in millis) for all startup conditions.", + "sourceType": "org.apache.camel.spring.boot.CamelStartupConditionConfigurationProperties", + "defaultValue": 20000 + }, { "name": "camel.threadpool.allow-core-thread-time-out", "type": "java.lang.Boolean", 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 9765035b30c..0df0385db1f 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 @@ -40,6 +40,7 @@ import org.apache.camel.spi.CliConnector; import org.apache.camel.spi.CliConnectorFactory; import org.apache.camel.spi.PackageScanClassResolver; import org.apache.camel.spi.PackageScanResourceResolver; +import org.apache.camel.spi.StartupConditionStrategy; import org.apache.camel.spi.StartupStepRecorder; import org.apache.camel.spi.VariableRepository; import org.apache.camel.spi.VariableRepositoryFactory; @@ -51,6 +52,9 @@ import org.apache.camel.support.LanguageSupport; import org.apache.camel.support.ResetableClock; import org.apache.camel.support.ResourceHelper; import org.apache.camel.support.service.ServiceHelper; +import org.apache.camel.support.startup.DefaultStartupConditionStrategy; +import org.apache.camel.support.startup.EnvStartupCondition; +import org.apache.camel.support.startup.FileStartupCondition; import org.apache.camel.support.startup.LoggingStartupStepRecorder; import org.apache.camel.util.IOHelper; import org.apache.camel.util.ObjectHelper; @@ -77,7 +81,7 @@ import org.springframework.core.env.MutablePropertySources; @ImportRuntimeHints(CamelRuntimeHints.class) @Configuration(proxyBeanMethods = false) -@EnableConfigurationProperties({ CamelConfigurationProperties.class }) +@EnableConfigurationProperties({CamelConfigurationProperties.class, CamelStartupConditionConfigurationProperties.class}) @Import(TypeConversionConfiguration.class) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public class CamelAutoConfiguration { @@ -97,7 +101,7 @@ public class CamelAutoConfiguration { @Bean(destroyMethod = "") @ConditionalOnMissingBean(CamelContext.class) CamelContext camelContext(ApplicationContext applicationContext, CamelConfigurationProperties config, - CamelBeanPostProcessor beanPostProcessor) throws Exception { + CamelBeanPostProcessor beanPostProcessor, StartupConditionStrategy startup) throws Exception { Clock clock = new ResetableClock(); CamelContext camelContext = new SpringBootCamelContext(applicationContext, config.getSpringboot().isWarnOnEarlyShutdown()); @@ -105,6 +109,8 @@ public class CamelAutoConfiguration { // 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); } @@ -112,7 +118,7 @@ public class CamelAutoConfiguration { * Not to be used by Camel end users */ public static CamelContext doConfigureCamelContext(ApplicationContext applicationContext, CamelContext camelContext, - CamelConfigurationProperties config) throws Exception { + CamelConfigurationProperties config) throws Exception { // setup startup recorder before building context configureStartupRecorder(camelContext, config); @@ -185,7 +191,7 @@ public class CamelAutoConfiguration { new FatJarPackageScanResourceResolver()); if (config.getMain().getRouteFilterIncludePattern() != null - || config.getMain().getRouteFilterExcludePattern() != null) { + || config.getMain().getRouteFilterExcludePattern() != null) { LOG.info("Route filtering pattern: include={}, exclude={}", config.getMain().getRouteFilterIncludePattern(), config.getMain().getRouteFilterExcludePattern()); camelContext.getCamelContextExtension().getContextPlugin(Model.class).setRouteFilterPattern( @@ -244,7 +250,7 @@ public class CamelAutoConfiguration { } else if ("logging".equals(config.getMain().getStartupRecorder())) { camelContext.getCamelContextExtension().setStartupStepRecorder(new LoggingStartupStepRecorder()); } else if ("java-flight-recorder".equals(config.getMain().getStartupRecorder()) - || config.getMain().getStartupRecorder() == null) { + || config.getMain().getStartupRecorder() == null) { // try to auto discover camel-jfr to use StartupStepRecorder fr = camelContext.getCamelContextExtension().getBootstrapFactoryFinder() .newInstance(StartupStepRecorder.FACTORY, StartupStepRecorder.class).orElse(null); @@ -261,7 +267,7 @@ public class CamelAutoConfiguration { @Bean CamelSpringBootApplicationController applicationController(ApplicationContext applicationContext, - CamelContext camelContext) { + CamelContext camelContext) { return new CamelSpringBootApplicationController(applicationContext, camelContext); } @@ -275,7 +281,7 @@ public class CamelAutoConfiguration { @Bean @ConditionalOnMissingBean(CamelSpringBootApplicationListener.class) CamelSpringBootApplicationListener routesCollectorListener(ApplicationContext applicationContext, - CamelConfigurationProperties config, RoutesCollector routesCollector) { + CamelConfigurationProperties config, RoutesCollector routesCollector) { Collection<CamelContextConfiguration> configurations = applicationContext .getBeansOfType(CamelContextConfiguration.class).values(); return new CamelSpringBootApplicationListener(applicationContext, new ArrayList(configurations), config, @@ -374,4 +380,26 @@ public class CamelAutoConfiguration { return new CamelSpringBootBeanPostProcessor(applicationContext); } + /** + * Camel startup strategy - used early by Camel + */ + @Bean + StartupConditionStrategy startupConditionStrategy(CamelStartupConditionConfigurationProperties config) { + StartupConditionStrategy scs = new DefaultStartupConditionStrategy(); + scs.setEnabled(config.isEnabled()); + scs.setInterval(config.getInterval()); + scs.setTimeout(config.getTimeout()); + scs.setOnTimeout(config.getOnTimeout()); + String envExist = config.getEnvironmentVariableExists(); + if (envExist != null) { + scs.addStartupCondition(new EnvStartupCondition(envExist)); + } + String file = config.getFileExists(); + if (file != null) { + scs.addStartupCondition(new FileStartupCondition(file)); + } + scs.addStartupConditions(config.getCustomClassNames()); + return scs; + } + } diff --git a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelStartupConditionConfigurationProperties.java b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelStartupConditionConfigurationProperties.java new file mode 100644 index 00000000000..428dc30899e --- /dev/null +++ b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelStartupConditionConfigurationProperties.java @@ -0,0 +1,119 @@ +/* + * 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.spi.Metadata; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "camel.startupcondition") +public class CamelStartupConditionConfigurationProperties { + + /** + * To enable using startup conditions + */ + private boolean enabled; + + /** + * Interval in millis between checking conditions. + */ + private int interval = 500; + + /** + * Total timeout (in millis) for all startup conditions. + */ + private int timeout = 20000; + + /** + * What action, to do on timeout. + * + * fail = do not startup, and throw an exception causing camel to fail stop = do not startup, and stop camel ignore + * = log a WARN and continue to startup + */ + @Metadata(defaultValue = "stop", enums = "fail,stop,ignore") + private String onTimeout = "stop"; + + /** + * Wait for an environment variable with the given name to exists before continuing + */ + private String environmentVariableExists; + + /** + * Wait for a file with the given name to exists before continuing + */ + private String fileExists; + + /** + * A list of custom class names (FQN). Multiple classes can be separated by comma. + */ + private String customClassNames; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public int getInterval() { + return interval; + } + + public void setInterval(int interval) { + this.interval = interval; + } + + public int getTimeout() { + return timeout; + } + + public void setTimeout(int timeout) { + this.timeout = timeout; + } + + public String getOnTimeout() { + return onTimeout; + } + + public void setOnTimeout(String onTimeout) { + this.onTimeout = onTimeout; + } + + public String getEnvironmentVariableExists() { + return environmentVariableExists; + } + + public void setEnvironmentVariableExists(String environmentVariableExists) { + this.environmentVariableExists = environmentVariableExists; + } + + public String getFileExists() { + return fileExists; + } + + public void setFileExists(String fileExists) { + this.fileExists = fileExists; + } + + public String getCustomClassNames() { + return customClassNames; + } + + public void setCustomClassNames(String customClassNames) { + this.customClassNames = customClassNames; + } +} diff --git a/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/CamelStartupConditionEnvTest.java b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/CamelStartupConditionEnvTest.java new file mode 100644 index 00000000000..e6a90b1b0b7 --- /dev/null +++ b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/CamelStartupConditionEnvTest.java @@ -0,0 +1,90 @@ +/* + * 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.support.startup.EnvStartupCondition; +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.concurrent.atomic.AtomicInteger; + +@DirtiesContext +@CamelSpringBootTest +@EnableAutoConfiguration +@SpringBootTest(properties = {"camel.startupcondition.enabled=true", "camel.startupcondition.interval=10", "camel.startupcondition.customClassNames=org.apache.camel.spring.boot.CamelStartupConditionEnvTest$MyEnvCondition"}) +public class CamelStartupConditionEnvTest { + + private static final AtomicInteger COUNTER = new AtomicInteger(); + + private CamelStartupConditionEnvTest() { + } + + public static CamelStartupConditionEnvTest createCamelStartupConditionEnvTest() { + return new CamelStartupConditionEnvTest(); + } + + @Configuration + static class Config { + @Bean + RouteBuilder routeBuilder() { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:foo").to("mock:foo"); + } + }; + } + + } + + @Autowired + CamelContext camelContext; + + @Autowired + ProducerTemplate producerTemplate; + + @Test + public void testCustomCondition() throws Exception { + Assertions.assertEquals(3, COUNTER.get()); + } + + public static class MyEnvCondition extends EnvStartupCondition { + + public MyEnvCondition() { + super("MY_ENV"); + } + + @Override + protected String lookupEnvironmentVariable(String env) { + if (COUNTER.incrementAndGet() < 3) { + return null; + } + return "FOO"; + } + } + +}