diff --git a/core-starter/camel-k-starter/pom.xml b/core-starter/camel-k-starter/pom.xml new file mode 100644 index 00000000000..3575ad39cfb --- /dev/null +++ b/core-starter/camel-k-starter/pom.xml @@ -0,0 +1,131 @@ + + + + 4.0.0 + + + org.apache.camel.springboot + core-starter + 4.5.0-SNAPSHOT + ../pom.xml + + + camel-k-starter + jar + Camel SB Starters :: camel-k + + + + + org.springframework.boot + spring-boot-starter + + + org.apache.camel.springboot + camel-core-starter + + + org.apache.camel.springboot + camel-kubernetes-starter + + + + + org.apache.camel + camel-test-spring-junit5 + + + org.apache.camel + camel-spring-xml + + + test + + + + org.awaitility + awaitility + test + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.apache.camel.springboot + camel-yaml-dsl-starter + test + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin-version} + + ${jdk.version} + ${jdk.version} + 512M + ${compiler.fork} + + true + + + + + org.apache.camel + camel-package-maven-plugin + ${camel-version} + + + generate + + prepare-components + generate-components-list + + process-classes + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + ${project.basedir}/src/test/resources/camel-k/conf.properties + ${project.basedir}/src/test/resources/camel-k/conf.d + + + + + + + + + diff --git a/core-starter/camel-k-starter/src/main/java/org/apache/camel/spring/boot/k/ApplicationConfiguration.java b/core-starter/camel-k-starter/src/main/java/org/apache/camel/spring/boot/k/ApplicationConfiguration.java new file mode 100644 index 00000000000..cb41448942d --- /dev/null +++ b/core-starter/camel-k-starter/src/main/java/org/apache/camel/spring/boot/k/ApplicationConfiguration.java @@ -0,0 +1,164 @@ +/* + * 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.k; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + +import java.util.ArrayList; +import java.util.List; + +@ConfigurationProperties(prefix = "camel.k") +public class ApplicationConfiguration { + /** + * Global option to enable/disable Camel K. + */ + private boolean enabled; + + + @NestedConfigurationProperty + private final ShutdownProperties shutdown = new ShutdownProperties(); + + @NestedConfigurationProperty + private final RoutesProperties routes = new RoutesProperties(); + + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public ShutdownProperties getShutdown() { + return shutdown; + } + + public RoutesProperties getRoutes() { + return routes; + } + + public static class RoutesProperties { + /** + * To define some rules to im-place replace some aspect of the loaded routes. + */ + @NestedConfigurationProperty + private final List overrides = new ArrayList<>(); + + public List getOverrides() { + return overrides; + } + } + + public static class ShutdownProperties { + + /** + * To specify how many messages to process by Camel before automatic terminating the Application or the Camel + * Context. If there are inflight messages, the shutdown is delayed till all the exchanges have been completed. + */ + private int maxMessages = 0; + + /** + * Controls whether the Camel application should terminate the Application or the CamelContext when the + * maxMessages threshold has been reached. Default is {@link ShutdownStrategy#APPLICATION} + *

+ * Note that this is meant to be used mainly for test. In real applications Camel K expects the application to + * be stopped. + */ + private ShutdownStrategy strategy = ShutdownStrategy.APPLICATION; + + public int getMaxMessages() { + return maxMessages; + } + + public void setMaxMessages(int maxMessages) { + this.maxMessages = maxMessages; + } + + public ShutdownStrategy getStrategy() { + return strategy; + } + + public void setStrategy(ShutdownStrategy strategy) { + this.strategy = strategy; + } + } + + public enum ShutdownStrategy { + APPLICATION, + CAMEL + } + + + public static class RouteOverride { + /** + * Identifies the route to be amended. + */ + private String id; + + /** + * Override for the {@link org.apache.camel.model.FromDefinition} of a + * {@link org.apache.camel.model.RouteDefinition}. + */ + private RouteInputOverride input; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public RouteInputOverride getInput() { + return input; + } + + public void setInput(RouteInputOverride input) { + this.input = input; + } + } + + public static class RouteInputOverride { + /** + * The optional endpoint that should be replaced. + */ + private String from; + + /** + * The value that should replace the endpoint. + */ + private String with; + + public String getFrom() { + return from; + } + + public void setFrom(String from) { + this.from = from; + } + + public String getWith() { + return with; + } + + public void setWith(String with) { + this.with = with; + } + } +} diff --git a/core-starter/camel-k-starter/src/main/java/org/apache/camel/spring/boot/k/ApplicationConstants.java b/core-starter/camel-k-starter/src/main/java/org/apache/camel/spring/boot/k/ApplicationConstants.java new file mode 100644 index 00000000000..009b97fb707 --- /dev/null +++ b/core-starter/camel-k-starter/src/main/java/org/apache/camel/spring/boot/k/ApplicationConstants.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.spring.boot.k; + +public final class ApplicationConstants { + public static final String ENV_CAMEL_K_CONF = "CAMEL_K_CONF"; + public static final String PROPERTY_CAMEL_K_CONF = "camel.k.conf"; + + public static final String ENV_CAMEL_K_CONF_D = "CAMEL_K_CONF_D"; + public static final String PROPERTY_CAMEL_K_CONF_D = "camel.k.conf.d"; + + public static final String ENV_CAMEL_K_MOUNT_PATH_CONFIGMAPS = "CAMEL_K_MOUNT_PATH_CONFIGMAPS"; + public static final String PROPERTY_CAMEL_K_MOUNT_PATH_CONFIGMAPS = "camel.k.mount-path.configmaps"; + public static final String PATH_CONFIGMAPS = "_configmaps"; + + public static final String ENV_CAMEL_K_MOUNT_PATH_SECRETS = "CAMEL_K_MOUNT_PATH_SECRETS"; + public static final String PROPERTY_CAMEL_K_MOUNT_PATH_SECRETS = "camel.k.mount-path.secrets"; + public static final String PATH_SECRETS = "_secrets"; + + public static final String ENV_CAMEL_K_MOUNT_PATH_SERVICEBINDINGS = "CAMEL_K_MOUNT_PATH_SERVICEBINDINGS"; + public static final String PROPERTY_CAMEL_K_MOUNT_PATH_SERVICEBINDINGS = "camel.k.mount-path.servicebindings"; + public static final String PATH_SERVICEBINDINGS = "_servicebindings"; + + private ApplicationConstants() { + } +} diff --git a/core-starter/camel-k-starter/src/main/java/org/apache/camel/spring/boot/k/ApplicationEnvironmentPostProcessor.java b/core-starter/camel-k-starter/src/main/java/org/apache/camel/spring/boot/k/ApplicationEnvironmentPostProcessor.java new file mode 100644 index 00000000000..d2e50382622 --- /dev/null +++ b/core-starter/camel-k-starter/src/main/java/org/apache/camel/spring/boot/k/ApplicationEnvironmentPostProcessor.java @@ -0,0 +1,193 @@ +/* + * 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.k; + +import org.apache.camel.component.kubernetes.properties.ConfigMapPropertiesFunction; +import org.apache.camel.util.ObjectHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.env.EnvironmentPostProcessor; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.PropertiesPropertySource; + +import java.io.IOException; +import java.io.Reader; +import java.nio.charset.MalformedInputException; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Objects; +import java.util.Properties; + +/** + * Inject Camel K's properties to Spring Boot Environment + */ +public class ApplicationEnvironmentPostProcessor implements EnvironmentPostProcessor { + private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationEnvironmentPostProcessor.class); + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + configurePropertySources(environment, application); + } + + private void configurePropertySources(ConfigurableEnvironment environment, SpringApplication application) { + + Properties sysProperties = new Properties(); + + // explicit disable looking up configmap and secret using the KubernetesClient + sysProperties.put(ConfigMapPropertiesFunction.CLIENT_ENABLED, "false"); + + environment.getPropertySources().addLast( + new PropertiesPropertySource("camel-k-sys", sysProperties)); + environment.getPropertySources().addLast( + new PropertiesPropertySource("camel-k-app", loadApplicationProperties())); + environment.getPropertySources().addLast( + new PropertiesPropertySource("camel-k-usr-configmap", loadConfigMapUserProperties())); + environment.getPropertySources().addLast( + new PropertiesPropertySource("camel-k-usr-secrets", loadSecretsProperties())); + environment.getPropertySources().addLast( + new PropertiesPropertySource("camel-k-servicebindings", loadServiceBindingsProperties())); + + } + + // ****************************************** + // + // Helpers + // + // ****************************************** + + private static Properties loadApplicationProperties() { + final String conf = System.getProperty(ApplicationConstants.PROPERTY_CAMEL_K_CONF, + System.getenv(ApplicationConstants.ENV_CAMEL_K_CONF)); + + final Properties properties = new Properties(); + + if (ObjectHelper.isEmpty(conf)) { + return properties; + } + + + try { + Path confPath = Paths.get(conf); + if (Files.exists(confPath) && !Files.isDirectory(confPath)) { + try (Reader reader = Files.newBufferedReader(confPath)) { + properties.load(reader); + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + + return properties; + } + + private static Properties loadConfigMapUserProperties() { + return loadUserProperties( + ApplicationConstants.PROPERTY_CAMEL_K_MOUNT_PATH_CONFIGMAPS, + ApplicationConstants.ENV_CAMEL_K_MOUNT_PATH_CONFIGMAPS, + ApplicationConstants.PATH_CONFIGMAPS); + } + + private static Properties loadSecretsProperties() { + return loadUserProperties( + ApplicationConstants.PROPERTY_CAMEL_K_MOUNT_PATH_SECRETS, + ApplicationConstants.ENV_CAMEL_K_MOUNT_PATH_SECRETS, + ApplicationConstants.PATH_SECRETS); + } + + private static Properties loadServiceBindingsProperties() { + return loadUserProperties( + ApplicationConstants.PROPERTY_CAMEL_K_MOUNT_PATH_SERVICEBINDINGS, + ApplicationConstants.ENV_CAMEL_K_MOUNT_PATH_SERVICEBINDINGS, + ApplicationConstants.PATH_SERVICEBINDINGS); + } + + private static Properties loadUserProperties(String property, String env, String subpath) { + String path = System.getProperty(property, System.getenv(env)); + + if (path == null) { + String conf = System.getProperty( + ApplicationConstants.PROPERTY_CAMEL_K_CONF_D, + System.getenv(ApplicationConstants.ENV_CAMEL_K_CONF_D)); + + if (conf != null) { + if (!conf.endsWith("/")) { + conf = conf + "/"; + } + + path = conf + subpath; + } + } + + final Properties properties = new Properties(); + + if (ObjectHelper.isEmpty(path)) { + return properties; + } + + final Path root = Paths.get(path); + + if (Files.exists(root)) { + FileVisitor visitor = propertiesCollector(properties); + try { + Files.walkFileTree(root, visitor); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + return properties; + } + + private static FileVisitor propertiesCollector(Properties properties) { + return new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Objects.requireNonNull(file); + Objects.requireNonNull(attrs); + + if (Files.isDirectory(file) || Files.isSymbolicLink(file)) { + return FileVisitResult.CONTINUE; + } + + if (file.toFile().getAbsolutePath().endsWith(".properties")) { + try (Reader reader = Files.newBufferedReader(file)) { + Properties p = new Properties(); + p.load(reader); + p.forEach((key, value) -> properties.put(String.valueOf(key), String.valueOf(value))); + } + } else { + try { + properties.put( + file.getFileName().toString(), + Files.readString(file, StandardCharsets.UTF_8)); + } catch (MalformedInputException mie) { + // Just skip if it is not a UTF-8 encoded file (ie a binary) + LOGGER.info("Cannot transform {} into UTF-8 text, skipping.", file); + } + } + return FileVisitResult.CONTINUE; + } + }; + } +} diff --git a/core-starter/camel-k-starter/src/main/java/org/apache/camel/spring/boot/k/ApplicationRoutesAutoConfiguration.java b/core-starter/camel-k-starter/src/main/java/org/apache/camel/spring/boot/k/ApplicationRoutesAutoConfiguration.java new file mode 100644 index 00000000000..46cbd7d5388 --- /dev/null +++ b/core-starter/camel-k-starter/src/main/java/org/apache/camel/spring/boot/k/ApplicationRoutesAutoConfiguration.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.spring.boot.k; + +import org.apache.camel.spi.CamelContextCustomizer; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.spring.boot.util.ConditionalOnCamelContextAndAutoConfigurationBeans; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@Conditional(ConditionalOnCamelContextAndAutoConfigurationBeans.class) +@EnableConfigurationProperties({ApplicationConfiguration.class}) +@ConditionalOnProperty(prefix = "camel.k.enabled", name = "enabled", havingValue = "true", matchIfMissing = true) +@AutoConfigureBefore({CamelAutoConfiguration.class}) +public class ApplicationRoutesAutoConfiguration { + + @Bean + CamelContextCustomizer registerRoutesCustomizer(ApplicationConfiguration configuration) throws Exception { + return new ApplicationRoutesCustomizer(configuration); + } +} diff --git a/core-starter/camel-k-starter/src/main/java/org/apache/camel/spring/boot/k/ApplicationRoutesCustomizer.java b/core-starter/camel-k-starter/src/main/java/org/apache/camel/spring/boot/k/ApplicationRoutesCustomizer.java new file mode 100644 index 00000000000..99e72867eea --- /dev/null +++ b/core-starter/camel-k-starter/src/main/java/org/apache/camel/spring/boot/k/ApplicationRoutesCustomizer.java @@ -0,0 +1,102 @@ +/* + * 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.k; + +import org.apache.camel.CamelContext; +import org.apache.camel.Route; +import org.apache.camel.impl.DefaultModelReifierFactory; +import org.apache.camel.model.FromDefinition; +import org.apache.camel.model.Model; +import org.apache.camel.model.RouteDefinition; +import org.apache.camel.spi.CamelContextCustomizer; +import org.apache.camel.util.ObjectHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Objects; + +/** + * Customize routes related logic. + */ +public class ApplicationRoutesCustomizer implements CamelContextCustomizer { + private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationRoutesCustomizer.class); + + private final ApplicationConfiguration config; + + public ApplicationRoutesCustomizer(ApplicationConfiguration config) { + this.config = config; + } + + + @Override + public void configure(CamelContext camelContext) { + camelContext.getCamelContextExtension() + .getContextPlugin(Model.class) + .setModelReifierFactory(new ApplicationModelReifierFactory(config)); + } + + public static class ApplicationModelReifierFactory extends DefaultModelReifierFactory { + private final ApplicationConfiguration config; + + public ApplicationModelReifierFactory(ApplicationConfiguration config) { + this.config = config; + } + + @Override + public Route createRoute(CamelContext camelContext, Object routeDefinition) { + + if (routeDefinition instanceof RouteDefinition) { + override((RouteDefinition) routeDefinition); + } + + return super.createRoute(camelContext, routeDefinition); + } + + public void override(RouteDefinition definition) { + if (config.getRoutes().getOverrides().isEmpty()) { + return; + } + + final String id = definition.getRouteId(); + final FromDefinition from = definition.getInput(); + + for (ApplicationConfiguration.RouteOverride override : config.getRoutes().getOverrides()) { + final String overrideRouteId = override.getId(); + final String overrideRouteFrom = override.getInput().getFrom(); + + if (ObjectHelper.isEmpty(overrideRouteId) && ObjectHelper.isEmpty(overrideRouteFrom)) { + continue; + } + if (ObjectHelper.isNotEmpty(overrideRouteId) && !Objects.equals(overrideRouteId, id)) { + continue; + } + if (ObjectHelper.isNotEmpty(overrideRouteFrom) && !Objects.equals(from.getEndpointUri(), overrideRouteFrom)) { + continue; + } + + LOGGER.debug("Replace '{}' --> '{}' for route {}", + from.getEndpointUri(), + override.getInput().getWith(), + definition.getRouteId()); + + from.setUri(override.getInput().getWith()); + + break; + } + } + } +} diff --git a/core-starter/camel-k-starter/src/main/java/org/apache/camel/spring/boot/k/ApplicationShutdownAutoConfiguration.java b/core-starter/camel-k-starter/src/main/java/org/apache/camel/spring/boot/k/ApplicationShutdownAutoConfiguration.java new file mode 100644 index 00000000000..953a56a8e0f --- /dev/null +++ b/core-starter/camel-k-starter/src/main/java/org/apache/camel/spring/boot/k/ApplicationShutdownAutoConfiguration.java @@ -0,0 +1,44 @@ +/* + * 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.k; + +import org.apache.camel.spi.CamelContextCustomizer; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.spring.boot.util.ConditionalOnCamelContextAndAutoConfigurationBeans; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@Conditional(ConditionalOnCamelContextAndAutoConfigurationBeans.class) +@EnableConfigurationProperties({ApplicationConfiguration.class}) +@ConditionalOnProperty(prefix = "camel.k.enabled", name = "enabled", havingValue = "true", matchIfMissing = true) +@AutoConfigureBefore({CamelAutoConfiguration.class}) +public class ApplicationShutdownAutoConfiguration { + + @Bean + CamelContextCustomizer registerShutdownCustomizer( + ConfigurableApplicationContext applicationContext, + ApplicationConfiguration configuration) { + + return new ApplicationShutdownCustomizer(applicationContext, configuration); + } +} diff --git a/core-starter/camel-k-starter/src/main/java/org/apache/camel/spring/boot/k/ApplicationShutdownCustomizer.java b/core-starter/camel-k-starter/src/main/java/org/apache/camel/spring/boot/k/ApplicationShutdownCustomizer.java new file mode 100644 index 00000000000..9c80bff32a2 --- /dev/null +++ b/core-starter/camel-k-starter/src/main/java/org/apache/camel/spring/boot/k/ApplicationShutdownCustomizer.java @@ -0,0 +1,118 @@ +/* + * 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.k; + +import org.apache.camel.CamelContext; +import org.apache.camel.spi.CamelContextCustomizer; +import org.apache.camel.spi.CamelEvent; +import org.apache.camel.support.EventNotifierSupport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ApplicationContext; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +public class ApplicationShutdownCustomizer implements CamelContextCustomizer { + private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationShutdownCustomizer.class); + + private final ApplicationContext applicationContext; + private final ApplicationConfiguration config; + + public ApplicationShutdownCustomizer( + ApplicationContext applicationContext, + ApplicationConfiguration config) { + + this.applicationContext = applicationContext; + this.config = config; + } + + @Override + public void configure(CamelContext camelContext) { + if (this.config.getShutdown().getMaxMessages() > 0) { + LOGGER.info( + "Configure the JVM to terminate after {} messages and none inflight (strategy: {})", + this.config.getShutdown().getMaxMessages(), + this.config.getShutdown().getStrategy()); + + camelContext.getManagementStrategy().addEventNotifier( + new ShutdownEventHandler(applicationContext, camelContext, config)); + } + } + + private static final class ShutdownEventHandler extends EventNotifierSupport { + private static final Logger LOGGER = LoggerFactory.getLogger(ShutdownEventHandler.class); + + private final ApplicationContext applicationContext; + private final CamelContext camelContext; + private final ApplicationConfiguration config; + private final AtomicInteger counter; + private final AtomicBoolean shutdownStarted; + + ShutdownEventHandler( + ApplicationContext applicationContext, + CamelContext camelContext, + ApplicationConfiguration config) { + + this.applicationContext = applicationContext; + this.camelContext = camelContext; + this.config = config; + this.counter = new AtomicInteger(); + this.shutdownStarted = new AtomicBoolean(); + } + + @Override + public void notify(CamelEvent event) throws Exception { + final int currentCounter = counter.incrementAndGet(); + final int currentInflight = camelContext.getInflightRepository().size(); + + LOGGER.debug("CamelEvent received (max: {}, handled: {}, inflight: {})", + config.getShutdown().getMaxMessages(), + currentCounter, + currentInflight); + + if (currentCounter < config.getShutdown().getMaxMessages() || currentInflight != 0) { + return; + } + + if (!shutdownStarted.compareAndExchange(false, true)) { + camelContext.getExecutorServiceManager().newThread("ShutdownStrategy", () -> { + + try { + LOGGER.info("Initiate runtime shutdown (max: {}, handled: {})", + config.getShutdown().getMaxMessages(), + currentCounter); + + if (config.getShutdown().getStrategy() == ApplicationConfiguration.ShutdownStrategy.APPLICATION) { + SpringApplication.exit(applicationContext, () -> 0); + } else { + camelContext.shutdown(); + } + } catch (Exception e) { + LOGGER.warn("Error while shutting down the runtime", e); + } + }).start(); + } + } + + @Override + public boolean isEnabled(CamelEvent event) { + return (event instanceof CamelEvent.ExchangeCompletedEvent || event instanceof CamelEvent.ExchangeFailedEvent); + } + } +} diff --git a/core-starter/camel-k-starter/src/main/resources/META-INF/LICENSE.txt b/core-starter/camel-k-starter/src/main/resources/META-INF/LICENSE.txt new file mode 100644 index 00000000000..6b0b1270ff0 --- /dev/null +++ b/core-starter/camel-k-starter/src/main/resources/META-INF/LICENSE.txt @@ -0,0 +1,203 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. + diff --git a/core-starter/camel-k-starter/src/main/resources/META-INF/NOTICE.txt b/core-starter/camel-k-starter/src/main/resources/META-INF/NOTICE.txt new file mode 100644 index 00000000000..2e215bf2e6b --- /dev/null +++ b/core-starter/camel-k-starter/src/main/resources/META-INF/NOTICE.txt @@ -0,0 +1,11 @@ + ========================================================================= + == NOTICE file corresponding to the section 4 d of == + == the Apache License, Version 2.0, == + == in this case for the Apache Camel distribution. == + ========================================================================= + + This product includes software developed by + The Apache Software Foundation (http://www.apache.org/). + + Please read the different LICENSE files present in the licenses directory of + this distribution. diff --git a/core-starter/camel-k-starter/src/main/resources/META-INF/spring.factories b/core-starter/camel-k-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000000..74437450523 --- /dev/null +++ b/core-starter/camel-k-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,18 @@ +## --------------------------------------------------------------------------- +## 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. +## --------------------------------------------------------------------------- + +org.springframework.boot.env.EnvironmentPostProcessor=org.apache.camel.spring.boot.k.ApplicationEnvironmentPostProcessor \ No newline at end of file diff --git a/core-starter/camel-k-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/core-starter/camel-k-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000000..38b1c5638f3 --- /dev/null +++ b/core-starter/camel-k-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,19 @@ +## --------------------------------------------------------------------------- +## 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. +## --------------------------------------------------------------------------- + +org.apache.camel.spring.boot.k.ApplicationShutdownAutoConfiguration +org.apache.camel.spring.boot.k.ApplicationRoutesAutoConfiguration \ No newline at end of file diff --git a/core-starter/camel-k-starter/src/test/java/org/apache/camel/spring/boot/k/ApplicationPropertiesTest.java b/core-starter/camel-k-starter/src/test/java/org/apache/camel/spring/boot/k/ApplicationPropertiesTest.java new file mode 100644 index 00000000000..fa146b4f53a --- /dev/null +++ b/core-starter/camel-k-starter/src/test/java/org/apache/camel/spring/boot/k/ApplicationPropertiesTest.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.spring.boot.k; + +import org.apache.camel.CamelContext; +import org.apache.camel.spi.PropertiesComponent; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.test.spring.junit5.CamelSpringBootTest; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.core.env.Environment; +import org.springframework.test.annotation.DirtiesContext; + +import static org.assertj.core.api.Assertions.assertThat; + +@DirtiesContext +@CamelSpringBootTest +@EnableAutoConfiguration +@SpringBootTest( + classes = { + CamelAutoConfiguration.class, + ApplicationRoutesAutoConfiguration.class, + ApplicationShutdownAutoConfiguration.class, + ApplicationPropertiesTest.class + } +) +public class ApplicationPropertiesTest { + + @Autowired + private CamelContext camelContext; + @Autowired + private Environment env; + + /** + * Ensure that properties are available both from Camel's {@link PropertiesComponent} as + * well as from Spring Boot's {@link Environment}. + */ + @ParameterizedTest + @CsvSource(value = { + "app.property,app.value", + "my.override,default-value", + "cm-flat,cm-flat-value", + "secret-flat,secret-flat-value", + }) + public void testProperties(String name, String value) throws Exception { + PropertiesComponent component = camelContext.getPropertiesComponent(); + + assertThat(component.resolveProperty("{{" + name + "}}")) + .isPresent() + .containsInstanceOf(String.class) + .contains(value); + + assertThat(env.getProperty(name)) + .isEqualTo(value); + } +} diff --git a/core-starter/camel-k-starter/src/test/java/org/apache/camel/spring/boot/k/ApplicationRoutesTest.java b/core-starter/camel-k-starter/src/test/java/org/apache/camel/spring/boot/k/ApplicationRoutesTest.java new file mode 100644 index 00000000000..83ad0f3414b --- /dev/null +++ b/core-starter/camel-k-starter/src/test/java/org/apache/camel/spring/boot/k/ApplicationRoutesTest.java @@ -0,0 +1,84 @@ +/* + * 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.k; + +import org.apache.camel.CamelContext; +import org.apache.camel.Route; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.test.spring.junit5.CamelSpringBootTest; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; + +import static org.assertj.core.api.Assertions.assertThat; + +@DirtiesContext +@CamelSpringBootTest +@EnableAutoConfiguration +@SpringBootTest( + classes = { + CamelAutoConfiguration.class, + ApplicationRoutesAutoConfiguration.class, + ApplicationShutdownAutoConfiguration.class, + ApplicationRoutesTest.class + }, + properties = { + // TODO: ideally it would be nice if the camel.main.routes-include-pattern would be honoured. + // The camel.springboot namespace should ideally be for Spring Boot specific options. + "camel.springboot.routes-include-pattern=classpath:camel-k/sources/test-route-001.yaml", + // camel-k + "camel.k.routes.overrides[0].input.from=direct:r1", + "camel.k.routes.overrides[0].input.with=direct:r1override", + "camel.k.routes.overrides[1].id=r2invalid", + "camel.k.routes.overrides[1].input.from=direct:r2", + "camel.k.routes.overrides[1].input.with=direct:r2override", + "camel.k.routes.overrides[2].id=r3", + "camel.k.routes.overrides[2].input.from=direct:r3invalid", + "camel.k.routes.overrides[2].input.with=direct:r3override", + "camel.k.routes.overrides[3].id=r4", + "camel.k.routes.overrides[3].input.from=direct:r4", + "camel.k.routes.overrides[3].input.with=direct:r4override", + "camel.k.routes.overrides[4].input.with=direct:r5invalid", + "camel.k.routes.overrides[5].id=r5", + "camel.k.routes.overrides[5].input.with=direct:r5override" + } +) +public class ApplicationRoutesTest { + + @Autowired + private CamelContext camelContext; + + @ParameterizedTest + @CsvSource({ + "r1,direct://r1override", + "r2,direct://r2", + "r3,direct://r3", + "r4,direct://r4override", + "r5,direct://r5override", + }) + public void testOverrides(String id, String expected) throws Exception { + Route route = camelContext.getRoute(id); + + assertThat(route.getRouteId()) + .isEqualTo(id); + assertThat(route.getEndpoint().getEndpointUri()) + .isEqualTo(expected); + } +} diff --git a/core-starter/camel-k-starter/src/test/java/org/apache/camel/spring/boot/k/ApplicationShutdownTest.java b/core-starter/camel-k-starter/src/test/java/org/apache/camel/spring/boot/k/ApplicationShutdownTest.java new file mode 100644 index 00000000000..a9d634175fc --- /dev/null +++ b/core-starter/camel-k-starter/src/test/java/org/apache/camel/spring/boot/k/ApplicationShutdownTest.java @@ -0,0 +1,84 @@ +/* + * 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.k; + +import org.apache.camel.CamelContext; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.test.spring.junit5.CamelSpringBootTest; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; + +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; + +@DirtiesContext +@CamelSpringBootTest +@EnableAutoConfiguration +@SpringBootTest( + classes = { + CamelAutoConfiguration.class, + ApplicationRoutesAutoConfiguration.class, + ApplicationShutdownAutoConfiguration.class, + ApplicationShutdownTest.TestConfiguration.class, + }, + properties = { + // TODO: ideally it would be nice if the camel.main.routes-include-pattern would be honoured. + // The camel.springboot namespace should ideally be for Spring Boot specific options. + "camel.springboot.routes-include-pattern=classpath:camel-k/sources/test-route-002.yaml", + // camel-k + "camel.k.shutdown.maxMessages=1", + "camel.k.shutdown.strategy=CAMEL", + // misc + "greeted.subject=Joe" + } +) +@ExtendWith(OutputCaptureExtension.class) +public class ApplicationShutdownTest { + + @Autowired + private CamelContext camelContext; + + @Test + public void testShutdown(CapturedOutput output) throws Exception { + + Logger l = LoggerFactory.getLogger(getClass()); + + Awaitility.await() + .pollInterval(1, TimeUnit.SECONDS) + .atMost(3, TimeUnit.MINUTES) + .untilAsserted(() -> { + assertThat(output).contains("Hello Joe!"); + assertThat(output).contains("Once done"); + assertThat(output).contains("Initiate runtime shutdown (max: 1, handled: 2)"); + }); + } + + @Configuration + public static class TestConfiguration { + } +} diff --git a/core-starter/camel-k-starter/src/test/resources/application.properties b/core-starter/camel-k-starter/src/test/resources/application.properties new file mode 100644 index 00000000000..95c05d1ebf7 --- /dev/null +++ b/core-starter/camel-k-starter/src/test/resources/application.properties @@ -0,0 +1,18 @@ +# +# 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. +# + +spring.main.banner-mode = off \ No newline at end of file diff --git a/core-starter/camel-k-starter/src/test/resources/camel-k/conf.d/_configmaps/001/application.properties b/core-starter/camel-k-starter/src/test/resources/camel-k/conf.d/_configmaps/001/application.properties new file mode 100644 index 00000000000..bd3da39352e --- /dev/null +++ b/core-starter/camel-k-starter/src/test/resources/camel-k/conf.d/_configmaps/001/application.properties @@ -0,0 +1,19 @@ +## --------------------------------------------------------------------------- +## 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. +## --------------------------------------------------------------------------- + +cm.properties = cm-properties-value +my.override = shouldNotOverride \ No newline at end of file diff --git a/core-starter/camel-k-starter/src/test/resources/camel-k/conf.d/_configmaps/001/cm-flat b/core-starter/camel-k-starter/src/test/resources/camel-k/conf.d/_configmaps/001/cm-flat new file mode 100644 index 00000000000..3a53bf5a742 --- /dev/null +++ b/core-starter/camel-k-starter/src/test/resources/camel-k/conf.d/_configmaps/001/cm-flat @@ -0,0 +1 @@ +cm-flat-value \ No newline at end of file diff --git a/core-starter/camel-k-starter/src/test/resources/camel-k/conf.d/_secrets/001/secret-flat b/core-starter/camel-k-starter/src/test/resources/camel-k/conf.d/_secrets/001/secret-flat new file mode 100644 index 00000000000..7ae6abb1147 --- /dev/null +++ b/core-starter/camel-k-starter/src/test/resources/camel-k/conf.d/_secrets/001/secret-flat @@ -0,0 +1 @@ +secret-flat-value \ No newline at end of file diff --git a/core-starter/camel-k-starter/src/test/resources/camel-k/conf.properties b/core-starter/camel-k-starter/src/test/resources/camel-k/conf.properties new file mode 100644 index 00000000000..702f71e815c --- /dev/null +++ b/core-starter/camel-k-starter/src/test/resources/camel-k/conf.properties @@ -0,0 +1,19 @@ +## --------------------------------------------------------------------------- +## 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. +## --------------------------------------------------------------------------- + +app.property = app.value +my.override = default-value \ No newline at end of file diff --git a/core-starter/camel-k-starter/src/test/resources/camel-k/sources/test-route-001.yaml b/core-starter/camel-k-starter/src/test/resources/camel-k/sources/test-route-001.yaml new file mode 100644 index 00000000000..ee61f2fc7f8 --- /dev/null +++ b/core-starter/camel-k-starter/src/test/resources/camel-k/sources/test-route-001.yaml @@ -0,0 +1,47 @@ +# +# 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. +# + +- route: + id: "r1" + from: + uri: "direct:r1" + steps: + - to: "log:info1" +- route: + id: "r2" + from: + uri: "direct:r2" + steps: + - to: "log:info2" +- route: + id: "r3" + from: + uri: "direct:r3" + steps: + - to: "log:info3" +- route: + id: "r4" + from: + uri: "direct:r4" + steps: + - to: "log:info4" +- route: + id: "r5" + from: + uri: "direct:r5" + steps: + - to: "log:info5" diff --git a/core-starter/camel-k-starter/src/test/resources/camel-k/sources/test-route-002.yaml b/core-starter/camel-k-starter/src/test/resources/camel-k/sources/test-route-002.yaml new file mode 100644 index 00000000000..f6e0dcb451a --- /dev/null +++ b/core-starter/camel-k-starter/src/test/resources/camel-k/sources/test-route-002.yaml @@ -0,0 +1,40 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +- route: + from: + uri: 'timer:once' + parameters: + delay: '1s' + repeatCount: '1' + steps: + - wireTap: + uri: "direct:in" + - delay: + constant: '250' + - log: + message: 'Once done' +- route: + from: + uri: 'direct:in' + steps: + - delay: + constant: '3000' + - setBody: + simple: 'Hello {{greeted.subject}}!' + - log: + message: '${body}' diff --git a/core-starter/camel-k-starter/src/test/resources/logback.xml b/core-starter/camel-k-starter/src/test/resources/logback.xml new file mode 100644 index 00000000000..63aa9d3c96c --- /dev/null +++ b/core-starter/camel-k-starter/src/test/resources/logback.xml @@ -0,0 +1,46 @@ + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + target/camel-k-test.log + + + + + + + + + + + + diff --git a/core-starter/pom.xml b/core-starter/pom.xml index fd0acb9e987..99691181f95 100644 --- a/core-starter/pom.xml +++ b/core-starter/pom.xml @@ -33,5 +33,6 @@ camel-spring-boot-starter camel-spring-boot-engine-starter camel-spring-boot-xml-starter + camel-k-starter 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 9a71b8b1b2c..88cbe111582 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,12 +22,10 @@ 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.spi.SpringInjector; import org.apache.camel.spring.xml.XmlCamelContextConfigurer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; -import org.springframework.context.ConfigurableApplicationContext; /** * Used to merge Camel Spring Boot configuration with {@link org.apache.camel.CamelContext} that @@ -47,6 +45,7 @@ public void configure(ApplicationContext applicationContext, SpringCamelContext // 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); } catch (Exception e) { throw RuntimeCamelException.wrapRuntimeCamelException(e); 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 06e904e4073..da76cf374b9 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 @@ -31,7 +31,6 @@ import org.apache.camel.component.properties.PropertiesComponent; import org.apache.camel.component.properties.PropertiesParser; import org.apache.camel.main.DefaultConfigurationConfigurer; -import org.apache.camel.main.DefaultRoutesCollector; import org.apache.camel.main.RoutesCollector; import org.apache.camel.model.Model; import org.apache.camel.spi.BeanRepository; @@ -96,10 +95,12 @@ public class CamelAutoConfiguration { CamelContext camelContext(ApplicationContext applicationContext, CamelConfigurationProperties config, CamelBeanPostProcessor beanPostProcessor) throws Exception { + CamelContext camelContext = new SpringBootCamelContext(applicationContext, config.isWarnOnEarlyShutdown()); // bean post processor is created before CamelContext beanPostProcessor.setCamelContext(camelContext); camelContext.getCamelContextExtension().addContextPlugin(CamelBeanPostProcessor.class, beanPostProcessor); + return doConfigureCamelContext(applicationContext, camelContext, config); }