blob: 0d7c0c3e7bb08f9745974f04c638aef31a8d4e10 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.main;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.camel.CamelContext;
import org.apache.camel.Component;
import org.apache.camel.ExtendedCamelContext;
import org.apache.camel.NoSuchLanguageException;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.PropertyBindingException;
import org.apache.camel.RoutesBuilder;
import org.apache.camel.RuntimeCamelException;
import org.apache.camel.builder.ThreadPoolProfileBuilder;
import org.apache.camel.health.HealthCheck;
import org.apache.camel.health.HealthCheckConfiguration;
import org.apache.camel.health.HealthCheckRegistry;
import org.apache.camel.health.HealthCheckRepository;
import org.apache.camel.model.FaultToleranceConfigurationDefinition;
import org.apache.camel.model.HystrixConfigurationDefinition;
import org.apache.camel.model.Model;
import org.apache.camel.model.ModelCamelContext;
import org.apache.camel.model.Resilience4jConfigurationDefinition;
import org.apache.camel.model.RouteDefinition;
import org.apache.camel.saga.CamelSagaService;
import org.apache.camel.spi.CamelBeanPostProcessor;
import org.apache.camel.spi.DataFormat;
import org.apache.camel.spi.Language;
import org.apache.camel.spi.PropertiesComponent;
import org.apache.camel.spi.PropertyConfigurer;
import org.apache.camel.spi.PropertyConfigurerGetter;
import org.apache.camel.spi.RouteTemplateParameterSource;
import org.apache.camel.spi.ThreadPoolProfile;
import org.apache.camel.support.CamelContextHelper;
import org.apache.camel.support.LifecycleStrategySupport;
import org.apache.camel.support.PropertyBindingSupport;
import org.apache.camel.support.ResourceHelper;
import org.apache.camel.support.service.BaseService;
import org.apache.camel.support.service.ServiceHelper;
import org.apache.camel.util.FileUtil;
import org.apache.camel.util.IOHelper;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.OrderedProperties;
import org.apache.camel.util.PropertiesHelper;
import org.apache.camel.util.StringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.camel.main.MainHelper.loadEnvironmentVariablesAsProperties;
import static org.apache.camel.main.MainHelper.lookupPropertyFromSysOrEnv;
import static org.apache.camel.support.ObjectHelper.invokeMethod;
import static org.apache.camel.util.ReflectionHelper.findMethod;
import static org.apache.camel.util.StringHelper.matches;
/**
* Base class for main implementations to allow bootstrapping Camel in standalone mode.
*/
public abstract class BaseMainSupport extends BaseService {
public static final String DEFAULT_PROPERTY_PLACEHOLDER_LOCATION = "classpath:application.properties;optional=true";
public static final String INITIAL_PROPERTIES_LOCATION = "camel.main.initial-properties-location";
public static final String OVERRIDE_PROPERTIES_LOCATION = "camel.main.override-properties-location";
public static final String PROPERTY_PLACEHOLDER_LOCATION = "camel.main.property-placeholder-location";
private static final Logger LOG = LoggerFactory.getLogger(BaseMainSupport.class);
private static final String SENSITIVE_KEYS = "passphrase|password|secretkey|accesstoken|clientsecret|authorizationtoken|sasljaasconfig";
protected volatile CamelContext camelContext;
protected volatile ProducerTemplate camelTemplate;
protected final List<MainListener> listeners = new ArrayList<>();
protected final MainConfigurationProperties mainConfigurationProperties = new MainConfigurationProperties();
protected final Properties wildcardProperties = new OrderedProperties();
protected RoutesCollector routesCollector = new DefaultRoutesCollector();
protected String propertyPlaceholderLocations;
protected String defaultPropertyPlaceholderLocation = DEFAULT_PROPERTY_PLACEHOLDER_LOCATION;
protected Properties initialProperties;
protected Properties overrideProperties;
protected static String optionKey(String key) {
// as we ignore case for property names we should use keys in same case and without dashes
key = StringHelper.dashToCamelCase(key);
return key;
}
protected static boolean setPropertiesOnTarget(CamelContext context, Object target, Object source) throws Exception {
ObjectHelper.notNull(context, "context");
ObjectHelper.notNull(target, "target");
boolean rc = false;
PropertyConfigurer targetConfigurer = null;
if (target instanceof Component) {
// the component needs to be initialized to have the configurer ready
ServiceHelper.initService(target);
targetConfigurer = ((Component) target).getComponentPropertyConfigurer();
}
if (targetConfigurer == null) {
String name = target.getClass().getSimpleName();
if (target instanceof ExtendedCamelContext) {
// special for camel context itself as we have an extended configurer
name = "ExtendedCamelContext";
}
// see if there is a configurer for it
targetConfigurer = context.adapt(ExtendedCamelContext.class)
.getConfigurerResolver().resolvePropertyConfigurer(name, context);
}
PropertyConfigurer sourceConfigurer = null;
if (source instanceof Component) {
// the component needs to be initialized to have the configurer ready
ServiceHelper.initService(source);
sourceConfigurer = ((Component) source).getComponentPropertyConfigurer();
}
if (sourceConfigurer == null) {
String name = source.getClass().getSimpleName();
if (source instanceof ExtendedCamelContext) {
// special for camel context itself as we have an extended configurer
name = "ExtendedCamelContext";
}
// see if there is a configurer for it
sourceConfigurer = context.adapt(ExtendedCamelContext.class)
.getConfigurerResolver().resolvePropertyConfigurer(name, context);
}
if (targetConfigurer != null && sourceConfigurer instanceof PropertyConfigurerGetter) {
PropertyConfigurerGetter getter = (PropertyConfigurerGetter) sourceConfigurer;
for (String key : getter.getAllOptions(source).keySet()) {
Object value = getter.getOptionValue(source, key, true);
if (value != null) {
rc |= targetConfigurer.configure(context, target, key, value, true);
}
}
}
return rc;
}
protected static boolean setPropertiesOnTarget(CamelContext context, Object target, Map<String, Object> properties,
String optionPrefix, boolean failIfNotSet, boolean ignoreCase,
Map<String, String> autoConfiguredProperties) throws Exception {
ObjectHelper.notNull(context, "context");
ObjectHelper.notNull(target, "target");
ObjectHelper.notNull(properties, "properties");
boolean rc = false;
PropertyConfigurer configurer = null;
if (target instanceof Component) {
// the component needs to be initialized to have the configurer ready
ServiceHelper.initService(target);
configurer = ((Component) target).getComponentPropertyConfigurer();
}
if (configurer == null) {
String name = target.getClass().getSimpleName();
if (target instanceof ExtendedCamelContext) {
// special for camel context itself as we have an extended configurer
name = "ExtendedCamelContext";
}
// see if there is a configurer for it
configurer = context.adapt(ExtendedCamelContext.class)
.getConfigurerResolver().resolvePropertyConfigurer(name, context);
}
try {
// keep a reference of the original keys
Map<String, Object> backup = new LinkedHashMap<>(properties);
rc = PropertyBindingSupport.build()
.withMandatory(failIfNotSet)
.withRemoveParameters(true)
.withConfigurer(configurer)
.withIgnoreCase(ignoreCase)
.bind(context, target, properties);
for (Map.Entry<String, Object> entry: backup.entrySet()) {
if (entry.getValue() != null && !properties.containsKey(entry.getKey())) {
String prefix = optionPrefix;
if (prefix != null && !prefix.endsWith(".")) {
prefix = "." + prefix;
}
LOG.debug("Configured property: {}{}={} on bean: {}", prefix, entry.getKey(), entry.getValue(), target);
autoConfiguredProperties.put(prefix + entry.getKey(), entry.getValue().toString());
}
}
} catch (PropertyBindingException e) {
String key = e.getOptionKey();
if (key == null) {
String prefix = e.getOptionPrefix();
if (prefix != null && !prefix.endsWith(".")) {
prefix = "." + prefix;
}
key = prefix != null
? prefix + "." + e.getPropertyName()
: e.getPropertyName();
}
if (failIfNotSet) {
// enrich the error with more precise details with option prefix and key
throw new PropertyBindingException(e.getTarget(), e.getPropertyName(), e.getValue(), optionPrefix, key, e.getCause());
} else {
LOG.debug("Error configuring property (" + key + ") with name: " + e.getPropertyName() + ") on bean: " + target
+ " with value: " + e.getValue() + ". This exception is ignored as failIfNotSet=false.", e);
}
}
return rc;
}
/**
* To configure options on Camel Main.
*/
public MainConfigurationProperties configure() {
return mainConfigurationProperties;
}
public RoutesCollector getRoutesCollector() {
return routesCollector;
}
/**
* To use a custom {@link RoutesCollector}.
*/
public void setRoutesCollector(RoutesCollector routesCollector) {
this.routesCollector = routesCollector;
}
public String getPropertyPlaceholderLocations() {
return propertyPlaceholderLocations;
}
/**
* A list of locations to add for loading properties.
* You can use comma to separate multiple locations.
*/
public void setPropertyPlaceholderLocations(String location) {
this.propertyPlaceholderLocations = location;
}
public String getDefaultPropertyPlaceholderLocation() {
return defaultPropertyPlaceholderLocation;
}
/**
* Set the default location for application properties if no locations have been set.
* If the value is set to "false" or empty, the default location is not taken into account.
* <p/>
* Default value is "classpath:application.properties;optional=true".
*/
public void setDefaultPropertyPlaceholderLocation(String defaultPropertyPlaceholderLocation) {
this.defaultPropertyPlaceholderLocation = defaultPropertyPlaceholderLocation;
}
@Deprecated
public boolean isAutoConfigurationEnabled() {
return mainConfigurationProperties.isAutoConfigurationEnabled();
}
/**
* Whether auto-configuration of components/dataformats/languages is enabled or not.
* When enabled the configuration parameters are loaded from the properties component
* and configured as defaults (similar to spring-boot auto-configuration). You can prefix
* the parameters in the properties file with:
* - camel.component.name.option1=value1
* - camel.component.name.option2=value2
* - camel.dataformat.name.option1=value1
* - camel.dataformat.name.option2=value2
* - camel.language.name.option1=value1
* - camel.language.name.option2=value2
* Where name is the name of the component, dataformat or language such as seda,direct,jaxb.
* <p/>
* The auto-configuration also works for any options on components
* that is a complex type (not standard Java type) and there has been an explicit single
* bean instance registered to the Camel registry via the {@link org.apache.camel.spi.Registry#bind(String, Object)} method
* or by using the {@link org.apache.camel.BindToRegistry} annotation style.
* <p/>
* This option is default enabled.
* @deprecated use {@link #configure()}
*/
@Deprecated
public void setAutoConfigurationEnabled(boolean autoConfigurationEnabled) {
mainConfigurationProperties.setAutoConfigurationEnabled(autoConfigurationEnabled);
}
public Properties getInitialProperties() {
return initialProperties;
}
/**
* Sets initial properties for the properties component,
* which will be used before any locations are resolved.
*/
public void setInitialProperties(Properties initialProperties) {
this.initialProperties = initialProperties;
}
/**
* Adds a property (initial) for the properties component,
* which will be used before any locations are resolved.
*
* @param key the property key
* @param value the property value
*
* @see #addInitialProperty(String, String)
* @see #addOverrideProperty(String, String)
*/
public void addProperty(String key, String value) {
addInitialProperty(key, value);
}
/**
* Adds a initial property for the properties component,
* which will be used before any locations are resolved.
*
* @param key the property key
* @param value the property value
*/
public void addInitialProperty(String key, String value) {
if (initialProperties == null) {
initialProperties = new OrderedProperties();
}
initialProperties.setProperty(key, value);
}
public Properties getOverrideProperties() {
return overrideProperties;
}
/**
* Sets a special list of override properties that take precedence
* and will use first, if a property exist.
*/
public void setOverrideProperties(Properties overrideProperties) {
this.overrideProperties = overrideProperties;
}
/**
* Adds an override property that take precedence
* and will use first, if a property exist.
*
* @param key the property key
* @param value the property value
*/
public void addOverrideProperty(String key, String value) {
if (overrideProperties == null) {
overrideProperties = new OrderedProperties();
}
overrideProperties.setProperty(key, value);
}
public CamelContext getCamelContext() {
return camelContext;
}
/**
* Adds a {@link MainListener} to receive callbacks when the main is started or stopping
*
* @param listener the listener
*/
public void addMainListener(MainListener listener) {
listeners.add(listener);
}
/**
* Removes the {@link MainListener}
*
* @param listener the listener
*/
public void removeMainListener(MainListener listener) {
listeners.remove(listener);
}
public List<RouteDefinition> getRouteDefinitions() {
List<RouteDefinition> answer = new ArrayList<>();
if (camelContext != null) {
answer.addAll(camelContext.getExtension(Model.class).getRouteDefinitions());
}
return answer;
}
public ProducerTemplate getCamelTemplate() throws Exception {
if (camelTemplate == null) {
camelTemplate = findOrCreateCamelTemplate();
}
return camelTemplate;
}
protected abstract ProducerTemplate findOrCreateCamelTemplate();
protected abstract CamelContext createCamelContext();
protected void initCamelContext() throws Exception {
camelContext = createCamelContext();
if (camelContext == null) {
throw new IllegalStateException("Created CamelContext is null");
}
postProcessCamelContext(camelContext);
}
protected void loadRouteBuilders(CamelContext camelContext) throws Exception {
// lets use Camel's bean post processor on any existing route builder classes
// so the instance has some support for dependency injection
CamelBeanPostProcessor postProcessor = camelContext.adapt(ExtendedCamelContext.class).getBeanPostProcessor();
for (RoutesBuilder routeBuilder : mainConfigurationProperties.getRoutesBuilders()) {
postProcessor.postProcessBeforeInitialization(routeBuilder, routeBuilder.getClass().getName());
postProcessor.postProcessAfterInitialization(routeBuilder, routeBuilder.getClass().getName());
}
if (mainConfigurationProperties.getRoutesBuilderClasses() != null) {
String[] routeClasses = mainConfigurationProperties.getRoutesBuilderClasses().split(",");
for (String routeClass : routeClasses) {
Class<RoutesBuilder> routeClazz = camelContext.getClassResolver().resolveClass(routeClass, RoutesBuilder.class);
if (routeClazz == null) {
LOG.warn("Unable to resolve class: {}", routeClass);
continue;
}
// lets use Camel's injector so the class has some support for dependency injection
RoutesBuilder builder = camelContext.getInjector().newInstance(routeClazz);
mainConfigurationProperties.addRoutesBuilder(builder);
}
}
if (mainConfigurationProperties.getPackageScanRouteBuilders() != null) {
String[] pkgs = mainConfigurationProperties.getPackageScanRouteBuilders().split(",");
Set<Class<?>> set = camelContext.adapt(ExtendedCamelContext.class)
.getPackageScanClassResolver()
.findImplementations(RoutesBuilder.class, pkgs);
for (Class<?> routeClazz : set) {
Object builder = camelContext.getInjector().newInstance(routeClazz);
if (builder instanceof RoutesBuilder) {
mainConfigurationProperties.addRoutesBuilder((RoutesBuilder) builder);
} else {
LOG.warn("Class {} is not a RouteBuilder class", routeClazz);
}
}
}
}
protected void loadConfigurations(CamelContext camelContext) throws Exception {
// lets use Camel's bean post processor on any existing configuration classes
// so the instance has some support for dependency injection
CamelBeanPostProcessor postProcessor = camelContext.adapt(ExtendedCamelContext.class).getBeanPostProcessor();
for (Object configuration : mainConfigurationProperties.getConfigurations()) {
postProcessor.postProcessBeforeInitialization(configuration, configuration.getClass().getName());
postProcessor.postProcessAfterInitialization(configuration, configuration.getClass().getName());
}
if (mainConfigurationProperties.getConfigurationClasses() != null) {
String[] configClasses = mainConfigurationProperties.getConfigurationClasses().split(",");
for (String configClass : configClasses) {
Class<?> configClazz = camelContext.getClassResolver().resolveClass(configClass);
// lets use Camel's injector so the class has some support for dependency injection
Object config = camelContext.getInjector().newInstance(configClazz);
mainConfigurationProperties.addConfiguration(config);
}
}
for (Object config : mainConfigurationProperties.getConfigurations()) {
// invoke configure method if exists
Method method = findMethod(config.getClass(), "configure");
if (method != null) {
LOG.info("Calling configure method on configuration class: {}", config.getClass().getName());
invokeMethod(method, config);
} else {
Object arg = camelContext;
method = findMethod(config.getClass(), "configure", CamelContext.class);
if (method == null) {
method = findMethod(config.getClass(), "configure", Main.class);
arg = this;
}
if (method != null) {
LOG.info("Calling configure method on configuration class: {}", config.getClass().getName());
invokeMethod(method, config, arg);
}
}
}
}
protected void configurePropertiesService(CamelContext camelContext) throws Exception {
PropertiesComponent pc = camelContext.getPropertiesComponent();
if (pc.getLocations().isEmpty()) {
String locations = propertyPlaceholderLocations;
if (locations == null) {
locations = lookupPropertyFromSysOrEnv(PROPERTY_PLACEHOLDER_LOCATION).orElse(defaultPropertyPlaceholderLocation);
}
if (!Objects.equals(locations, "false")) {
pc.addLocation(locations);
LOG.info("Using properties from: {}", locations);
}
}
Properties ip = initialProperties;
if (ip == null || ip.isEmpty()) {
Optional<String> location = lookupPropertyFromSysOrEnv(INITIAL_PROPERTIES_LOCATION);
if (location.isPresent()) {
try (InputStream is = ResourceHelper.resolveMandatoryResourceAsInputStream(camelContext, location.get())) {
ip = new Properties();
ip.load(is);
}
}
}
if (ip != null) {
pc.setInitialProperties(ip);
}
Properties op = overrideProperties;
if (op == null || op.isEmpty()) {
Optional<String> location = lookupPropertyFromSysOrEnv(OVERRIDE_PROPERTIES_LOCATION);
if (location.isPresent()) {
try (InputStream is = ResourceHelper.resolveMandatoryResourceAsInputStream(camelContext, location.get())) {
op = new Properties();
op.load(is);
}
}
}
if (op != null) {
pc.setOverrideProperties(op);
}
}
protected void configureLifecycle(CamelContext camelContext) throws Exception {
}
protected void autoconfigure(CamelContext camelContext) throws Exception {
// gathers the properties (key=value) that was auto-configured
final Map<String, String> autoConfiguredProperties = new LinkedHashMap<>();
// need to eager allow to auto-configure properties component
if (mainConfigurationProperties.isAutoConfigurationEnabled()) {
autoConfigurationFailFast(camelContext, autoConfiguredProperties);
autoConfigurationPropertiesComponent(camelContext, autoConfiguredProperties);
autoConfigurationMainConfiguration(camelContext, mainConfigurationProperties, autoConfiguredProperties);
}
// configure from main configuration properties
doConfigureCamelContextFromMainConfiguration(camelContext, mainConfigurationProperties, autoConfiguredProperties);
// try to load configuration classes
loadConfigurations(camelContext);
// conventional configuration via properties to allow configuring options on
// component, dataformat, and languages (like spring-boot auto-configuration)
if (mainConfigurationProperties.isAutowireComponentProperties() || mainConfigurationProperties.isAutowireComponentPropertiesDeep()) {
autowireConfigurationFromRegistry(
camelContext,
mainConfigurationProperties.isAutowireComponentPropertiesNonNullOnly(),
mainConfigurationProperties.isAutowireComponentPropertiesDeep());
}
if (mainConfigurationProperties.isAutoConfigurationEnabled()) {
autoConfigurationFromProperties(camelContext, autoConfiguredProperties);
}
if (mainConfigurationProperties.isAutowireComponentProperties() || mainConfigurationProperties.isAutowireComponentPropertiesDeep()) {
autowireWildcardProperties(camelContext);
}
// tracing may be enabled by some other property (i.e. camel.context.tracer.exchange-formatter.show-headers)
if (camelContext.isTracing() && !mainConfigurationProperties.isTracing()) {
camelContext.setTracing(Boolean.FALSE);
}
// log summary of configurations
if (mainConfigurationProperties.isAutoConfigurationLogSummary() && !autoConfiguredProperties.isEmpty()) {
LOG.info("Auto-configuration summary:");
autoConfiguredProperties.forEach((k, v) -> {
boolean sensitive = SENSITIVE_KEYS.contains(k.toLowerCase(Locale.ENGLISH));
if (sensitive) {
LOG.info("\t{}=xxxxxx", k);
} else {
LOG.info("\t{}={}", k, v);
}
});
}
}
protected void configureRoutes(CamelContext camelContext) throws Exception {
// try to load the route builders
loadRouteBuilders(camelContext);
// then configure and add the routes
RoutesConfigurer configurer = new RoutesConfigurer(routesCollector, mainConfigurationProperties.getRoutesBuilders());
configurer.configureRoutes(camelContext, mainConfigurationProperties);
// allow to do additional configuration after routes are configured
for (Object config : mainConfigurationProperties.getConfigurations()) {
// invoke configure method if exists
Method method = findMethod(config.getClass(), "configureRouteTemplates");
if (method != null) {
LOG.info("Calling configureRouteTemplates method on configuration class: {}", config.getClass().getName());
invokeMethod(method, config);
} else {
Object arg = camelContext;
method = findMethod(config.getClass(), "configureRouteTemplates", CamelContext.class);
if (method == null) {
method = findMethod(config.getClass(), "configureRouteTemplates", Main.class);
arg = this;
}
if (method != null) {
LOG.info("Calling configureRouteTemplates method on configuration class: {}", config.getClass().getName());
invokeMethod(method, config, arg);
}
}
}
}
protected void postProcessCamelContext(CamelContext camelContext) throws Exception {
// ensure camel is initialized
camelContext.build();
for (MainListener listener : listeners) {
listener.beforeInitialize(this);
}
configurePropertiesService(camelContext);
// allow to do configuration before its started
for (MainListener listener : listeners) {
listener.beforeConfigure(this);
}
autoconfigure(camelContext);
configureLifecycle(camelContext);
configureRoutes(camelContext);
// allow to do configuration before its started
for (MainListener listener : listeners) {
listener.afterConfigure(this);
listener.configure(camelContext);
}
}
protected void autoConfigurationFailFast(CamelContext camelContext, Map<String, String> autoConfiguredProperties) throws Exception {
// load properties
Properties prop = camelContext.getPropertiesComponent().loadProperties(name -> name.startsWith("camel."));
LOG.debug("Properties from Camel properties component:");
for (String key : prop.stringPropertyNames()) {
LOG.debug(" {}={}", key, prop.getProperty(key));
}
// special for environment-variable-enabled as we need to know this early before we set all the other options
Object envEnabled = prop.remove("camel.main.autoConfigurationEnvironmentVariablesEnabled");
if (envEnabled == null) {
envEnabled = prop.remove("camel.main.auto-configuration-environment-variables-enabled");
if (envEnabled != null) {
PropertyBindingSupport.build().withMandatory(true).withIgnoreCase(true).bind(camelContext, mainConfigurationProperties, "autoConfigurationEnvironmentVariablesEnabled", envEnabled);
autoConfiguredProperties.put("camel.main.auto-configuration-environment-variables-enabled", envEnabled.toString());
}
}
// load properties from ENV (override existing)
Properties propENV = null;
if (mainConfigurationProperties.isAutoConfigurationEnvironmentVariablesEnabled()) {
propENV = loadEnvironmentVariablesAsProperties(new String[]{"camel.main."});
if (!propENV.isEmpty()) {
prop.putAll(propENV);
LOG.debug("Properties from OS environment variables:");
for (String key : propENV.stringPropertyNames()) {
LOG.debug(" {}={}", key, propENV.getProperty(key));
}
}
}
// special for fail-fast as we need to know this early before we set all the other options
Object failFast = propENV != null ? propENV.remove("camel.main.autoconfigurationfailfast") : null;
if (failFast != null) {
PropertyBindingSupport.build().withMandatory(true).withIgnoreCase(true).bind(camelContext, mainConfigurationProperties, "autoConfigurationFailFast", failFast);
} else {
failFast = prop.remove("camel.main.autoConfigurationFailFast");
if (failFast == null) {
failFast = prop.remove("camel.main.auto-configuration-fail-fast");
}
if (failFast != null) {
PropertyBindingSupport.build().withMandatory(true).withIgnoreCase(true).bind(camelContext, mainConfigurationProperties, "autoConfigurationFailFast", failFast);
autoConfiguredProperties.put("camel.main.auto-configuration-fail-fast", failFast.toString());
}
}
}
/**
* Configures CamelContext from the {@link MainConfigurationProperties} properties.
*/
protected void doConfigureCamelContextFromMainConfiguration(CamelContext camelContext, MainConfigurationProperties config,
Map<String, String> autoConfiguredProperties) throws Exception {
if (config.getFileConfigurations() != null) {
String[] locs = config.getFileConfigurations().split(",");
for (String loc : locs) {
String path = FileUtil.onlyPath(loc);
if (path != null) {
String pattern = loc.length() > path.length() ? loc.substring(path.length() + 1) : null;
File[] files = new File(path).listFiles(f -> matches(pattern, f.getName()));
if (files != null) {
for (File file : files) {
Properties props = new Properties();
try (FileInputStream is = new FileInputStream(file)) {
props.load(is);
}
if (!props.isEmpty()) {
if (overrideProperties == null) {
// setup override properties on properties component
overrideProperties = new Properties();
PropertiesComponent pc = camelContext.getPropertiesComponent();
pc.setOverrideProperties(overrideProperties);
}
LOG.info("Loaded additional {} properties from file: {}", props.size(), file);
overrideProperties.putAll(props);
}
}
}
}
}
}
// configure the common/default options
DefaultConfigurationConfigurer.configure(camelContext, config);
// lookup and configure SPI beans
DefaultConfigurationConfigurer.afterConfigure(camelContext);
// now configure context/hystrix/resilience4j/rest with additional properties
Properties prop = camelContext.getPropertiesComponent().loadProperties(name -> name.startsWith("camel."));
// load properties from ENV (override existing)
if (mainConfigurationProperties.isAutoConfigurationEnvironmentVariablesEnabled()) {
Properties propENV = loadEnvironmentVariablesAsProperties(new String[]{"camel.component.properties."});
if (!propENV.isEmpty()) {
prop.putAll(propENV);
LOG.debug("Properties from OS environment variables:");
for (String key : propENV.stringPropertyNames()) {
LOG.debug(" {}={}", key, propENV.getProperty(key));
}
}
}
Map<String, Object> contextProperties = new LinkedHashMap<>();
Map<String, Object> hystrixProperties = new LinkedHashMap<>();
Map<String, Object> resilience4jProperties = new LinkedHashMap<>();
Map<String, Object> faultToleranceProperties = new LinkedHashMap<>();
Map<String, Object> restProperties = new LinkedHashMap<>();
Map<String, Object> threadPoolProperties = new LinkedHashMap<>();
Map<String, Object> healthProperties = new LinkedHashMap<>();
Map<String, Object> lraProperties = new LinkedHashMap<>();
Map<String, Object> routeTemplateProperties = new LinkedHashMap<>();
Map<String, Object> beansProperties = new LinkedHashMap<>();
for (String key : prop.stringPropertyNames()) {
if (key.startsWith("camel.context.")) {
// grab the value
String value = prop.getProperty(key);
String option = key.substring(14);
validateOptionAndValue(key, option, value);
contextProperties.put(optionKey(option), value);
} else if (key.startsWith("camel.hystrix.")) {
// grab the value
String value = prop.getProperty(key);
String option = key.substring(14);
validateOptionAndValue(key, option, value);
hystrixProperties.put(optionKey(option), value);
} else if (key.startsWith("camel.resilience4j.")) {
// grab the value
String value = prop.getProperty(key);
String option = key.substring(19);
validateOptionAndValue(key, option, value);
resilience4jProperties.put(optionKey(option), value);
} else if (key.startsWith("camel.faulttolerance.")) {
// grab the value
String value = prop.getProperty(key);
String option = key.substring(21);
validateOptionAndValue(key, option, value);
faultToleranceProperties.put(optionKey(option), value);
} else if (key.startsWith("camel.rest.")) {
// grab the value
String value = prop.getProperty(key);
String option = key.substring(11);
validateOptionAndValue(key, option, value);
restProperties.put(optionKey(option), value);
} else if (key.startsWith("camel.threadpool.")) {
// grab the value
String value = prop.getProperty(key);
String option = key.substring(17);
validateOptionAndValue(key, option, value);
threadPoolProperties.put(optionKey(option), value);
} else if (key.startsWith("camel.health.")) {
// grab the value
String value = prop.getProperty(key);
String option = key.substring(13);
validateOptionAndValue(key, option, value);
healthProperties.put(optionKey(option), value);
} else if (key.startsWith("camel.lra.")) {
// grab the value
String value = prop.getProperty(key);
String option = key.substring(10);
validateOptionAndValue(key, option, value);
lraProperties.put(optionKey(option), value);
} else if (key.startsWith("camel.route-template")) {
// grab the value
String value = prop.getProperty(key);
String option = key.substring(20);
validateOptionAndValue(key, option, value);
routeTemplateProperties.put(optionKey(option), value);
} else if (key.startsWith("camel.beans.")) {
// grab the value
String value = prop.getProperty(key);
String option = key.substring(12);
validateOptionAndValue(key, option, value);
beansProperties.put(optionKey(option), value);
}
}
ModelCamelContext model = camelContext.adapt(ModelCamelContext.class);
// create beans first as they may be used later
if (!beansProperties.isEmpty()) {
LOG.debug("Creating and binding beans to registry from loaded properties: {}", beansProperties.size());
bindBeansToRegistry(camelContext, beansProperties, "camel.beans.",
mainConfigurationProperties.isAutoConfigurationFailFast(), true, autoConfiguredProperties);
}
if (!contextProperties.isEmpty()) {
LOG.debug("Auto-configuring CamelContext from loaded properties: {}", contextProperties.size());
setPropertiesOnTarget(camelContext, camelContext, contextProperties, "camel.context.",
mainConfigurationProperties.isAutoConfigurationFailFast(), true, autoConfiguredProperties);
}
HystrixConfigurationProperties hystrix = mainConfigurationProperties.hystrix();
if (!hystrixProperties.isEmpty()) {
LOG.debug("Auto-configuring Hystrix Circuit Breaker EIP from loaded properties: {}", hystrixProperties.size());
setPropertiesOnTarget(camelContext, hystrix, hystrixProperties, "camel.hystrix.",
mainConfigurationProperties.isAutoConfigurationFailFast(), true, autoConfiguredProperties);
}
HystrixConfigurationDefinition hystrixModel = model.getHystrixConfiguration(null);
if (hystrixModel == null) {
hystrixModel = new HystrixConfigurationDefinition();
model.setHystrixConfiguration(hystrixModel);
}
setPropertiesOnTarget(camelContext, hystrixModel, hystrix);
Resilience4jConfigurationProperties resilience4j = mainConfigurationProperties.resilience4j();
if (!resilience4jProperties.isEmpty()) {
LOG.debug("Auto-configuring Resilience4j Circuit Breaker EIP from loaded properties: {}", resilience4jProperties.size());
setPropertiesOnTarget(camelContext, resilience4j, resilience4jProperties, "camel.resilience4j.",
mainConfigurationProperties.isAutoConfigurationFailFast(), true, autoConfiguredProperties);
}
Resilience4jConfigurationDefinition resilience4jModel = model.getResilience4jConfiguration(null);
if (resilience4jModel == null) {
resilience4jModel = new Resilience4jConfigurationDefinition();
model.setResilience4jConfiguration(resilience4jModel);
}
setPropertiesOnTarget(camelContext, resilience4jModel, resilience4j);
FaultToleranceConfigurationProperties faultTolerance = mainConfigurationProperties.faultTolerance();
if (!faultToleranceProperties.isEmpty()) {
LOG.debug("Auto-configuring MicroProfile Fault Tolerance Circuit Breaker EIP from loaded properties: {}", faultToleranceProperties.size());
setPropertiesOnTarget(camelContext, faultTolerance, faultToleranceProperties, "camel.faulttolerance.",
mainConfigurationProperties.isAutoConfigurationFailFast(), true, autoConfiguredProperties);
}
FaultToleranceConfigurationDefinition faultToleranceModel = model.getFaultToleranceConfiguration(null);
if (faultToleranceModel == null) {
faultToleranceModel = new FaultToleranceConfigurationDefinition();
model.setFaultToleranceConfiguration(faultToleranceModel);
}
setPropertiesOnTarget(camelContext, faultToleranceModel, faultTolerance);
RestConfigurationProperties rest = mainConfigurationProperties.rest();
if (!restProperties.isEmpty()) {
LOG.debug("Auto-configuring Rest DSL from loaded properties: {}", restProperties.size());
setPropertiesOnTarget(camelContext, rest, restProperties, "camel.rest.",
mainConfigurationProperties.isAutoConfigurationFailFast(), true, autoConfiguredProperties);
}
camelContext.setRestConfiguration(rest);
if (!threadPoolProperties.isEmpty()) {
LOG.debug("Auto-configuring Thread Pool from loaded properties: {}", threadPoolProperties.size());
setThreadPoolProperties(camelContext, threadPoolProperties, mainConfigurationProperties.isAutoConfigurationFailFast(), autoConfiguredProperties);
}
if (!healthProperties.isEmpty()) {
LOG.debug("Auto-configuring HealthCheck from loaded properties: {}", healthProperties.size());
setHealthCheckProperties(camelContext, healthProperties, mainConfigurationProperties.isAutoConfigurationFailFast(), autoConfiguredProperties);
}
if (!routeTemplateProperties.isEmpty()) {
LOG.debug("Auto-configuring Route templates from loaded properties: {}", routeTemplateProperties.size());
setRouteTemplateProperties(camelContext, routeTemplateProperties, mainConfigurationProperties.isAutoConfigurationFailFast(), autoConfiguredProperties);
}
if (!lraProperties.isEmpty()) {
LOG.debug("Auto-configuring Saga LRA from loaded properties: {}", lraProperties.size());
setLraCheckProperties(camelContext, lraProperties, mainConfigurationProperties.isAutoConfigurationFailFast(), autoConfiguredProperties);
}
// log which options was not set
if (!beansProperties.isEmpty()) {
beansProperties.forEach((k, v) -> {
LOG.warn("Property not auto-configured: camel.beans.{}={}", k, v);
});
}
if (!contextProperties.isEmpty()) {
contextProperties.forEach((k, v) -> {
LOG.warn("Property not auto-configured: camel.context.{}={} on bean: {}", k, v, camelContext);
});
}
if (!hystrixProperties.isEmpty()) {
hystrixProperties.forEach((k, v) -> {
LOG.warn("Property not auto-configured: camel.hystrix.{}={} on bean: {}", k, v, hystrix);
});
}
if (!resilience4jProperties.isEmpty()) {
resilience4jProperties.forEach((k, v) -> {
LOG.warn("Property not auto-configured: camel.resilience4j.{}={} on bean: {}", k, v, resilience4j);
});
}
if (!faultToleranceProperties.isEmpty()) {
faultToleranceProperties.forEach((k, v) -> {
LOG.warn("Property not auto-configured: camel.faulttolerance.{}={} on bean: {}", k, v, faultTolerance);
});
}
if (!restProperties.isEmpty()) {
restProperties.forEach((k, v) -> {
LOG.warn("Property not auto-configured: camel.rest.{}={} on bean: {}", k, v, rest);
});
}
if (!threadPoolProperties.isEmpty()) {
threadPoolProperties.forEach((k, v) -> {
LOG.warn("Property not auto-configured: camel.threadpool.{}={}", k, v);
});
}
if (!healthProperties.isEmpty()) {
healthProperties.forEach((k, v) -> {
LOG.warn("Property not auto-configured: camel.health.{}={}", k, v);
});
}
if (!routeTemplateProperties.isEmpty()) {
routeTemplateProperties.forEach((k, v) -> {
LOG.warn("Property not auto-configured: camel.routetemplate.{}={}", k, v);
});
}
if (!lraProperties.isEmpty()) {
lraProperties.forEach((k, v) -> {
LOG.warn("Property not auto-configured: camel.lra.{}={}", k, v);
});
}
// and call after all properties are set
DefaultConfigurationConfigurer.afterPropertiesSet(camelContext);
}
private void setThreadPoolProperties(CamelContext camelContext, Map<String, Object> threadPoolProperties,
boolean failIfNotSet, Map<String, String> autoConfiguredProperties) throws Exception {
ThreadPoolConfigurationProperties tp = mainConfigurationProperties.threadPool();
// extract all config to know their parent ids so we can set the values afterwards
Map<String, Object> hcConfig = PropertiesHelper.extractProperties(threadPoolProperties, "config", false);
Map<String, ThreadPoolProfileConfigurationProperties> tpConfigs = new HashMap<>();
// build set of configuration objects
for (Map.Entry<String, Object> entry : hcConfig.entrySet()) {
String id = StringHelper.between(entry.getKey(), "[", "]");
if (id != null) {
ThreadPoolProfileConfigurationProperties tcp = tpConfigs.get(id);
if (tcp == null) {
tcp = new ThreadPoolProfileConfigurationProperties();
tcp.setId(id);
tpConfigs.put(id, tcp);
}
}
}
if (tp.getConfig() != null) {
tp.getConfig().putAll(tpConfigs);
} else {
tp.setConfig(tpConfigs);
}
setPropertiesOnTarget(camelContext, tp, threadPoolProperties, "camel.threadpool.",
mainConfigurationProperties.isAutoConfigurationFailFast(), true, autoConfiguredProperties);
// okay we have all properties set so we should be able to create thread pool profiles and register them on camel
final ThreadPoolProfile dp = new ThreadPoolProfileBuilder("default")
.poolSize(tp.getPoolSize())
.maxPoolSize(tp.getMaxPoolSize())
.keepAliveTime(tp.getKeepAliveTime(), tp.getTimeUnit())
.maxQueueSize(tp.getMaxQueueSize())
.allowCoreThreadTimeOut(tp.getAllowCoreThreadTimeOut())
.rejectedPolicy(tp.getRejectedPolicy()).build();
for (ThreadPoolProfileConfigurationProperties config : tp.getConfig().values()) {
ThreadPoolProfileBuilder builder = new ThreadPoolProfileBuilder(config.getId(), dp);
final ThreadPoolProfile tpp = builder.poolSize(config.getPoolSize())
.maxPoolSize(config.getMaxPoolSize())
.keepAliveTime(config.getKeepAliveTime(), config.getTimeUnit())
.maxQueueSize(config.getMaxQueueSize())
.allowCoreThreadTimeOut(config.getAllowCoreThreadTimeOut())
.rejectedPolicy(config.getRejectedPolicy()).build();
if (!tpp.isEmpty()) {
camelContext.getExecutorServiceManager().registerThreadPoolProfile(tpp);
}
}
if (!dp.isEmpty()) {
dp.setDefaultProfile(true);
camelContext.getExecutorServiceManager().setDefaultThreadPoolProfile(dp);
}
}
private void setRouteTemplateProperties(CamelContext camelContext, Map<String, Object> routeTemplateProperties,
boolean failIfNotSet, Map<String, String> autoConfiguredProperties) throws Exception {
// store the route template parameters as a source and register it on the camel context
PropertiesRouteTemplateParametersSource source = new PropertiesRouteTemplateParametersSource();
for (Map.Entry<String, Object> entry : routeTemplateProperties.entrySet()) {
String id = StringHelper.between(entry.getKey(), "[", "]");
String key = StringHelper.after(entry.getKey(), "].");
source.addParameter(id, key, entry.getValue());
}
camelContext.getRegistry().bind("CamelMainRouteTemplateParametersSource", RouteTemplateParameterSource.class, source);
// lets sort by keys
Map<String, Object> sorted = new TreeMap<>(routeTemplateProperties);
sorted.forEach((k, v) -> {
autoConfiguredProperties.put("camel.route-template" + k, v.toString());
});
routeTemplateProperties.clear();
}
private void setHealthCheckProperties(CamelContext camelContext, Map<String, Object> healthCheckProperties,
boolean failIfNotSet, Map<String, String> autoConfiguredProperties) throws Exception {
HealthCheckRegistry hcr = camelContext.getExtension(HealthCheckRegistry.class);
if (hcr == null) {
LOG.warn("Cannot find HealthCheckRegistry from classpath. Add camel-health to classpath.");
return;
}
HealthConfigurationProperties health = mainConfigurationProperties.health();
// extract all config to know their parent ids so we can set the values afterwards
Map<String, Object> hcConfig = PropertiesHelper.extractProperties(healthCheckProperties, "config", false);
Map<String, HealthCheckConfigurationProperties> hcConfigs = new HashMap<>();
// build set of configuration objects
for (Map.Entry<String, Object> entry : hcConfig.entrySet()) {
String parent = StringHelper.between(entry.getKey(), "[", "]");
if (parent != null) {
HealthCheckConfigurationProperties hcp = hcConfigs.get(parent);
if (hcp == null) {
hcp = new HealthCheckConfigurationProperties();
hcConfigs.put(parent, hcp);
}
}
}
if (health.getConfig() != null) {
health.getConfig().putAll(hcConfigs);
} else {
health.setConfig(hcConfigs);
}
setPropertiesOnTarget(camelContext, health, healthCheckProperties, "camel.health.",
mainConfigurationProperties.isAutoConfigurationFailFast(), true, autoConfiguredProperties);
if (health.getEnabled() != null) {
hcr.setEnabled(health.getEnabled());
}
// context is enabled by default
if (hcr.isEnabled() && (!health.getConfig().containsKey("context") || health.getContextEnabled() != null)) {
HealthCheck hc = (HealthCheck) hcr.resolveById("context");
if (hc != null) {
if (health.getContextEnabled() != null) {
hc.getConfiguration().setEnabled(health.getContextEnabled());
}
hcr.register(hc);
}
}
// routes is enabled by default
if (hcr.isEnabled() && (!health.getConfig().containsKey("routes") || health.getRoutesEnabled() != null)) {
HealthCheckRepository hc = hcr.getRepository("routes").orElse((HealthCheckRepository) hcr.resolveById("routes"));
if (hc != null) {
if (health.getRoutesEnabled() != null) {
hc.setEnabled(health.getRoutesEnabled());
}
hcr.register(hc);
}
}
// registry is enabled by default
if (hcr.isEnabled() && (!health.getConfig().containsKey("registry") || health.getRegistryEnabled() != null)) {
hcr.getRepository("registry").ifPresent(h -> {
if (health.getRegistryEnabled() != null) {
h.setEnabled(health.getRegistryEnabled());
}
});
}
// configure health checks configurations
for (String id : health.getConfig().keySet()) {
HealthCheckConfiguration hcc = health.getConfig().get(id);
String parent = hcc.getParent();
// lookup health check by id
Object hc = hcr.getCheck(parent).orElse(null);
if (hc == null) {
hc = hcr.resolveById(parent);
if (hc == null) {
LOG.warn("Cannot resolve HealthCheck with id: " + parent + " from classpath.");
continue;
}
hcr.register(hc);
if (hc instanceof HealthCheck) {
((HealthCheck) hc).getConfiguration().setParent(hcc.getParent());
((HealthCheck) hc).getConfiguration().setEnabled(hcc.isEnabled());
((HealthCheck) hc).getConfiguration().setFailureThreshold(hcc.getFailureThreshold());
((HealthCheck) hc).getConfiguration().setInterval(hcc.getInterval());
} else if (hc instanceof HealthCheckRepository) {
((HealthCheckRepository) hc).setEnabled(hcc.isEnabled());
((HealthCheckRepository) hc).addConfiguration(id, hcc);
}
}
}
}
private void setLraCheckProperties(CamelContext camelContext, Map<String, Object> lraProperties,
boolean failIfNotSet, Map<String, String> autoConfiguredProperties) throws Exception {
Object obj = lraProperties.get("enabled");
if (obj != null) {
autoConfiguredProperties.put("camel.lra.enabled", obj.toString());
}
boolean enabled = obj != null ? CamelContextHelper.parseBoolean(camelContext, obj.toString()) : true;
if (enabled) {
CamelSagaService css = resolveLraSagaService(camelContext);
setPropertiesOnTarget(camelContext, css, lraProperties, "camel.lra.", failIfNotSet, true, autoConfiguredProperties);
}
}
private static CamelSagaService resolveLraSagaService(CamelContext camelContext) throws Exception {
// lookup in service registry first
Set<CamelSagaService> set = camelContext.getRegistry().findByType(CamelSagaService.class);
if (set.size() == 1) {
return set.iterator().next();
}
CamelSagaService answer = camelContext.adapt(ExtendedCamelContext.class).getDefaultFactoryFinder()
.newInstance("lra-saga-service", CamelSagaService.class)
.orElseThrow(() -> new IllegalArgumentException("Cannot find LRASagaService on classpath. "
+ "Add camel-lra to classpath."));
// add as service so its discover by saga eip
camelContext.addService(answer, true, false);
return answer;
}
private void bindBeansToRegistry(CamelContext camelContext, Map<String, Object> properties,
String optionPrefix, boolean failIfNotSet, boolean ignoreCase,
Map<String, String> autoConfiguredProperties) throws Exception {
// make defensive copy as we mutate the map
Set<String> keys = new LinkedHashSet<>(properties.keySet());
for (String key : keys) {
if (key.indexOf('.') == -1) {
// create beans first and then set properties
String name = key;
Object value = properties.remove(key);
Object bean = PropertyBindingSupport.resolveBean(camelContext, name, value);
if (bean == null) {
throw new IllegalArgumentException("Cannot create/resolve bean with name " + name + " from value: " + value);
}
// register bean
camelContext.getRegistry().bind(name, bean);
autoConfiguredProperties.put(optionPrefix + key, value.toString());
// and then configure properties on the beans afterwards
Map<String, Object> config = PropertiesHelper.extractProperties(properties, key + ".");
setPropertiesOnTarget(camelContext, bean, config, optionPrefix + key + ".", failIfNotSet, ignoreCase, autoConfiguredProperties);
LOG.info("Binding bean: {} (type: {}) to the registry", key, ObjectHelper.classCanonicalName(bean));
}
}
}
protected void autoConfigurationPropertiesComponent(CamelContext camelContext, Map<String, String> autoConfiguredProperties) throws Exception {
// load properties
Properties prop = camelContext.getPropertiesComponent().loadProperties(name -> name.startsWith("camel."));
// load properties from ENV (override existing)
if (mainConfigurationProperties.isAutoConfigurationEnvironmentVariablesEnabled()) {
Properties propENV = loadEnvironmentVariablesAsProperties(new String[]{"camel.component.properties."});
if (!propENV.isEmpty()) {
prop.putAll(propENV);
}
}
Map<String, Object> properties = new LinkedHashMap<>();
for (String key : prop.stringPropertyNames()) {
if (key.startsWith("camel.component.properties.")) {
int dot = key.indexOf('.', 26);
String option = dot == -1 ? "" : key.substring(dot + 1);
String value = prop.getProperty(key, "");
validateOptionAndValue(key, option, value);
properties.put(optionKey(option), value);
}
}
if (!properties.isEmpty()) {
LOG.debug("Auto-configuring properties component from loaded properties: {}", properties.size());
setPropertiesOnTarget(camelContext, camelContext.getPropertiesComponent(), properties, "camel.component.properties.",
mainConfigurationProperties.isAutoConfigurationFailFast(), true, autoConfiguredProperties);
}
// log which options was not set
if (!properties.isEmpty()) {
properties.forEach((k, v) -> {
LOG.warn("Property not auto-configured: camel.component.properties.{}={} on object: {}", k, v, camelContext.getPropertiesComponent());
});
}
}
protected void autoConfigurationMainConfiguration(CamelContext camelContext, MainConfigurationProperties config, Map<String, String> autoConfiguredProperties) throws Exception {
// load properties
Properties prop = camelContext.getPropertiesComponent().loadProperties(name -> name.startsWith("camel."));
// load properties from ENV (override existing)
if (mainConfigurationProperties.isAutoConfigurationEnvironmentVariablesEnabled()) {
Properties propENV = loadEnvironmentVariablesAsProperties(new String[]{"camel.main."});
propENV.remove(INITIAL_PROPERTIES_LOCATION.replace('-', '.'));
propENV.remove(OVERRIDE_PROPERTIES_LOCATION.replace('-', '.'));
propENV.remove(PROPERTY_PLACEHOLDER_LOCATION.replace('-', '.'));
if (!propENV.isEmpty()) {
prop.putAll(propENV);
}
}
Map<String, Object> properties = new LinkedHashMap<>();
for (String key : prop.stringPropertyNames()) {
if (key.startsWith("camel.main.")) {
// grab the value
String value = prop.getProperty(key);
String option = key.substring(11);
validateOptionAndValue(key, option, value);
properties.put(optionKey(option), value);
}
}
if (!properties.isEmpty()) {
LOG.debug("Auto-configuring main from loaded properties: {}", properties.size());
setPropertiesOnTarget(camelContext, config, properties, "camel.main.",
mainConfigurationProperties.isAutoConfigurationFailFast(), true, autoConfiguredProperties);
}
// log which options was not set
if (!properties.isEmpty()) {
properties.forEach((k, v) -> {
LOG.warn("Property not auto-configured: camel.main.{}={} on bean: {}", k, v, config);
});
}
}
protected void autoConfigurationFromProperties(CamelContext camelContext, Map<String, String> autoConfiguredProperties) throws Exception {
// load optional META-INF/services/org/apache/camel/autowire.properties
Properties prop = new OrderedProperties();
try {
InputStream is = camelContext.getClassResolver().loadResourceAsStream("/META-INF/services/org/apache/camel/autowire.properties");
if (is != null) {
prop.load(is);
if (!prop.isEmpty()) {
LOG.info("Autowired enabled from classpath: META-INF/services/org/apache/camel/autowire.properties with {} properties", prop.size());
if (LOG.isDebugEnabled()) {
LOG.debug("Properties from classpath: META-INF/services/org/apache/camel/autowire.properties:");
for (String key : prop.stringPropertyNames()) {
LOG.debug(" {}={}", key, prop.getProperty(key));
}
}
}
IOHelper.close(is);
}
} catch (Throwable e) {
// ignore as this file is optional
}
// load properties from properties component (override existing)
Properties propPC = camelContext.getPropertiesComponent().loadProperties(name -> name.startsWith("camel."));
prop.putAll(propPC);
// load properties from ENV (override existing)
if (mainConfigurationProperties.isAutoConfigurationEnvironmentVariablesEnabled()) {
Properties propENV = loadEnvironmentVariablesAsProperties(new String[]{"camel.component.", "camel.dataformat.", "camel.language."});
if (!propENV.isEmpty()) {
prop.putAll(propENV);
}
}
Map<PropertyOptionKey, Map<String, Object>> properties = new LinkedHashMap<>();
// filter out wildcard properties
for (String key : prop.stringPropertyNames()) {
if (key.contains("*")) {
wildcardProperties.put(key, prop.getProperty(key));
}
}
// and remove wildcards
for (String key : wildcardProperties.stringPropertyNames()) {
prop.remove(key);
}
for (String key : prop.stringPropertyNames()) {
computeProperties("camel.component.", key, prop, properties, name -> {
// its an existing component name
Component target = camelContext.getComponent(name);
if (target == null) {
throw new IllegalArgumentException("Error configuring property: " + key + " because cannot find component with name " + name
+ ". Make sure you have the component on the classpath");
}
return Collections.singleton(target);
});
computeProperties("camel.dataformat.", key, prop, properties, name -> {
DataFormat target = camelContext.resolveDataFormat(name);
if (target == null) {
throw new IllegalArgumentException("Error configuring property: " + key + " because cannot find dataformat with name " + name
+ ". Make sure you have the dataformat on the classpath");
}
return Collections.singleton(target);
});
computeProperties("camel.language.", key, prop, properties, name -> {
Language target;
try {
target = camelContext.resolveLanguage(name);
} catch (NoSuchLanguageException e) {
throw new IllegalArgumentException("Error configuring property: " + key + " because cannot find language with name " + name
+ ". Make sure you have the language on the classpath");
}
return Collections.singleton(target);
});
}
if (!properties.isEmpty()) {
long total = properties.values().stream().mapToLong(Map::size).sum();
LOG.debug("Auto-configuring {} components/dataformat/languages from loaded properties: {}", properties.size(), total);
}
for (Map.Entry<PropertyOptionKey, Map<String, Object>> entry: properties.entrySet()) {
setPropertiesOnTarget(
camelContext,
entry.getKey().getInstance(),
entry.getValue(),
entry.getKey().getOptionPrefix(),
mainConfigurationProperties.isAutoConfigurationFailFast(),
true,
autoConfiguredProperties);
}
// log which options was not set
if (!properties.isEmpty()) {
for (PropertyOptionKey pok : properties.keySet()) {
Map<String, Object> values = properties.get(pok);
values.forEach((k, v) -> {
String stringValue = v != null ? v.toString() : null;
LOG.warn("Property ({}={}) not auto-configured with name: {} on bean: {} with value: {}", pok.getOptionPrefix() + "." + k, stringValue, k, pok.getInstance(), stringValue);
});
}
}
}
protected void autowireConfigurationFromRegistry(CamelContext camelContext, boolean bindNullOnly, boolean deepNesting) throws Exception {
camelContext.addLifecycleStrategy(new LifecycleStrategySupport() {
@Override
public void onComponentAdd(String name, Component component) {
PropertyBindingSupport.autowireSingletonPropertiesFromRegistry(camelContext, component, bindNullOnly, deepNesting, (obj, propertyName, type, value) -> {
LOG.info("Autowired property: {} on component: {} as exactly one instance of type: {} found in the registry",
propertyName, component.getClass().getSimpleName(), type.getName());
});
}
});
}
protected void autowireWildcardProperties(CamelContext camelContext) {
if (wildcardProperties.isEmpty()) {
return;
}
// autowire any pre-existing components as they have been added before we are invoked
for (String name : camelContext.getComponentNames()) {
Component comp = camelContext.getComponent(name);
doAutowireWildcardProperties(name, comp);
}
// and autowire any new components that may be added in the future
camelContext.addLifecycleStrategy(new LifecycleStrategySupport() {
@Override
public void onComponentAdd(String name, Component component) {
doAutowireWildcardProperties(name, component);
}
});
}
protected void doAutowireWildcardProperties(String name, Component component) {
Map<PropertyOptionKey, Map<String, Object>> properties = new LinkedHashMap<>();
Map<String, String> autoConfiguredProperties = new LinkedHashMap<>();
String match = ("camel.component." + name).toLowerCase(Locale.ENGLISH);
for (String key : wildcardProperties.stringPropertyNames()) {
String mKey = key.substring(0, key.indexOf('*')).toLowerCase(Locale.ENGLISH);
if (match.startsWith(mKey)) {
computeProperties("camel.component.", key, wildcardProperties, properties, s -> Collections.singleton(component));
}
}
try {
for (Map.Entry<PropertyOptionKey, Map<String, Object>> entry : properties.entrySet()) {
setPropertiesOnTarget(
camelContext,
entry.getKey().getInstance(),
entry.getValue(),
entry.getKey().getOptionPrefix(),
mainConfigurationProperties.isAutoConfigurationFailFast(),
true,
autoConfiguredProperties);
}
// log summary of configurations
if (mainConfigurationProperties.isAutoConfigurationLogSummary() && !autoConfiguredProperties.isEmpty()) {
LOG.info("Auto-configuration component {} summary:", name);
autoConfiguredProperties.forEach((k, v) -> {
boolean sensitive = SENSITIVE_KEYS.contains(k.toLowerCase(Locale.ENGLISH));
if (sensitive) {
LOG.info("\t{}=xxxxxx", k);
} else {
LOG.info("\t{}={}", k, v);
}
});
}
} catch (Exception e) {
throw RuntimeCamelException.wrapRuntimeException(e);
}
}
protected static void validateOptionAndValue(String key, String option, String value) {
if (ObjectHelper.isEmpty(option)) {
throw new IllegalArgumentException("Error configuring property: " + key + " because option is empty");
}
if (ObjectHelper.isEmpty(value)) {
throw new IllegalArgumentException("Error configuring property: " + key + " because value is empty");
}
}
private static final class PropertyOptionKey {
private final Object instance;
private final String optionPrefix;
private PropertyOptionKey(Object instance, String optionPrefix) {
this.instance = ObjectHelper.notNull(instance, "instance");
this.optionPrefix = ObjectHelper.notNull(optionPrefix, "optionPrefix");
}
public Object getInstance() {
return instance;
}
public String getOptionPrefix() {
return optionPrefix;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof PropertyOptionKey)) {
return false;
}
PropertyOptionKey key = (PropertyOptionKey) o;
return Objects.equals(instance, key.instance)
&& Objects.equals(optionPrefix, key.optionPrefix);
}
@Override
public int hashCode() {
return Objects.hash(instance, optionPrefix);
}
}
protected static void computeProperties(String keyPrefix, String key, Properties prop, Map<PropertyOptionKey, Map<String, Object>> properties,
Function<String, Iterable<Object>> supplier) {
if (key.startsWith(keyPrefix)) {
// grab name
final int dot = key.indexOf('.', keyPrefix.length());
final String name = dot == -1 ? key.substring(keyPrefix.length()) : key.substring(keyPrefix.length(), dot);
// enabled is a virtual property
if ("enabled".equals(name)) {
return;
}
// skip properties as its already keyPrefix earlier
if ("properties".equals(name)) {
return;
}
// determine if the service is enabled or not by taking into account two options:
//
// 1. ${keyPrefix}.enabled = true|false
// 2. ${keyPrefix}.${name}.enabled = true|false
//
// The option [2] has the higher priority so as example:
//
// camel.component.enabled = false
// camel.component.seda.enabled = true
//
// enables auto configuration of the seda component only
if (!isServiceEnabled(keyPrefix, name, prop)) {
return;
}
String prefix = dot == -1 ? "" : key.substring(0, dot + 1);
String option = dot == -1 ? "" : key.substring(dot + 1);
String value = prop.getProperty(key, "");
// enabled is a virtual property
if ("enabled".equalsIgnoreCase(option)) {
return;
}
validateOptionAndValue(key, option, value);
Iterable<Object> targets = supplier.apply(name);
for (Object target : targets) {
PropertyOptionKey pok = new PropertyOptionKey(target, prefix);
Map<String, Object> values = properties.computeIfAbsent(pok, k -> new LinkedHashMap<>());
// we ignore case for property keys (so we should store them in canonical style
values.put(optionKey(option), value);
}
}
}
protected static boolean isServiceEnabled(String prefix, String name, Properties properties) {
ObjectHelper.notNull(prefix, "prefix");
ObjectHelper.notNull(name, "name");
ObjectHelper.notNull(properties, "properties");
if (!prefix.endsWith(".")) {
prefix = prefix + ".";
}
final String group = properties.getProperty(prefix + "enabled", "true");
final String item = properties.getProperty(prefix + name + ".enabled", group);
return Boolean.parseBoolean(item);
}
}