This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit 31407635006759958d927c01890a4efa2cad08c8 Author: Claus Ibsen <[email protected]> AuthorDate: Mon Jan 31 12:57:58 2022 +0100 CAMEL-17567: camel-main - Add basePackageScan to auto discover route builders, configuration classes, and type converters. --- .../main/camel-main-configuration-metadata.json | 3 +- .../org/apache/camel/ExtendedCamelContext.java | 14 +++ .../camel/spi/AnnotationScanTypeConverters.java | 2 +- .../camel/impl/engine/AbstractCamelContext.java | 16 ++- .../impl/engine/DefaultCamelBeanPostProcessor.java | 6 ++ .../apache/camel/impl/engine/DefaultInjector.java | 11 ++- .../converter/AnnotationTypeConverterLoader.java | 21 ++-- .../camel/impl/converter/DefaultTypeConverter.java | 12 ++- .../camel/impl/ExtendedCamelContextConfigurer.java | 6 ++ .../camel/impl/lw/LightweightCamelContext.java | 10 ++ .../impl/lw/LightweightRuntimeCamelContext.java | 12 +++ .../MainConfigurationPropertiesConfigurer.java | 20 ++-- .../camel-main-configuration-metadata.json | 3 +- core/camel-main/src/main/docs/main.adoc | 76 ++++++++++++++- .../org/apache/camel/main/BaseMainSupport.java | 53 +++++++++- .../org/apache/camel/main/CamelConfiguration.java} | 21 ++-- .../src/main/java/org/apache/camel/main/Main.java | 26 ++++- .../apache/camel/main/MainCommandLineSupport.java | 3 +- .../camel/main/MainConfigurationProperties.java | 107 +++++++++++++-------- .../java/org/apache/camel/main/MainSupport.java | 7 +- .../org/apache/camel/main/RoutesConfigurer.java | 14 +-- .../org/apache/camel/main/ContextEventsTest.java | 2 +- .../org/apache/camel/main/MainCustomizerTest.java | 8 +- .../camel/main/MainIoCNewRouteBuilderTest.java | 2 +- .../java/org/apache/camel/main/MainIoCTest.java | 4 +- .../main/MainRoutesCollectorPackageScanTest.java | 10 +- .../org/apache/camel/main/scan/MyConverter.java} | 28 +++--- .../camel/main/scan/MyScanConfiguration.java} | 27 +++--- .../ROOT/pages/camel-3x-upgrade-guide-3_16.adoc | 12 +++ .../modules/ROOT/pages/camel-3x-upgrade-guide.adoc | 1 + 30 files changed, 407 insertions(+), 130 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json index f68a484..0eaab4a 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json @@ -19,6 +19,8 @@ { "name": "camel.main.autoStartup", "description": "Sets whether the object should automatically start when Camel starts. Important: Currently only routes can be disabled, as CamelContext's are always started. Note: When setting auto startup false on CamelContext then that takes precedence and no routes are started. You would need to start CamelContext explicit using the org.apache.camel.CamelContext.start() method, to start the context, and then you would need to start the routes ma [...] { "name": "camel.main.autowiredEnabled", "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching type, which then gets configured on the component. This can be used for automatic configuring JDBC data sources, JMS connection factories, AWS Clients, etc. Default is true.", "sourceType": "org.apache.camel.main.DefaultConfiguration [...] { "name": "camel.main.backlogTracing", "description": "Sets whether backlog tracing is enabled or not. Default is false.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": "false" }, + { "name": "camel.main.basePackageScan", "description": "Package name to use as base (offset) for classpath scanning of RouteBuilder , and org.apache.camel.TypeConverter classes. If you are using Spring Boot then it is instead recommended to use Spring Boots component scanning and annotate your route builder classes with Component. In other words only use this for Camel Main in standalone mode.", "sourceType": "org.apache.camel.main.MainConfigurationProperties", "type": "string", "jav [...] + { "name": "camel.main.basePackageScanEnabled", "description": "Whether base package scan is enabled.", "sourceType": "org.apache.camel.main.MainConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": true }, { "name": "camel.main.beanIntrospectionExtendedStatistics", "description": "Sets whether bean introspection uses extended statistics. The default is false.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": "false" }, { "name": "camel.main.beanIntrospectionLoggingLevel", "description": "Sets the logging level used by bean introspection, logging activity of its usage. The default is TRACE.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "object", "javaType": "org.apache.camel.LoggingLevel", "enum": [ "ERROR", "WARN", "INFO", "DEBUG", "TRACE", "OFF" ] }, { "name": "camel.main.beanPostProcessorEnabled", "description": "Can be used to turn off bean post processing. Be careful to turn this off, as this means that beans that use Camel annotations such as org.apache.camel.EndpointInject , org.apache.camel.ProducerTemplate , org.apache.camel.Produce , org.apache.camel.Consume etc will not be injected and in use. Turning this off should only be done if you are sure you do not use any of these Camel features. Not all runtimes allow turning t [...] @@ -58,7 +60,6 @@ { "name": "camel.main.mdcLoggingKeysPattern", "description": "Sets the pattern used for determine which custom MDC keys to propagate during message routing when the routing engine continues routing asynchronously for the given message. Setting this pattern to will propagate all custom keys. Or setting the pattern to foo,bar will propagate any keys starting with either foo or bar. Notice that a set of standard Camel MDC keys are always propagated which starts with camel. as key name. [...] { "name": "camel.main.messageHistory", "description": "Sets whether message history is enabled or not. Default is false.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": "false" }, { "name": "camel.main.name", "description": "Sets the name of the CamelContext.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "string", "javaType": "java.lang.String" }, - { "name": "camel.main.packageScanRouteBuilders", "description": "Sets package names for scanning for org.apache.camel.builder.RouteBuilder classes as candidates to be included. If you are using Spring Boot then its instead recommended to use Spring Boots component scanning and annotate your route builder classes with Component. In other words only use this for Camel Main in standalone mode.", "sourceType": "org.apache.camel.main.MainConfigurationProperties", "type": "string", "javaTy [...] { "name": "camel.main.producerTemplateCacheSize", "description": "Producer template endpoints cache size.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "integer", "javaType": "int", "defaultValue": 1000 }, { "name": "camel.main.routeControllerBackOffDelay", "description": "Backoff delay in millis when restarting a route that failed to startup.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "integer", "javaType": "long" }, { "name": "camel.main.routeControllerBackOffMaxAttempts", "description": "Backoff maximum number of attempts to restart a route that failed to startup. When this threshold has been exceeded then the controller will give up attempting to restart the route, and the route will remain as stopped.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "integer", "javaType": "long" }, diff --git a/core/camel-api/src/main/java/org/apache/camel/ExtendedCamelContext.java b/core/camel-api/src/main/java/org/apache/camel/ExtendedCamelContext.java index 764ec0e..b02a103 100644 --- a/core/camel-api/src/main/java/org/apache/camel/ExtendedCamelContext.java +++ b/core/camel-api/src/main/java/org/apache/camel/ExtendedCamelContext.java @@ -830,4 +830,18 @@ public interface ExtendedCamelContext extends CamelContext { */ String resolvePropertyPlaceholders(String text, boolean keepUnresolvedOptional); + /** + * Package name to use as base (offset) for classpath scanning of custom services, type converters, and the likes + * + * @return the base package name (can bre null if not configured) + */ + String getBasePackageScan(); + + /** + * Package name to use as base (offset) for classpath scanning of custom services, type converters, and the likes + * + * @param basePackageScan the base package name + */ + void setBasePackageScan(String basePackageScan); + } diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/AnnotationScanTypeConverters.java b/core/camel-api/src/main/java/org/apache/camel/spi/AnnotationScanTypeConverters.java index 9f5e8d2..79d9007 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/AnnotationScanTypeConverters.java +++ b/core/camel-api/src/main/java/org/apache/camel/spi/AnnotationScanTypeConverters.java @@ -20,7 +20,7 @@ package org.apache.camel.spi; * A {@link org.apache.camel.TypeConverter} which is capable of annotation scanning for * {@link org.apache.camel.Converter} classes and add these as type converters. * <p/> - * This is using Camel 2.x style and its recommended to migrate to @Converter(loader = true) for fast type converter + * This is using Camel 2.x style, and it is recommended to migrate to @Converter(loader = true) for fast type converter * mode. */ public interface AnnotationScanTypeConverters { diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java index 0ca1b16..4790907 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java @@ -275,6 +275,7 @@ public abstract class AbstractCamelContext extends BaseService private Boolean allowUseOriginalMessage = Boolean.FALSE; private Boolean caseInsensitiveHeaders = Boolean.TRUE; private Boolean autowiredEnabled = Boolean.TRUE; + private String basePackageScan; private boolean lightweight; private Long delay; private ErrorHandlerFactory errorHandlerFactory; @@ -2766,8 +2767,9 @@ public abstract class AbstractCamelContext extends BaseService // optimize - before starting routes lets check if event notifications is possible eventNotificationApplicable = EventHelper.eventsApplicable(this); - // ensure additional type converters is loaded - if (loadTypeConverters && typeConverter instanceof AnnotationScanTypeConverters) { + // ensure additional type converters is loaded (either if enabled or we should use package scanning from the base) + boolean load = loadTypeConverters || getBasePackageScan() != null; + if (load && typeConverter instanceof AnnotationScanTypeConverters) { StartupStep step2 = startupStepRecorder.beginStep(CamelContext.class, null, "Scan TypeConverters"); ((AnnotationScanTypeConverters) typeConverter).scanTypeConverters(); startupStepRecorder.endStep(step2); @@ -4271,6 +4273,16 @@ public abstract class AbstractCamelContext extends BaseService } @Override + public String getBasePackageScan() { + return basePackageScan; + } + + @Override + public void setBasePackageScan(String basePackageScan) { + this.basePackageScan = basePackageScan; + } + + @Override public Boolean isDumpRoutes() { return dumpRoutes; } diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCamelBeanPostProcessor.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCamelBeanPostProcessor.java index e663b1f..127df21 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCamelBeanPostProcessor.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCamelBeanPostProcessor.java @@ -121,6 +121,7 @@ public class DefaultCamelBeanPostProcessor implements CamelBeanPostProcessor, Ca if (enabled) { // do bean binding on simple types first, and then afterwards on complex types + injectCamelContextPass(bean, beanName); injectFirstPass(bean, beanName, type -> !isComplexUserType(type)); injectSecondPass(bean, beanName, type -> isComplexUserType(type)); } @@ -194,6 +195,11 @@ public class DefaultCamelBeanPostProcessor implements CamelBeanPostProcessor, Ca return true; } + protected void injectCamelContextPass(Object bean, String beanName) { + // initial pass to inject CamelContext + injectFields(bean, beanName, type -> type.isAssignableFrom(CamelContext.class)); + } + protected void injectFirstPass(Object bean, String beanName, Function<Class, Boolean> filter) { // on first pass do field and methods first injectFields(bean, beanName, filter); diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultInjector.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultInjector.java index 0130b6c..dc70793 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultInjector.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultInjector.java @@ -20,6 +20,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import org.apache.camel.CamelContext; +import org.apache.camel.CamelContextAware; import org.apache.camel.ExtendedCamelContext; import org.apache.camel.RuntimeCamelException; import org.apache.camel.spi.CamelBeanPostProcessor; @@ -33,10 +34,12 @@ import org.apache.camel.support.ObjectHelper; public class DefaultInjector implements Injector { // use the reflection injector + private final CamelContext camelContext; private final CamelBeanPostProcessor postProcessor; public DefaultInjector(CamelContext context) { - postProcessor = context.adapt(ExtendedCamelContext.class).getBeanPostProcessor(); + this.camelContext = context; + this.postProcessor = context.adapt(ExtendedCamelContext.class).getBeanPostProcessor(); } @Override @@ -54,6 +57,8 @@ public class DefaultInjector implements Injector { Object obj = fm.invoke(null); answer = type.cast(obj); } + // inject camel context if needed + CamelContextAware.trySetCamelContext(answer, camelContext); } catch (Exception e) { throw new RuntimeCamelException("Error invoking factory method: " + factoryMethod + " on class: " + type, e); } @@ -63,7 +68,9 @@ public class DefaultInjector implements Injector { @Override public <T> T newInstance(Class<T> type, boolean postProcessBean) { T answer = ObjectHelper.newInstance(type); - if (answer != null && postProcessBean) { + // inject camel context if needed + CamelContextAware.trySetCamelContext(answer, camelContext); + if (postProcessBean) { try { postProcessor.postProcessBeforeInitialization(answer, answer.getClass().getName()); postProcessor.postProcessAfterInitialization(answer, answer.getClass().getName()); diff --git a/core/camel-base/src/main/java/org/apache/camel/impl/converter/AnnotationTypeConverterLoader.java b/core/camel-base/src/main/java/org/apache/camel/impl/converter/AnnotationTypeConverterLoader.java index 65ac1a9..3a1dad1 100644 --- a/core/camel-base/src/main/java/org/apache/camel/impl/converter/AnnotationTypeConverterLoader.java +++ b/core/camel-base/src/main/java/org/apache/camel/impl/converter/AnnotationTypeConverterLoader.java @@ -22,6 +22,7 @@ import java.io.InputStreamReader; import java.lang.reflect.Method; import java.net.URL; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; @@ -65,13 +66,19 @@ import static java.lang.reflect.Modifier.isStatic; public class AnnotationTypeConverterLoader implements TypeConverterLoader { public static final String META_INF_SERVICES = "META-INF/services/org/apache/camel/TypeConverter"; private static final Logger LOG = LoggerFactory.getLogger(AnnotationTypeConverterLoader.class); - private static final Charset UTF8 = Charset.forName("UTF-8"); + private static final Charset UTF8 = StandardCharsets.UTF_8; protected PackageScanClassResolver resolver; protected Set<Class<?>> visitedClasses = new HashSet<>(); protected Set<String> visitedURIs = new HashSet<>(); + private final String basePackage; public AnnotationTypeConverterLoader(PackageScanClassResolver resolver) { + this(resolver, null); + } + + public AnnotationTypeConverterLoader(PackageScanClassResolver resolver, String basePackage) { this.resolver = resolver; + this.basePackage = basePackage; } @Override @@ -117,12 +124,10 @@ public class AnnotationTypeConverterLoader implements TypeConverterLoader { LOG.trace("Found converter packages to scan: {}", String.join(", ", packageNames)); } Set<Class<?>> scannedClasses = resolver.findAnnotated(Converter.class, packageNames); - if (scannedClasses.isEmpty()) { - throw new TypeConverterLoaderException( - "Cannot find any type converter classes from the following packages: " + Arrays.asList(packageNames)); + if (!scannedClasses.isEmpty()) { + LOG.debug("Found {} packages with {} @Converter classes to load", packageNames.length, scannedClasses.size()); + classes.addAll(scannedClasses); } - LOG.debug("Found {} packages with {} @Converter classes to load", packageNames.length, scannedClasses.size()); - classes.addAll(scannedClasses); } // load all the found classes into the type converter registry @@ -206,6 +211,10 @@ public class AnnotationTypeConverterLoader implements TypeConverterLoader { */ protected String[] findPackageNames() throws IOException { Set<String> packages = new HashSet<>(); + if (basePackage != null) { + String[] pks = basePackage.split(","); + packages.addAll(Arrays.asList(pks)); + } ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl != null) { findPackages(packages, ccl); diff --git a/core/camel-base/src/main/java/org/apache/camel/impl/converter/DefaultTypeConverter.java b/core/camel-base/src/main/java/org/apache/camel/impl/converter/DefaultTypeConverter.java index a027d7d0..c8a55df 100644 --- a/core/camel-base/src/main/java/org/apache/camel/impl/converter/DefaultTypeConverter.java +++ b/core/camel-base/src/main/java/org/apache/camel/impl/converter/DefaultTypeConverter.java @@ -17,9 +17,11 @@ package org.apache.camel.impl.converter; import org.apache.camel.CamelContext; +import org.apache.camel.ExtendedCamelContext; import org.apache.camel.spi.AnnotationScanTypeConverters; import org.apache.camel.spi.Injector; import org.apache.camel.spi.PackageScanClassResolver; +import org.apache.camel.spi.TypeConverterLoader; import org.apache.camel.util.StopWatch; import org.apache.camel.util.TimeUtils; import org.slf4j.Logger; @@ -90,7 +92,7 @@ public class DefaultTypeConverter extends BaseTypeConverterRegistry implements A loadTypeConvertersDone = true; if (resolver != null) { - typeConverterLoaders.add(new AnnotationTypeConverterLoader(resolver)); + typeConverterLoaders.add(createScanTypeConverterLoader()); } int fast = typeMappings.size(); @@ -115,4 +117,12 @@ public class DefaultTypeConverter extends BaseTypeConverterRegistry implements A String time = TimeUtils.printDuration(watch.taken()); LOG.debug("Scanned {} type converters in {}", typeMappings.size(), time); } + + /** + * Creates the {@link TypeConverterLoader} to use for scanning for type converters such as from the classpath. + */ + protected TypeConverterLoader createScanTypeConverterLoader() { + String basePackages = camelContext != null ? camelContext.adapt(ExtendedCamelContext.class).getBasePackageScan() : null; + return new AnnotationTypeConverterLoader(resolver, basePackages); + } } diff --git a/core/camel-core-engine/src/generated/java/org/apache/camel/impl/ExtendedCamelContextConfigurer.java b/core/camel-core-engine/src/generated/java/org/apache/camel/impl/ExtendedCamelContextConfigurer.java index af12c3d..d012b1a2 100644 --- a/core/camel-core-engine/src/generated/java/org/apache/camel/impl/ExtendedCamelContextConfigurer.java +++ b/core/camel-core-engine/src/generated/java/org/apache/camel/impl/ExtendedCamelContextConfigurer.java @@ -35,6 +35,8 @@ public class ExtendedCamelContextConfigurer extends org.apache.camel.support.com case "AutowiredEnabled": target.setAutowiredEnabled(property(camelContext, java.lang.Boolean.class, value)); return true; case "backlogtracing": case "BacklogTracing": target.setBacklogTracing(property(camelContext, java.lang.Boolean.class, value)); return true; + case "basepackagescan": + case "BasePackageScan": target.setBasePackageScan(property(camelContext, java.lang.String.class, value)); return true; case "beanintrospection": case "BeanIntrospection": target.setBeanIntrospection(property(camelContext, org.apache.camel.spi.BeanIntrospection.class, value)); return true; case "beanpostprocessor": @@ -228,6 +230,8 @@ public class ExtendedCamelContextConfigurer extends org.apache.camel.support.com case "AutowiredEnabled": return java.lang.Boolean.class; case "backlogtracing": case "BacklogTracing": return java.lang.Boolean.class; + case "basepackagescan": + case "BasePackageScan": return java.lang.String.class; case "beanintrospection": case "BeanIntrospection": return org.apache.camel.spi.BeanIntrospection.class; case "beanpostprocessor": @@ -422,6 +426,8 @@ public class ExtendedCamelContextConfigurer extends org.apache.camel.support.com case "AutowiredEnabled": return target.isAutowiredEnabled(); case "backlogtracing": case "BacklogTracing": return target.isBacklogTracing(); + case "basepackagescan": + case "BasePackageScan": return target.getBasePackageScan(); case "beanintrospection": case "BeanIntrospection": return target.getBeanIntrospection(); case "beanpostprocessor": diff --git a/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightCamelContext.java b/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightCamelContext.java index 947801b..c78e7c0 100644 --- a/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightCamelContext.java +++ b/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightCamelContext.java @@ -931,6 +931,16 @@ public class LightweightCamelContext implements ExtendedCamelContext, CatalogCam } @Override + public String getBasePackageScan() { + return getExtendedCamelContext().getBasePackageScan(); + } + + @Override + public void setBasePackageScan(String basePackageScan) { + getExtendedCamelContext().setBasePackageScan(basePackageScan); + } + + @Override public Boolean isUseMDCLogging() { return delegate.isUseMDCLogging(); } diff --git a/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightRuntimeCamelContext.java b/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightRuntimeCamelContext.java index 398d92c..191d79c 100644 --- a/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightRuntimeCamelContext.java +++ b/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightRuntimeCamelContext.java @@ -200,6 +200,7 @@ public class LightweightRuntimeCamelContext implements ExtendedCamelContext, Cat private final boolean dumpRoutes; private final String mdcLoggingKeysPattern; private final boolean useMDCLogging; + private final String basePackageScan; private final List<Route> routes; private final boolean messageHistory; private final boolean allowUseOriginalMessage; @@ -251,6 +252,7 @@ public class LightweightRuntimeCamelContext implements ExtendedCamelContext, Cat dumpRoutes = context.isDumpRoutes(); mdcLoggingKeysPattern = context.getMDCLoggingKeysPattern(); useMDCLogging = context.isUseMDCLogging(); + basePackageScan = context.adapt(ExtendedCamelContext.class).getBasePackageScan(); messageHistory = context.isMessageHistory(); allowUseOriginalMessage = context.isAllowUseOriginalMessage(); logExhaustedMessageBody = context.isLogExhaustedMessageBody(); @@ -1248,6 +1250,16 @@ public class LightweightRuntimeCamelContext implements ExtendedCamelContext, Cat } @Override + public String getBasePackageScan() { + return basePackageScan; + } + + @Override + public void setBasePackageScan(String basePackageScan) { + throw new UnsupportedOperationException(); + } + + @Override public Boolean isDumpRoutes() { return dumpRoutes; } diff --git a/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java b/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java index c508310..8ec80c7 100644 --- a/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java +++ b/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java @@ -39,6 +39,10 @@ public class MainConfigurationPropertiesConfigurer extends org.apache.camel.supp case "AutowiredEnabled": target.setAutowiredEnabled(property(camelContext, boolean.class, value)); return true; case "backlogtracing": case "BacklogTracing": target.setBacklogTracing(property(camelContext, boolean.class, value)); return true; + case "basepackagescan": + case "BasePackageScan": target.setBasePackageScan(property(camelContext, java.lang.String.class, value)); return true; + case "basepackagescanenabled": + case "BasePackageScanEnabled": target.setBasePackageScanEnabled(property(camelContext, boolean.class, value)); return true; case "beanintrospectionextendedstatistics": case "BeanIntrospectionExtendedStatistics": target.setBeanIntrospectionExtendedStatistics(property(camelContext, boolean.class, value)); return true; case "beanintrospectionlogginglevel": @@ -117,8 +121,6 @@ public class MainConfigurationPropertiesConfigurer extends org.apache.camel.supp case "MessageHistory": target.setMessageHistory(property(camelContext, boolean.class, value)); return true; case "name": case "Name": target.setName(property(camelContext, java.lang.String.class, value)); return true; - case "packagescanroutebuilders": - case "PackageScanRouteBuilders": target.setPackageScanRouteBuilders(property(camelContext, java.lang.String.class, value)); return true; case "producertemplatecachesize": case "ProducerTemplateCacheSize": target.setProducerTemplateCacheSize(property(camelContext, int.class, value)); return true; case "routecontrollerbackoffdelay": @@ -260,6 +262,10 @@ public class MainConfigurationPropertiesConfigurer extends org.apache.camel.supp case "AutowiredEnabled": return boolean.class; case "backlogtracing": case "BacklogTracing": return boolean.class; + case "basepackagescan": + case "BasePackageScan": return java.lang.String.class; + case "basepackagescanenabled": + case "BasePackageScanEnabled": return boolean.class; case "beanintrospectionextendedstatistics": case "BeanIntrospectionExtendedStatistics": return boolean.class; case "beanintrospectionlogginglevel": @@ -338,8 +344,6 @@ public class MainConfigurationPropertiesConfigurer extends org.apache.camel.supp case "MessageHistory": return boolean.class; case "name": case "Name": return java.lang.String.class; - case "packagescanroutebuilders": - case "PackageScanRouteBuilders": return java.lang.String.class; case "producertemplatecachesize": case "ProducerTemplateCacheSize": return int.class; case "routecontrollerbackoffdelay": @@ -482,6 +486,10 @@ public class MainConfigurationPropertiesConfigurer extends org.apache.camel.supp case "AutowiredEnabled": return target.isAutowiredEnabled(); case "backlogtracing": case "BacklogTracing": return target.isBacklogTracing(); + case "basepackagescan": + case "BasePackageScan": return target.getBasePackageScan(); + case "basepackagescanenabled": + case "BasePackageScanEnabled": return target.isBasePackageScanEnabled(); case "beanintrospectionextendedstatistics": case "BeanIntrospectionExtendedStatistics": return target.isBeanIntrospectionExtendedStatistics(); case "beanintrospectionlogginglevel": @@ -560,8 +568,6 @@ public class MainConfigurationPropertiesConfigurer extends org.apache.camel.supp case "MessageHistory": return target.isMessageHistory(); case "name": case "Name": return target.getName(); - case "packagescanroutebuilders": - case "PackageScanRouteBuilders": return target.getPackageScanRouteBuilders(); case "producertemplatecachesize": case "ProducerTemplateCacheSize": return target.getProducerTemplateCacheSize(); case "routecontrollerbackoffdelay": @@ -686,7 +692,7 @@ public class MainConfigurationPropertiesConfigurer extends org.apache.camel.supp public Object getCollectionValueType(Object target, String name, boolean ignoreCase) { switch (ignoreCase ? name.toLowerCase() : name) { case "configurations": - case "Configurations": return java.lang.Object.class; + case "Configurations": return org.apache.camel.main.CamelConfiguration.class; case "globaloptions": case "GlobalOptions": return java.lang.String.class; case "routesbuilders": diff --git a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json index f68a484..0eaab4a 100644 --- a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json +++ b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json @@ -19,6 +19,8 @@ { "name": "camel.main.autoStartup", "description": "Sets whether the object should automatically start when Camel starts. Important: Currently only routes can be disabled, as CamelContext's are always started. Note: When setting auto startup false on CamelContext then that takes precedence and no routes are started. You would need to start CamelContext explicit using the org.apache.camel.CamelContext.start() method, to start the context, and then you would need to start the routes ma [...] { "name": "camel.main.autowiredEnabled", "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching type, which then gets configured on the component. This can be used for automatic configuring JDBC data sources, JMS connection factories, AWS Clients, etc. Default is true.", "sourceType": "org.apache.camel.main.DefaultConfiguration [...] { "name": "camel.main.backlogTracing", "description": "Sets whether backlog tracing is enabled or not. Default is false.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": "false" }, + { "name": "camel.main.basePackageScan", "description": "Package name to use as base (offset) for classpath scanning of RouteBuilder , and org.apache.camel.TypeConverter classes. If you are using Spring Boot then it is instead recommended to use Spring Boots component scanning and annotate your route builder classes with Component. In other words only use this for Camel Main in standalone mode.", "sourceType": "org.apache.camel.main.MainConfigurationProperties", "type": "string", "jav [...] + { "name": "camel.main.basePackageScanEnabled", "description": "Whether base package scan is enabled.", "sourceType": "org.apache.camel.main.MainConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": true }, { "name": "camel.main.beanIntrospectionExtendedStatistics", "description": "Sets whether bean introspection uses extended statistics. The default is false.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": "false" }, { "name": "camel.main.beanIntrospectionLoggingLevel", "description": "Sets the logging level used by bean introspection, logging activity of its usage. The default is TRACE.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "object", "javaType": "org.apache.camel.LoggingLevel", "enum": [ "ERROR", "WARN", "INFO", "DEBUG", "TRACE", "OFF" ] }, { "name": "camel.main.beanPostProcessorEnabled", "description": "Can be used to turn off bean post processing. Be careful to turn this off, as this means that beans that use Camel annotations such as org.apache.camel.EndpointInject , org.apache.camel.ProducerTemplate , org.apache.camel.Produce , org.apache.camel.Consume etc will not be injected and in use. Turning this off should only be done if you are sure you do not use any of these Camel features. Not all runtimes allow turning t [...] @@ -58,7 +60,6 @@ { "name": "camel.main.mdcLoggingKeysPattern", "description": "Sets the pattern used for determine which custom MDC keys to propagate during message routing when the routing engine continues routing asynchronously for the given message. Setting this pattern to will propagate all custom keys. Or setting the pattern to foo,bar will propagate any keys starting with either foo or bar. Notice that a set of standard Camel MDC keys are always propagated which starts with camel. as key name. [...] { "name": "camel.main.messageHistory", "description": "Sets whether message history is enabled or not. Default is false.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": "false" }, { "name": "camel.main.name", "description": "Sets the name of the CamelContext.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "string", "javaType": "java.lang.String" }, - { "name": "camel.main.packageScanRouteBuilders", "description": "Sets package names for scanning for org.apache.camel.builder.RouteBuilder classes as candidates to be included. If you are using Spring Boot then its instead recommended to use Spring Boots component scanning and annotate your route builder classes with Component. In other words only use this for Camel Main in standalone mode.", "sourceType": "org.apache.camel.main.MainConfigurationProperties", "type": "string", "javaTy [...] { "name": "camel.main.producerTemplateCacheSize", "description": "Producer template endpoints cache size.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "integer", "javaType": "int", "defaultValue": 1000 }, { "name": "camel.main.routeControllerBackOffDelay", "description": "Backoff delay in millis when restarting a route that failed to startup.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "integer", "javaType": "long" }, { "name": "camel.main.routeControllerBackOffMaxAttempts", "description": "Backoff maximum number of attempts to restart a route that failed to startup. When this threshold has been exceeded then the controller will give up attempting to restart the route, and the route will remain as stopped.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "integer", "javaType": "long" }, diff --git a/core/camel-main/src/main/docs/main.adoc b/core/camel-main/src/main/docs/main.adoc index 1095d85..8774ae8 100644 --- a/core/camel-main/src/main/docs/main.adoc +++ b/core/camel-main/src/main/docs/main.adoc @@ -18,7 +18,7 @@ The following tables lists all the options: // main options: START === Camel Main configurations -The camel.main supports 107 options, which are listed below. +The camel.main supports 108 options, which are listed below. [width="100%",cols="2,5,^1,2",options="header"] |=== @@ -32,6 +32,8 @@ The camel.main supports 107 options, which are listed below. | *camel.main.autoStartup* | Sets whether the object should automatically start when Camel starts. Important: Currently only routes can be disabled, as CamelContext's are always started. Note: When setting auto startup false on CamelContext then that takes precedence and no routes are started. You would need to start CamelContext explicit using the org.apache.camel.CamelContext.start() method, to start the context, and then you would need to start the routes manually using CamelContext.g [...] | *camel.main.autowiredEnabled* | Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching type, which then gets configured on the component. This can be used for automatic configuring JDBC data sources, JMS connection factories, AWS Clients, etc. Default is true. | true | boolean | *camel.main.backlogTracing* | Sets whether backlog tracing is enabled or not. Default is false. | false | boolean +| *camel.main.basePackageScan* | Package name to use as base (offset) for classpath scanning of RouteBuilder , and org.apache.camel.TypeConverter classes. If you are using Spring Boot then it is instead recommended to use Spring Boots component scanning and annotate your route builder classes with Component. In other words only use this for Camel Main in standalone mode. | | String +| *camel.main.basePackageScan{zwsp}Enabled* | Whether base package scan is enabled. | true | boolean | *camel.main.beanIntrospection{zwsp}ExtendedStatistics* | Sets whether bean introspection uses extended statistics. The default is false. | false | boolean | *camel.main.beanIntrospection{zwsp}LoggingLevel* | Sets the logging level used by bean introspection, logging activity of its usage. The default is TRACE. | | LoggingLevel | *camel.main.beanPostProcessor{zwsp}Enabled* | Can be used to turn off bean post processing. Be careful to turn this off, as this means that beans that use Camel annotations such as org.apache.camel.EndpointInject , org.apache.camel.ProducerTemplate , org.apache.camel.Produce , org.apache.camel.Consume etc will not be injected and in use. Turning this off should only be done if you are sure you do not use any of these Camel features. Not all runtimes allow turning this off (such as came [...] @@ -71,7 +73,6 @@ The camel.main supports 107 options, which are listed below. | *camel.main.mdcLoggingKeys{zwsp}Pattern* | Sets the pattern used for determine which custom MDC keys to propagate during message routing when the routing engine continues routing asynchronously for the given message. Setting this pattern to will propagate all custom keys. Or setting the pattern to foo,bar will propagate any keys starting with either foo or bar. Notice that a set of standard Camel MDC keys are always propagated which starts with camel. as key name. The match rules are a [...] | *camel.main.messageHistory* | Sets whether message history is enabled or not. Default is false. | false | boolean | *camel.main.name* | Sets the name of the CamelContext. | | String -| *camel.main.packageScanRoute{zwsp}Builders* | Sets package names for scanning for org.apache.camel.builder.RouteBuilder classes as candidates to be included. If you are using Spring Boot then its instead recommended to use Spring Boots component scanning and annotate your route builder classes with Component. In other words only use this for Camel Main in standalone mode. | | String | *camel.main.producerTemplate{zwsp}CacheSize* | Producer template endpoints cache size. | 1000 | int | *camel.main.routeControllerBack{zwsp}OffDelay* | Backoff delay in millis when restarting a route that failed to startup. | | long | *camel.main.routeControllerBack{zwsp}OffMaxAttempts* | Backoff maximum number of attempts to restart a route that failed to startup. When this threshold has been exceeded then the controller will give up attempting to restart the route, and the route will remain as stopped. | | long @@ -298,6 +299,77 @@ The camel.lra supports 4 options, which are listed below. |=== // main options: END +== Package Scanning + +*Available since Camel 3.16* + +When running Camel standalone via `camel-main` JAR, then Camel will use package scanning to discover: + +- Camel routes by discovering `RouteBuilder` classes +- Camel configuration classes by discovering `CamelConfiguration` classes +- Camel type converters by discovering classes annotated with `@Converter` + +To use package scanning then Camel needs to know the base package to use as _offset_. This +can be specified either with the `camel.main.basePackage` option or via `Main` class as shown below: + +[source,java] +---- +package com.foo.acme; + +public class MyCoolApplication { + + public static void main(String[] args) { + Main main = new Main(MyCoolApplication.class); + main.run(); + } + +} +---- + +In the example above, then we use `com.foo.acme` as the base package, which is done +by passing in the class in the `Main` constructor. This is similar with how Spring Boot does this. + +Camel will then scan from the base package and the sub packages. + +=== Disabling Package Scanning + +Package scanning can be turned off by setting `camel.main.basePackageScanEnabled=false`. + +There is a little overhead when using package scanning as Camel performs this scan during startup. + +== Configuring Camel Main applications + +You can use _configuration_ classes to configure Camel Main applications from Java. + +IMPORTANT: In *Camel 3.16* onwards the configuration classes must implement the interface `org.apache.camel.main.CamelConfiguration`. +In previous versions this was not required. + +For example to configure a Camel application by creating custom beans you can do: + +[source,java] +---- +public class MyConfiguration implements CamelConfiguration { + + public void configure(CamelContext camelContext) throws Exception { + // this method is optional and can be omitted + // do any kind of configuration here if needed + } + + @BindToRegistry + public MyBean myAwesomeBean() { + MyBean bean = new MyBean(); + // do something on bean + return bean; + } +} +---- + +In the configuration class you can also have custom methods that creates beans, such as the `myAwesomeBean` method +that creates the `MyBean` and registers it with the name `myAwesomeBean` (defaults to method name). + +This is similar to Spring Boot where you can also do this with the Spring Boot `@Bean` annotations, +or in Quarkus/CDI with the `@Produces` annotation. + == Specifying custom beans Custom beans can be configured in `camel-main` via properties (such as in the `application.properties` file). diff --git a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java index 1d68bc6..5c108df 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java @@ -52,6 +52,7 @@ import org.apache.camel.spi.AutowiredLifecycleStrategy; import org.apache.camel.spi.CamelBeanPostProcessor; import org.apache.camel.spi.DataFormat; import org.apache.camel.spi.Language; +import org.apache.camel.spi.PackageScanClassResolver; import org.apache.camel.spi.PropertiesComponent; import org.apache.camel.spi.RouteTemplateParameterSource; import org.apache.camel.spi.StartupStepRecorder; @@ -302,13 +303,40 @@ public abstract class BaseMainSupport extends BaseService { postProcessor.postProcessAfterInitialization(configuration, configuration.getClass().getName()); } + // auto-detect camel configurations via base package scanning + String basePackage = camelContext.adapt(ExtendedCamelContext.class).getBasePackageScan(); + if (basePackage != null) { + PackageScanClassResolver pscr = camelContext.adapt(ExtendedCamelContext.class).getPackageScanClassResolver(); + Set<Class<?>> found = pscr.findImplementations(CamelConfiguration.class, basePackage); + for (Class<?> clazz : found) { + // lets use Camel's injector so the class has some support for dependency injection + Object config = camelContext.getInjector().newInstance(clazz); + if (config instanceof CamelConfiguration) { + LOG.debug("Discovered CamelConfiguration class: {}", clazz); + CamelConfiguration cc = (CamelConfiguration) config; + mainConfigurationProperties.addConfiguration(cc); + } + } + } + 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); + Class<CamelConfiguration> configClazz + = camelContext.getClassResolver().resolveClass(configClass, CamelConfiguration.class); + // skip main classes + boolean mainClass = false; + try { + configClazz.getDeclaredMethod("main", String[].class); + mainClass = true; + } catch (NoSuchMethodException e) { + // ignore + } + if (!mainClass) { + // lets use Camel's injector so the class has some support for dependency injection + CamelConfiguration config = camelContext.getInjector().newInstance(configClazz); + mainConfigurationProperties.addConfiguration(config); + } } } @@ -493,6 +521,16 @@ public abstract class BaseMainSupport extends BaseService { } } + protected void configurePackageScan(CamelContext camelContext) { + if (mainConfigurationProperties.isBasePackageScanEnabled()) { + // only set the base package if enabled + camelContext.adapt(ExtendedCamelContext.class).setBasePackageScan(mainConfigurationProperties.getBasePackageScan()); + if (mainConfigurationProperties.getBasePackageScan() != null) { + LOG.info("Classpath scanning enabled from base package: {}", mainConfigurationProperties.getBasePackageScan()); + } + } + } + protected void configureRoutes(CamelContext camelContext) throws Exception { // then configure and add the routes RoutesConfigurer configurer = new RoutesConfigurer(); @@ -504,7 +542,10 @@ public abstract class BaseMainSupport extends BaseService { configurer.setBeanPostProcessor(camelContext.adapt(ExtendedCamelContext.class).getBeanPostProcessor()); configurer.setRoutesBuilders(mainConfigurationProperties.getRoutesBuilders()); configurer.setRoutesBuilderClasses(mainConfigurationProperties.getRoutesBuilderClasses()); - configurer.setPackageScanRouteBuilders(mainConfigurationProperties.getPackageScanRouteBuilders()); + if (mainConfigurationProperties.isBasePackageScanEnabled()) { + // only set the base package if enabled + configurer.setBasePackageScan(mainConfigurationProperties.getBasePackageScan()); + } configurer.setJavaRoutesExcludePattern(mainConfigurationProperties.getJavaRoutesExcludePattern()); configurer.setJavaRoutesIncludePattern(mainConfigurationProperties.getJavaRoutesIncludePattern()); configurer.setRoutesExcludePattern(mainConfigurationProperties.getRoutesExcludePattern()); @@ -521,6 +562,8 @@ public abstract class BaseMainSupport extends BaseService { configurePropertiesService(camelContext); // setup startup recorder before building context configureStartupRecorder(camelContext); + // setup package scan + configurePackageScan(camelContext); // ensure camel context is build camelContext.build(); diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/AnnotationScanTypeConverters.java b/core/camel-main/src/main/java/org/apache/camel/main/CamelConfiguration.java similarity index 57% copy from core/camel-api/src/main/java/org/apache/camel/spi/AnnotationScanTypeConverters.java copy to core/camel-main/src/main/java/org/apache/camel/main/CamelConfiguration.java index 9f5e8d2..0c871bb 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/AnnotationScanTypeConverters.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/CamelConfiguration.java @@ -14,21 +14,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.spi; +package org.apache.camel.main; + +import org.apache.camel.CamelContext; /** - * A {@link org.apache.camel.TypeConverter} which is capable of annotation scanning for - * {@link org.apache.camel.Converter} classes and add these as type converters. - * <p/> - * This is using Camel 2.x style and its recommended to migrate to @Converter(loader = true) for fast type converter - * mode. + * Configuration class for Camel Main applications. */ -public interface AnnotationScanTypeConverters { +public interface CamelConfiguration { + + default void configure(CamelContext camelContext) throws Exception { + } - /** - * Scan for {@link org.apache.camel.Converter} classes and add those as type converters. - * - * @throws Exception is thrown if error happened - */ - void scanTypeConverters() throws Exception; } diff --git a/core/camel-main/src/main/java/org/apache/camel/main/Main.java b/core/camel-main/src/main/java/org/apache/camel/main/Main.java index e666bc0..ae367a1 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/Main.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/Main.java @@ -31,11 +31,33 @@ public class Main extends MainCommandLineSupport { protected static Main instance; protected final MainRegistry registry = new MainRegistry(); + /** + * Camel main application + * + * It is recommended to use {@link Main#Main(Class)} to specify the main class. + */ public Main() { } - public Main(Class<?>... configurationClass) { - super(configurationClass); + /** + * Camel main application + * + * @param mainClass the main class + */ + public Main(Class<?> mainClass) { + configure().withBasePackageScan(mainClass.getPackageName()); + } + + /** + * Camel main application + * + * @param mainClass the main class + * @param configurationClasses additional camel configuration classes + */ + @SafeVarargs + public Main(Class<?> mainClass, Class<CamelConfiguration>... configurationClasses) { + super(configurationClasses); + configure().withBasePackageScan(mainClass.getPackageName()); } public static void main(String... args) throws Exception { diff --git a/core/camel-main/src/main/java/org/apache/camel/main/MainCommandLineSupport.java b/core/camel-main/src/main/java/org/apache/camel/main/MainCommandLineSupport.java index 9cf4a41..c8688b8 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/MainCommandLineSupport.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/MainCommandLineSupport.java @@ -29,7 +29,8 @@ public abstract class MainCommandLineSupport extends MainSupport { protected final List<Option> options = new ArrayList<>(); private volatile boolean initOptionsDone; - public MainCommandLineSupport(Class... configurationClasses) { + @SafeVarargs + public MainCommandLineSupport(Class<CamelConfiguration>... configurationClasses) { super(configurationClasses); } diff --git a/core/camel-main/src/main/java/org/apache/camel/main/MainConfigurationProperties.java b/core/camel-main/src/main/java/org/apache/camel/main/MainConfigurationProperties.java index 445b9e2..b8334b3 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/MainConfigurationProperties.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/MainConfigurationProperties.java @@ -26,7 +26,7 @@ import org.apache.camel.spi.BootstrapCloseable; import org.apache.camel.spi.Configurer; /** - * Global configuration for Camel Main to setup context name, stream caching and other global configurations. + * Global configuration for Camel Main to configure context name, stream caching and other global configurations. */ @Configurer(bootstrap = true) public class MainConfigurationProperties extends DefaultConfigurationProperties<MainConfigurationProperties> @@ -38,13 +38,14 @@ public class MainConfigurationProperties extends DefaultConfigurationProperties< private boolean autoConfigurationFailFast = true; private boolean autoConfigurationLogSummary = true; private int durationHitExitCode; - private String packageScanRouteBuilders; + private String basePackageScan; + private boolean basePackageScanEnabled = true; private String routesBuilderClasses; private String configurationClasses; private List<RoutesBuilder> routesBuilders = new ArrayList<>(); - private List<Object> configurations = new ArrayList<>(); + private List<CamelConfiguration> configurations = new ArrayList<>(); // extended configuration private HealthConfigurationProperties healthConfigurationProperties; @@ -303,18 +304,30 @@ public class MainConfigurationProperties extends DefaultConfigurationProperties< this.autoConfigurationLogSummary = autoConfigurationLogSummary; } - public String getPackageScanRouteBuilders() { - return packageScanRouteBuilders; + public String getBasePackageScan() { + return basePackageScan; } /** - * Sets package names for scanning for {@link org.apache.camel.builder.RouteBuilder} classes as candidates to be - * included. If you are using Spring Boot then its instead recommended to use Spring Boots component scanning and - * annotate your route builder classes with `@Component`. In other words only use this for Camel Main in standalone - * mode. + * Package name to use as base (offset) for classpath scanning of {@link RouteBuilder}, and + * {@link org.apache.camel.TypeConverter} classes. + * + * If you are using Spring Boot then it is instead recommended to use Spring Boots component scanning and annotate + * your route builder classes with `@Component`. In other words only use this for Camel Main in standalone mode. */ - public void setPackageScanRouteBuilders(String packageScanRouteBuilders) { - this.packageScanRouteBuilders = packageScanRouteBuilders; + public void setBasePackageScan(String basePackageScan) { + this.basePackageScan = basePackageScan; + } + + public boolean isBasePackageScanEnabled() { + return basePackageScanEnabled; + } + + /** + * Whether base package scan is enabled. + */ + public void setBasePackageScanEnabled(boolean basePackageScanEnabled) { + this.basePackageScanEnabled = basePackageScanEnabled; } public int getDurationHitExitCode() { @@ -344,15 +357,16 @@ public class MainConfigurationProperties extends DefaultConfigurationProperties< } /** - * Add an additional configuration class to the known list of configurations classes. + * Adds configuration object to the known list of configurations objects. */ - public void addConfigurationClass(Class<?>... configuration) { + @SuppressWarnings("unchecked") + private void addConfigurationClass(Class<? extends CamelConfiguration>... configuration) { String existing = configurationClasses; if (existing == null) { existing = ""; } if (configuration != null) { - for (Class clazz : configuration) { + for (Class<? extends CamelConfiguration> clazz : configuration) { if (!existing.isEmpty()) { existing = existing + ","; } @@ -363,20 +377,27 @@ public class MainConfigurationProperties extends DefaultConfigurationProperties< } /** - * Add an additional configuration object to the known list of configurations objects. + * Adds configuration object to the known list of configurations objects. */ - public void addConfiguration(Object configuration) { + public void addConfiguration(CamelConfiguration configuration) { configurations.add(configuration); } - public List<Object> getConfigurations() { + /** + * Adds configuration object to the known list of configurations objects. + */ + public void addConfiguration(Class<? extends CamelConfiguration> configuration) { + addConfigurationClass(configuration); + } + + public List<CamelConfiguration> getConfigurations() { return configurations; } /** * Sets the configuration objects used to configure the camel context. */ - public void setConfigurations(List<Object> configurations) { + public void setConfigurations(List<CamelConfiguration> configurations) { this.configurations = configurations; } @@ -524,48 +545,58 @@ public class MainConfigurationProperties extends DefaultConfigurationProperties< } /** - * Sets package names for scanning for {@link org.apache.camel.builder.RouteBuilder} classes as candidates to be - * included. If you are using Spring Boot then its instead recommended to use Spring Boots component scanning and - * annotate your route builder classes with `@Component`. In other words only use this for Camel Main in standalone - * mode. + * Package name to use as base (offset) for classpath scanning of {@link RouteBuilder}, and + * {@link org.apache.camel.TypeConverter} classes. + * + * If you are using Spring Boot then it is instead recommended to use Spring Boots component scanning and annotate + * your route builder classes with `@Component`. In other words only use this for Camel Main in standalone mode. */ - public MainConfigurationProperties withPackageScanRouteBuilders(String packageScanRouteBuilders) { - this.packageScanRouteBuilders = packageScanRouteBuilders; + public MainConfigurationProperties withBasePackageScan(String basePackageScan) { + this.basePackageScan = basePackageScan; return this; } - // fluent builders - configurations - // -------------------------------------------------------------- - /** - * Sets classes names that will be used to configure the camel context as example by providing custom beans through - * {@link org.apache.camel.BindToRegistry} annotation. + * Whether base package scan is enabled. */ - public MainConfigurationProperties withConfigurationClasses(String configurations) { - setConfigurationClasses(configurations); + public MainConfigurationProperties withBasePackageScanEnabled(boolean basePackageScanEnabled) { + this.basePackageScanEnabled = basePackageScanEnabled; return this; } + // fluent builders - configurations + // -------------------------------------------------------------- + /** - * Add an additional configuration class to the known list of configurations classes. + * Adds classes names that will be used to configure the camel context as example by providing custom beans through + * {@link org.apache.camel.BindToRegistry} annotation. */ - public MainConfigurationProperties withAdditionalConfigurationClasses(Class... configuration) { - addConfigurationClass(configuration); + public MainConfigurationProperties withConfigurations(String configurations) { + if (this.configurationClasses == null) { + this.configurationClasses = ""; + } + if (this.configurationClasses.isEmpty()) { + this.configurationClasses = configurations; + } else { + this.configurationClasses = "," + configurations; + } return this; } /** - * Add an additional configuration object to the known list of configurations objects. + * Adds a configuration class to the known list of configurations classes. */ - public MainConfigurationProperties withAdditionalConfiguration(Object configuration) { - addConfiguration(configuration); + @SuppressWarnings("unchecked") + public MainConfigurationProperties withConfigurations( + Class<? extends CamelConfiguration>... configuration) { + addConfigurationClass(configuration); return this; } /** * Sets the configuration objects used to configure the camel context. */ - public MainConfigurationProperties withConfigurations(List<Object> configurations) { + public MainConfigurationProperties withConfigurations(List<CamelConfiguration> configurations) { setConfigurations(configurations); return this; } diff --git a/core/camel-main/src/main/java/org/apache/camel/main/MainSupport.java b/core/camel-main/src/main/java/org/apache/camel/main/MainSupport.java index 824bfec..820d721 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/MainSupport.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/MainSupport.java @@ -48,9 +48,12 @@ public abstract class MainSupport extends BaseMainSupport { private int durationHitExitCode; private String durationMaxAction = "shutdown"; - protected MainSupport(Class<?>... configurationClasses) { + @SafeVarargs + protected MainSupport(Class<? extends CamelConfiguration>... configurationClasses) { this(); - configure().addConfigurationClass(configurationClasses); + for (Class<? extends CamelConfiguration> clazz : configurationClasses) { + configure().addConfiguration(clazz); + } } protected MainSupport() { diff --git a/core/camel-main/src/main/java/org/apache/camel/main/RoutesConfigurer.java b/core/camel-main/src/main/java/org/apache/camel/main/RoutesConfigurer.java index 1a543b1..d4c1bef 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/RoutesConfigurer.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/RoutesConfigurer.java @@ -42,7 +42,7 @@ public class RoutesConfigurer { private RoutesCollector routesCollector; private CamelBeanPostProcessor beanPostProcessor; private List<RoutesBuilder> routesBuilders; - private String packageScanRouteBuilders; + private String basePackageScan; private String routesBuilderClasses; private String javaRoutesExcludePattern; private String javaRoutesIncludePattern; @@ -57,12 +57,12 @@ public class RoutesConfigurer { this.routesBuilders = routesBuilders; } - public String getPackageScanRouteBuilders() { - return packageScanRouteBuilders; + public String getBasePackageScan() { + return basePackageScan; } - public void setPackageScanRouteBuilders(String packageScanRouteBuilders) { - this.packageScanRouteBuilders = packageScanRouteBuilders; + public void setBasePackageScan(String basePackageScan) { + this.basePackageScan = basePackageScan; } public String getRoutesBuilderClasses() { @@ -148,8 +148,8 @@ public class RoutesConfigurer { } } - if (getPackageScanRouteBuilders() != null) { - String[] pkgs = getPackageScanRouteBuilders().split(","); + if (getBasePackageScan() != null) { + String[] pkgs = getBasePackageScan().split(","); Set<Class<?>> set = camelContext.adapt(ExtendedCamelContext.class) .getPackageScanClassResolver() .findImplementations(RoutesBuilder.class, pkgs); diff --git a/core/camel-main/src/test/java/org/apache/camel/main/ContextEventsTest.java b/core/camel-main/src/test/java/org/apache/camel/main/ContextEventsTest.java index 314feb5..734cd7b 100644 --- a/core/camel-main/src/test/java/org/apache/camel/main/ContextEventsTest.java +++ b/core/camel-main/src/test/java/org/apache/camel/main/ContextEventsTest.java @@ -43,7 +43,7 @@ public class ContextEventsTest { assertEquals(1, config.onStop.get()); } - public static class MyConfig { + public static class MyConfig implements CamelConfiguration { final AtomicInteger onInitializing = new AtomicInteger(); final AtomicInteger onInitialized = new AtomicInteger(); final AtomicInteger onStart = new AtomicInteger(); diff --git a/core/camel-main/src/test/java/org/apache/camel/main/MainCustomizerTest.java b/core/camel-main/src/test/java/org/apache/camel/main/MainCustomizerTest.java index c4c8031..07bc162 100644 --- a/core/camel-main/src/test/java/org/apache/camel/main/MainCustomizerTest.java +++ b/core/camel-main/src/test/java/org/apache/camel/main/MainCustomizerTest.java @@ -32,7 +32,7 @@ public class MainCustomizerTest { Main main = new Main(); try { - main.configure().addConfigurationClass(MyConfiguration.class); + main.configure().addConfiguration(MyConfiguration.class); main.start(); LogComponent component = main.getCamelContext().getComponent("log", LogComponent.class); @@ -48,7 +48,7 @@ public class MainCustomizerTest { try { main.bind("my-filter", ComponentCustomizer.Policy.none()); - main.configure().addConfigurationClass(MyConfiguration.class); + main.configure().addConfiguration(MyConfiguration.class); main.start(); LogComponent component = main.getCamelContext().getComponent("log", LogComponent.class); @@ -65,7 +65,7 @@ public class MainCustomizerTest { try { main.addInitialProperty("camel.customizer.component.log.enabled", "false"); main.bind("my-filter", ComponentCustomizer.Policy.any()); - main.configure().addConfigurationClass(MyConfiguration.class); + main.configure().addConfiguration(MyConfiguration.class); main.start(); LogComponent component = main.getCamelContext().getComponent("log", LogComponent.class); @@ -81,7 +81,7 @@ public class MainCustomizerTest { // // **************************** - public static class MyConfiguration { + public static class MyConfiguration implements CamelConfiguration { @BindToRegistry public ComponentCustomizer logCustomizer() { return ComponentCustomizer.builder(LogComponent.class) diff --git a/core/camel-main/src/test/java/org/apache/camel/main/MainIoCNewRouteBuilderTest.java b/core/camel-main/src/test/java/org/apache/camel/main/MainIoCNewRouteBuilderTest.java index 08aa9c0..fe7ba54 100644 --- a/core/camel-main/src/test/java/org/apache/camel/main/MainIoCNewRouteBuilderTest.java +++ b/core/camel-main/src/test/java/org/apache/camel/main/MainIoCNewRouteBuilderTest.java @@ -54,7 +54,7 @@ public class MainIoCNewRouteBuilderTest { main.stop(); } - public static class MyConfiguration { + public static class MyConfiguration implements CamelConfiguration { @BeanInject private CamelContext camel; diff --git a/core/camel-main/src/test/java/org/apache/camel/main/MainIoCTest.java b/core/camel-main/src/test/java/org/apache/camel/main/MainIoCTest.java index 7f47049..70881df 100644 --- a/core/camel-main/src/test/java/org/apache/camel/main/MainIoCTest.java +++ b/core/camel-main/src/test/java/org/apache/camel/main/MainIoCTest.java @@ -38,7 +38,7 @@ public class MainIoCTest { // use configuration class Main main = new Main(); // add the configuration - main.configure().addConfigurationClass(MyConfiguration.class); + main.configure().withConfigurations(MyConfiguration.class); // add as class so we get IoC main.configure().addRoutesBuilder(MyRouteBuilder.class); // manually bind @@ -101,7 +101,7 @@ public class MainIoCTest { // noop } - public static class MyConfiguration { + public static class MyConfiguration implements CamelConfiguration { @BeanInject private CamelContext camel; diff --git a/core/camel-main/src/test/java/org/apache/camel/main/MainRoutesCollectorPackageScanTest.java b/core/camel-main/src/test/java/org/apache/camel/main/MainRoutesCollectorPackageScanTest.java index 80e6aa4..81c0711 100644 --- a/core/camel-main/src/test/java/org/apache/camel/main/MainRoutesCollectorPackageScanTest.java +++ b/core/camel-main/src/test/java/org/apache/camel/main/MainRoutesCollectorPackageScanTest.java @@ -18,6 +18,7 @@ package org.apache.camel.main; import org.apache.camel.CamelContext; import org.apache.camel.component.mock.MockEndpoint; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -28,7 +29,7 @@ public class MainRoutesCollectorPackageScanTest { @Test public void testMainRoutesCollector() throws Exception { Main main = new Main(); - main.configure().withPackageScanRouteBuilders("org.apache.camel.main.scan"); + main.configure().withBasePackageScan("org.apache.camel.main.scan"); main.start(); CamelContext camelContext = main.getCamelContext(); @@ -50,6 +51,13 @@ public class MainRoutesCollectorPackageScanTest { endpoint2.assertIsSatisfied(); endpoint3.assertIsSatisfied(); + // camel configuration should be scanned + Assertions.assertEquals("true", camelContext.getGlobalOption("scanConfigured")); + + // custom type converter should be scanned + MyFoo foo = camelContext.getTypeConverter().convertTo(MyFoo.class, "Donald"); + assertEquals("Donald", foo.getName()); + main.stop(); } diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/AnnotationScanTypeConverters.java b/core/camel-main/src/test/java/org/apache/camel/main/scan/MyConverter.java similarity index 57% copy from core/camel-api/src/main/java/org/apache/camel/spi/AnnotationScanTypeConverters.java copy to core/camel-main/src/test/java/org/apache/camel/main/scan/MyConverter.java index 9f5e8d2..013bd75 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/AnnotationScanTypeConverters.java +++ b/core/camel-main/src/test/java/org/apache/camel/main/scan/MyConverter.java @@ -14,21 +14,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.spi; +package org.apache.camel.main.scan; -/** - * A {@link org.apache.camel.TypeConverter} which is capable of annotation scanning for - * {@link org.apache.camel.Converter} classes and add these as type converters. - * <p/> - * This is using Camel 2.x style and its recommended to migrate to @Converter(loader = true) for fast type converter - * mode. - */ -public interface AnnotationScanTypeConverters { +import org.apache.camel.Converter; +import org.apache.camel.main.MyFoo; + +@Converter +public final class MyConverter { + + private MyConverter() { + } - /** - * Scan for {@link org.apache.camel.Converter} classes and add those as type converters. - * - * @throws Exception is thrown if error happened - */ - void scanTypeConverters() throws Exception; + @Converter + public static MyFoo toFoo(String name) { + return new MyFoo(name); + } } diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/AnnotationScanTypeConverters.java b/core/camel-main/src/test/java/org/apache/camel/main/scan/MyScanConfiguration.java similarity index 57% copy from core/camel-api/src/main/java/org/apache/camel/spi/AnnotationScanTypeConverters.java copy to core/camel-main/src/test/java/org/apache/camel/main/scan/MyScanConfiguration.java index 9f5e8d2..ce78a59 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/AnnotationScanTypeConverters.java +++ b/core/camel-main/src/test/java/org/apache/camel/main/scan/MyScanConfiguration.java @@ -14,21 +14,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.spi; +package org.apache.camel.main.scan; -/** - * A {@link org.apache.camel.TypeConverter} which is capable of annotation scanning for - * {@link org.apache.camel.Converter} classes and add these as type converters. - * <p/> - * This is using Camel 2.x style and its recommended to migrate to @Converter(loader = true) for fast type converter - * mode. - */ -public interface AnnotationScanTypeConverters { +import org.apache.camel.CamelContext; +import org.apache.camel.main.CamelConfiguration; +import org.junit.jupiter.api.Assertions; + +public class MyScanConfiguration implements CamelConfiguration { + + @Override + public void configure(CamelContext camelContext) throws Exception { + Assertions.assertNotNull(camelContext); + camelContext.getGlobalOptions().put("scanConfigured", "true"); + } - /** - * Scan for {@link org.apache.camel.Converter} classes and add those as type converters. - * - * @throws Exception is thrown if error happened - */ - void scanTypeConverters() throws Exception; } diff --git a/docs/user-manual/modules/ROOT/pages/camel-3x-upgrade-guide-3_16.adoc b/docs/user-manual/modules/ROOT/pages/camel-3x-upgrade-guide-3_16.adoc new file mode 100644 index 0000000..0de3f0b --- /dev/null +++ b/docs/user-manual/modules/ROOT/pages/camel-3x-upgrade-guide-3_16.adoc @@ -0,0 +1,12 @@ += Apache Camel 3.x Upgrade Guide + +This document is for helping you upgrade your Apache Camel application +from Camel 3.x to 3.y. For example if you are upgrading Camel 3.0 to 3.2, then you should follow the guides +from both 3.0 to 3.1 and 3.1 to 3.2. + +== Upgrading Camel 3.15 to 3.16 + +=== camel-main + +The option `camel.main.packageScanRouteBuilders` has been renamed to `camel.main.basePackageScan`. + diff --git a/docs/user-manual/modules/ROOT/pages/camel-3x-upgrade-guide.adoc b/docs/user-manual/modules/ROOT/pages/camel-3x-upgrade-guide.adoc index b7dff7f..945f0a2 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-3x-upgrade-guide.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-3x-upgrade-guide.adoc @@ -24,3 +24,4 @@ You can find upgrade guide for each release in the following pages: - xref:camel-3x-upgrade-guide-3_13.adoc[Upgrade guide 3.12 -> 3.13] - xref:camel-3x-upgrade-guide-3_14.adoc[Upgrade guide 3.13 -> 3.14] - xref:camel-3x-upgrade-guide-3_15.adoc[Upgrade guide 3.14 -> 3.15] +- xref:camel-3x-upgrade-guide-3_16.adoc[Upgrade guide 3.15 -> 3.16]
