/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.camel.spring.boot; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.apache.camel.CamelContext; import org.apache.camel.ConsumerTemplate; import org.apache.camel.Exchange; import org.apache.camel.ProducerTemplate; import org.apache.camel.TypeConverters; import org.apache.camel.component.properties.PropertiesComponent; import org.apache.camel.component.properties.PropertiesParser; import org.apache.camel.impl.FileWatcherReloadStrategy; import org.apache.camel.processor.interceptor.BacklogTracer; import org.apache.camel.processor.interceptor.DefaultTraceFormatter; import org.apache.camel.processor.interceptor.HandleFault; import org.apache.camel.processor.interceptor.TraceFormatter; import org.apache.camel.processor.interceptor.Tracer; import org.apache.camel.spi.AsyncProcessorAwaitManager; import org.apache.camel.spi.EndpointStrategy; import org.apache.camel.spi.EventFactory; import org.apache.camel.spi.EventNotifier; import org.apache.camel.spi.InflightRepository; import org.apache.camel.spi.InterceptStrategy; import org.apache.camel.spi.LifecycleStrategy; import org.apache.camel.spi.ManagementNamingStrategy; import org.apache.camel.spi.ManagementStrategy; import org.apache.camel.spi.ReloadStrategy; import org.apache.camel.spi.RoutePolicyFactory; import org.apache.camel.spi.RuntimeEndpointRegistry; import org.apache.camel.spi.ShutdownStrategy; import org.apache.camel.spi.StreamCachingStrategy; import org.apache.camel.spi.ThreadPoolProfile; import org.apache.camel.spi.UnitOfWorkFactory; import org.apache.camel.spring.CamelBeanPostProcessor; import org.apache.camel.spring.SpringCamelContext; import org.apache.camel.util.ObjectHelper; import org.apache.camel.util.jsse.GlobalSSLContextParametersSupplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.env.MutablePropertySources; @Configuration @EnableConfigurationProperties(CamelConfigurationProperties.class) @Import(TypeConversionConfiguration.class) public class CamelAutoConfiguration { private static final Logger LOG = LoggerFactory.getLogger(CamelAutoConfiguration.class); /** * Spring-aware Camel context for the application. Auto-detects and loads all routes available in the Spring context. */ @Bean @ConditionalOnMissingBean(CamelContext.class) CamelContext camelContext(ApplicationContext applicationContext, CamelConfigurationProperties config) { if (ObjectHelper.isNotEmpty(config.getFileConfigurations())) { Environment env = applicationContext.getEnvironment(); if (env instanceof ConfigurableEnvironment) { MutablePropertySources sources = ((ConfigurableEnvironment) env).getPropertySources(); if (sources != null) { if (!sources.contains("camel-file-configuration")) { sources.addFirst(new FilePropertySource("camel-file-configuration", applicationContext, config.getFileConfigurations())); } } } } CamelContext camelContext = new SpringCamelContext(applicationContext); SpringCamelContext.setNoStart(true); if (!config.isJmxEnabled()) { camelContext.disableJMX(); } if (config.getName() != null) { ((SpringCamelContext) camelContext).setName(config.getName()); } if (config.getShutdownTimeout() > 0) { camelContext.getShutdownStrategy().setTimeout(config.getShutdownTimeout()); } camelContext.getShutdownStrategy().setSuppressLoggingOnTimeout(config.isShutdownSuppressLoggingOnTimeout()); camelContext.getShutdownStrategy().setShutdownNowOnTimeout(config.isShutdownNowOnTimeout()); camelContext.getShutdownStrategy().setShutdownRoutesInReverseOrder(config.isShutdownRoutesInReverseOrder()); camelContext.getShutdownStrategy().setLogInflightExchangesOnTimeout(config.isShutdownLogInflightExchangesOnTimeout()); if (config.getLogDebugMaxChars() > 0) { camelContext.getGlobalOptions().put(Exchange.LOG_DEBUG_BODY_MAX_CHARS, "" + config.getLogDebugMaxChars()); } // stream caching camelContext.setStreamCaching(config.isStreamCachingEnabled()); camelContext.getStreamCachingStrategy().setAnySpoolRules(config.isStreamCachingAnySpoolRules()); camelContext.getStreamCachingStrategy().setBufferSize(config.getStreamCachingBufferSize()); camelContext.getStreamCachingStrategy().setRemoveSpoolDirectoryWhenStopping(config.isStreamCachingRemoveSpoolDirectoryWhenStopping()); camelContext.getStreamCachingStrategy().setSpoolChiper(config.getStreamCachingSpoolChiper()); if (config.getStreamCachingSpoolDirectory() != null) { camelContext.getStreamCachingStrategy().setSpoolDirectory(config.getStreamCachingSpoolDirectory()); } if (config.getStreamCachingSpoolThreshold() != 0) { camelContext.getStreamCachingStrategy().setSpoolThreshold(config.getStreamCachingSpoolThreshold()); } if (config.getStreamCachingSpoolUsedHeapMemoryLimit() != null) { StreamCachingStrategy.SpoolUsedHeapMemoryLimit limit; if ("Committed".equalsIgnoreCase(config.getStreamCachingSpoolUsedHeapMemoryLimit())) { limit = StreamCachingStrategy.SpoolUsedHeapMemoryLimit.Committed; } else if ("Max".equalsIgnoreCase(config.getStreamCachingSpoolUsedHeapMemoryLimit())) { limit = StreamCachingStrategy.SpoolUsedHeapMemoryLimit.Max; } else { throw new IllegalArgumentException("Invalid option " + config.getStreamCachingSpoolUsedHeapMemoryLimit() + " must either be Committed or Max"); } camelContext.getStreamCachingStrategy().setSpoolUsedHeapMemoryLimit(limit); } if (config.getStreamCachingSpoolUsedHeapMemoryThreshold() != 0) { camelContext.getStreamCachingStrategy().setSpoolUsedHeapMemoryThreshold(config.getStreamCachingSpoolUsedHeapMemoryThreshold()); } camelContext.setMessageHistory(config.isMessageHistory()); camelContext.setLogMask(config.isLogMask()); camelContext.setLogExhaustedMessageBody(config.isLogExhaustedMessageBody()); camelContext.setHandleFault(config.isHandleFault()); camelContext.setAutoStartup(config.isAutoStartup()); camelContext.setAllowUseOriginalMessage(config.isAllowUseOriginalMessage()); if (camelContext.getManagementStrategy().getManagementAgent() != null) { camelContext.getManagementStrategy().getManagementAgent().setEndpointRuntimeStatisticsEnabled(config.isEndpointRuntimeStatisticsEnabled()); camelContext.getManagementStrategy().getManagementAgent().setStatisticsLevel(config.getJmxManagementStatisticsLevel()); camelContext.getManagementStrategy().getManagementAgent().setManagementNamePattern(config.getJmxManagementNamePattern()); camelContext.getManagementStrategy().getManagementAgent().setCreateConnector(config.isJmxCreateConnector()); } camelContext.setPackageScanClassResolver(new FatJarPackageScanClassResolver()); // tracing camelContext.setTracing(config.isTracing()); if (camelContext.getDefaultTracer() instanceof Tracer) { Tracer tracer = (Tracer) camelContext.getDefaultTracer(); if (tracer.getDefaultTraceFormatter() != null) { DefaultTraceFormatter formatter = tracer.getDefaultTraceFormatter(); if (config.getTracerFormatterBreadCrumbLength() != null) { formatter.setBreadCrumbLength(config.getTracerFormatterBreadCrumbLength()); } if (config.getTracerFormatterMaxChars() != null) { formatter.setMaxChars(config.getTracerFormatterMaxChars()); } if (config.getTracerFormatterNodeLength() != null) { formatter.setNodeLength(config.getTracerFormatterNodeLength()); } formatter.setShowBody(config.isTraceFormatterShowBody()); formatter.setShowBodyType(config.isTracerFormatterShowBodyType()); formatter.setShowBreadCrumb(config.isTraceFormatterShowBreadCrumb()); formatter.setShowException(config.isTraceFormatterShowException()); formatter.setShowExchangeId(config.isTraceFormatterShowExchangeId()); formatter.setShowExchangePattern(config.isTraceFormatterShowExchangePattern()); formatter.setShowHeaders(config.isTraceFormatterShowHeaders()); formatter.setShowNode(config.isTraceFormatterShowNode()); formatter.setShowProperties(config.isTraceFormatterShowProperties()); formatter.setShowRouteId(config.isTraceFormatterShowRouteId()); formatter.setShowShortExchangeId(config.isTraceFormatterShowShortExchangeId()); } } if (config.getXmlRoutesReloadDirectory() != null) { ReloadStrategy reload = new FileWatcherReloadStrategy(config.getXmlRoutesReloadDirectory()); camelContext.setReloadStrategy(reload); } // additional advanced configuration which is not configured using CamelConfigurationProperties afterPropertiesSet(applicationContext, camelContext); return camelContext; } @Bean CamelSpringBootApplicationController applicationController(ApplicationContext applicationContext, CamelContext camelContext) { // CAMEL-10279: We have to call setNoStart(true) here so that if a <camelContext> is imported via // @ImportResource then it does not get started before the RoutesCollector gets a chance to add any // routes found in RouteBuilders. Even if no RouteBuilders are found, the RoutesCollector will handle // starting the the Camel Context. SpringCamelContext.setNoStart(true); return new CamelSpringBootApplicationController(applicationContext, camelContext); } @Bean @ConditionalOnMissingBean(RoutesCollector.class) RoutesCollector routesCollector(ApplicationContext applicationContext, CamelConfigurationProperties config) { Collection<CamelContextConfiguration> configurations = applicationContext.getBeansOfType(CamelContextConfiguration.class).values(); return new RoutesCollector(applicationContext, new ArrayList<CamelContextConfiguration>(configurations), config); } /** * Default producer template for the bootstrapped Camel context. */ @Bean(initMethod = "", destroyMethod = "") // Camel handles the lifecycle of this bean @ConditionalOnMissingBean(ProducerTemplate.class) ProducerTemplate producerTemplate(CamelContext camelContext, CamelConfigurationProperties config) throws Exception { final ProducerTemplate producerTemplate = camelContext.createProducerTemplate(config.getProducerTemplateCacheSize()); camelContext.addService(producerTemplate); return producerTemplate; } /** * Default consumer template for the bootstrapped Camel context. */ @Bean(initMethod = "", destroyMethod = "") // Camel handles the lifecycle of this bean @ConditionalOnMissingBean(ConsumerTemplate.class) ConsumerTemplate consumerTemplate(CamelContext camelContext, CamelConfigurationProperties config) throws Exception { final ConsumerTemplate consumerTemplate = camelContext.createConsumerTemplate(config.getConsumerTemplateCacheSize()); camelContext.addService(consumerTemplate); return consumerTemplate; } // SpringCamelContext integration @Bean PropertiesParser propertiesParser() { return new SpringPropertiesParser(); } @Bean(initMethod = "", destroyMethod = "") // Camel handles the lifecycle of this bean PropertiesComponent properties(CamelContext camelContext, PropertiesParser parser) { if (camelContext.hasComponent("properties") != null) { return camelContext.getComponent("properties", PropertiesComponent.class); } else { PropertiesComponent pc = new PropertiesComponent(); pc.setPropertiesParser(parser); return pc; } } /** * Camel post processor - required to support Camel annotations. */ @Bean CamelBeanPostProcessor camelBeanPostProcessor(ApplicationContext applicationContext) { CamelBeanPostProcessor processor = new CamelBeanPostProcessor(); processor.setApplicationContext(applicationContext); return processor; } /** * Performs additional configuration to lookup beans of Camel types to configure * advanced configurations. * <p/> * Similar code in camel-core-xml module in class org.apache.camel.core.xml.AbstractCamelContextFactoryBean. */ void afterPropertiesSet(ApplicationContext applicationContext, CamelContext camelContext) { Tracer tracer = getSingleBeanOfType(applicationContext, Tracer.class); if (tracer != null) { // use formatter if there is a TraceFormatter bean defined TraceFormatter formatter = getSingleBeanOfType(applicationContext, TraceFormatter.class); if (formatter != null) { tracer.setFormatter(formatter); } LOG.info("Using custom Tracer: {}", tracer); camelContext.addInterceptStrategy(tracer); } BacklogTracer backlogTracer = getSingleBeanOfType(applicationContext, BacklogTracer.class); if (backlogTracer != null) { LOG.info("Using custom BacklogTracer: {}", backlogTracer); camelContext.addInterceptStrategy(backlogTracer); } HandleFault handleFault = getSingleBeanOfType(applicationContext, HandleFault.class); if (handleFault != null) { LOG.info("Using custom HandleFault: {}", handleFault); camelContext.addInterceptStrategy(handleFault); } InflightRepository inflightRepository = getSingleBeanOfType(applicationContext, InflightRepository.class); if (inflightRepository != null) { LOG.info("Using custom InflightRepository: {}", inflightRepository); camelContext.setInflightRepository(inflightRepository); } AsyncProcessorAwaitManager asyncProcessorAwaitManager = getSingleBeanOfType(applicationContext, AsyncProcessorAwaitManager.class); if (asyncProcessorAwaitManager != null) { LOG.info("Using custom AsyncProcessorAwaitManager: {}", asyncProcessorAwaitManager); camelContext.setAsyncProcessorAwaitManager(asyncProcessorAwaitManager); } ManagementStrategy managementStrategy = getSingleBeanOfType(applicationContext, ManagementStrategy.class); if (managementStrategy != null) { LOG.info("Using custom ManagementStrategy: {}", managementStrategy); camelContext.setManagementStrategy(managementStrategy); } ManagementNamingStrategy managementNamingStrategy = getSingleBeanOfType(applicationContext, ManagementNamingStrategy.class); if (managementNamingStrategy != null) { LOG.info("Using custom ManagementNamingStrategy: {}", managementNamingStrategy); camelContext.getManagementStrategy().setManagementNamingStrategy(managementNamingStrategy); } EventFactory eventFactory = getSingleBeanOfType(applicationContext, EventFactory.class); if (eventFactory != null) { LOG.info("Using custom EventFactory: {}", eventFactory); camelContext.getManagementStrategy().setEventFactory(eventFactory); } UnitOfWorkFactory unitOfWorkFactory = getSingleBeanOfType(applicationContext, UnitOfWorkFactory.class); if (unitOfWorkFactory != null) { LOG.info("Using custom UnitOfWorkFactory: {}", unitOfWorkFactory); camelContext.setUnitOfWorkFactory(unitOfWorkFactory); } RuntimeEndpointRegistry runtimeEndpointRegistry = getSingleBeanOfType(applicationContext, RuntimeEndpointRegistry.class); if (runtimeEndpointRegistry != null) { LOG.info("Using custom RuntimeEndpointRegistry: {}", runtimeEndpointRegistry); camelContext.setRuntimeEndpointRegistry(runtimeEndpointRegistry); } // custom type converters defined as <bean>s Map<String, TypeConverters> typeConverters = applicationContext.getBeansOfType(TypeConverters.class); if (typeConverters != null && !typeConverters.isEmpty()) { for (Map.Entry<String, TypeConverters> entry : typeConverters.entrySet()) { TypeConverters converter = entry.getValue(); LOG.info("Adding custom TypeConverters with id: {} and implementation: {}", entry.getKey(), converter); camelContext.getTypeConverterRegistry().addTypeConverters(converter); } } // set the event notifier strategies if defined Map<String, EventNotifier> eventNotifiers = applicationContext.getBeansOfType(EventNotifier.class); if (eventNotifiers != null && !eventNotifiers.isEmpty()) { for (Map.Entry<String, EventNotifier> entry : eventNotifiers.entrySet()) { EventNotifier notifier = entry.getValue(); // do not add if already added, for instance a tracer that is also an InterceptStrategy class if (!camelContext.getManagementStrategy().getEventNotifiers().contains(notifier)) { LOG.info("Using custom EventNotifier with id: {} and implementation: {}", entry.getKey(), notifier); camelContext.getManagementStrategy().addEventNotifier(notifier); } } } // set endpoint strategies if defined Map<String, EndpointStrategy> endpointStrategies = applicationContext.getBeansOfType(EndpointStrategy.class); if (endpointStrategies != null && !endpointStrategies.isEmpty()) { for (Map.Entry<String, EndpointStrategy> entry : endpointStrategies.entrySet()) { EndpointStrategy strategy = entry.getValue(); LOG.info("Using custom EndpointStrategy with id: {} and implementation: {}", entry.getKey(), strategy); camelContext.addRegisterEndpointCallback(strategy); } } // shutdown ShutdownStrategy shutdownStrategy = getSingleBeanOfType(applicationContext, ShutdownStrategy.class); if (shutdownStrategy != null) { LOG.info("Using custom ShutdownStrategy: " + shutdownStrategy); camelContext.setShutdownStrategy(shutdownStrategy); } // add global interceptors Map<String, InterceptStrategy> interceptStrategies = applicationContext.getBeansOfType(InterceptStrategy.class); if (interceptStrategies != null && !interceptStrategies.isEmpty()) { for (Map.Entry<String, InterceptStrategy> entry : interceptStrategies.entrySet()) { InterceptStrategy strategy = entry.getValue(); // do not add if already added, for instance a tracer that is also an InterceptStrategy class if (!camelContext.getInterceptStrategies().contains(strategy)) { LOG.info("Using custom InterceptStrategy with id: {} and implementation: {}", entry.getKey(), strategy); camelContext.addInterceptStrategy(strategy); } } } // set the lifecycle strategy if defined Map<String, LifecycleStrategy> lifecycleStrategies = applicationContext.getBeansOfType(LifecycleStrategy.class); if (lifecycleStrategies != null && !lifecycleStrategies.isEmpty()) { for (Map.Entry<String, LifecycleStrategy> entry : lifecycleStrategies.entrySet()) { LifecycleStrategy strategy = entry.getValue(); // do not add if already added, for instance a tracer that is also an InterceptStrategy class if (!camelContext.getLifecycleStrategies().contains(strategy)) { LOG.info("Using custom LifecycleStrategy with id: {} and implementation: {}", entry.getKey(), strategy); camelContext.addLifecycleStrategy(strategy); } } } // add route policy factories Map<String, RoutePolicyFactory> routePolicyFactories = applicationContext.getBeansOfType(RoutePolicyFactory.class); if (routePolicyFactories != null && !routePolicyFactories.isEmpty()) { for (Map.Entry<String, RoutePolicyFactory> entry : routePolicyFactories.entrySet()) { RoutePolicyFactory factory = entry.getValue(); LOG.info("Using custom RoutePolicyFactory with id: {} and implementation: {}", entry.getKey(), factory); camelContext.addRoutePolicyFactory(factory); } } // add SSL context parameters GlobalSSLContextParametersSupplier sslContextParametersSupplier = getSingleBeanOfType(applicationContext, GlobalSSLContextParametersSupplier.class); if (sslContextParametersSupplier != null) { camelContext.setSSLContextParameters(sslContextParametersSupplier.get()); } // set the default thread pool profile if defined initThreadPoolProfiles(applicationContext, camelContext); } private void initThreadPoolProfiles(ApplicationContext applicationContext, CamelContext camelContext) { Set<String> defaultIds = new HashSet<String>(); // lookup and use custom profiles from the registry Map<String, ThreadPoolProfile> profiles = applicationContext.getBeansOfType(ThreadPoolProfile.class); if (profiles != null && !profiles.isEmpty()) { for (Map.Entry<String, ThreadPoolProfile> entry : profiles.entrySet()) { ThreadPoolProfile profile = entry.getValue(); // do not add if already added, for instance a tracer that is also an InterceptStrategy class if (profile.isDefaultProfile()) { LOG.info("Using custom default ThreadPoolProfile with id: {} and implementation: {}", entry.getKey(), profile); camelContext.getExecutorServiceManager().setDefaultThreadPoolProfile(profile); defaultIds.add(entry.getKey()); } else { camelContext.getExecutorServiceManager().registerThreadPoolProfile(profile); } } } // validate at most one is defined if (defaultIds.size() > 1) { throw new IllegalArgumentException("Only exactly one default ThreadPoolProfile is allowed, was " + defaultIds.size() + " ids: " + defaultIds); } } private <T> T getSingleBeanOfType(ApplicationContext applicationContext, Class<T> type) { Map<String, T> beans = applicationContext.getBeansOfType(type); if (beans.size() == 1) { return beans.values().iterator().next(); } else { return null; } } }