This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch gs in repository https://gitbox.apache.org/repos/asf/camel.git
commit 7f060889f6c79dc38bb356bf8a6d4dd597b1a95d Author: Claus Ibsen <[email protected]> AuthorDate: Mon Jul 14 12:07:53 2025 +0200 CAMEL-22214: camel-groovy - Allow to pre-load groovy source files for shared functions and DTOs --- .../main/camel-main-configuration-metadata.json | 1 + .../src/main/docs/groovy-language.adoc | 32 ++++++++++++++- .../groovy/DefaultGroovyScriptCompiler.java | 47 +++++++++++++++------- .../groovy/DefaultGroovyCompilerTest.java | 2 +- .../processor/groovy/GroovyCompilerRouteTest.java | 2 +- .../groovy/MainGroovyCompilerRouteTest.java | 4 +- .../camel-main-configuration-metadata.json | 2 +- core/camel-main/src/main/docs/main.adoc | 2 +- .../camel/main/DefaultConfigurationProperties.java | 14 ++++++- 9 files changed, 83 insertions(+), 23 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 ecd123e7a0b..c857def438b 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 @@ -72,6 +72,7 @@ { "name": "camel.main.extraShutdownTimeout", "description": "Extra timeout in seconds to graceful shutdown Camel. When Camel is shutting down then Camel first shutdown all the routes (shutdownTimeout). Then additional services is shutdown (extraShutdownTimeout).", "sourceType": "org.apache.camel.main.MainConfigurationProperties", "type": "integer", "javaType": "int", "defaultValue": 15 }, { "name": "camel.main.fileConfigurations", "description": "Directory to load additional configuration files that contains configuration values that takes precedence over any other configuration. This can be used to refer to files that may have secret configuration that has been mounted on the file system for containers. You can specify a pattern to load from sub directories and a name pattern such as \/var\/app\/secret\/.properties, multiple directories can be separated by comma.", " [...] { "name": "camel.main.globalOptions", "description": "Sets global options that can be referenced in the camel context Important: This has nothing to do with property placeholders, and is just a plain set of key\/value pairs which are used to configure global options on CamelContext, such as a maximum debug logging length etc.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "object", "javaType": "java.util.Map" }, + { "name": "camel.main.groovyScriptPattern", "description": "Directories to scan for groovy source to be pre-compiled. For example: scripts\/.groovy will scan inside the classpath folder scripts for all groovy source files. By default, sources are scanned from the classpath, but you can prefix with file: to use file system. The directories are using Ant-path style pattern, and multiple directories can be specified separated by comma.", "sourceType": "org.apache.camel.main.DefaultConfi [...] { "name": "camel.main.inflightRepositoryBrowseEnabled", "description": "Sets whether the inflight repository should allow browsing each inflight exchange. This is by default disabled as there is a very slight performance overhead when enabled.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": "false" }, { "name": "camel.main.javaRoutesExcludePattern", "description": "Used for exclusive filtering RouteBuilder classes which are collected from the registry or via classpath scanning. The exclusive filtering takes precedence over inclusive filtering. The pattern is using Ant-path style pattern. Multiple patterns can be specified separated by comma. For example to exclude all classes starting with Bar use: **\/Bar* To exclude all routes form a specific package use: com\/mycomp [...] { "name": "camel.main.javaRoutesIncludePattern", "description": "Used for inclusive filtering RouteBuilder classes which are collected from the registry or via classpath scanning. The exclusive filtering takes precedence over inclusive filtering. The pattern is using Ant-path style pattern. Multiple patterns can be specified separated by comma. Multiple patterns can be specified separated by comma. For example to include all classes starting with Foo use: **\/Foo To include a [...] diff --git a/components/camel-groovy/src/main/docs/groovy-language.adoc b/components/camel-groovy/src/main/docs/groovy-language.adoc index bba44aa4d4c..8077a2bdcc8 100644 --- a/components/camel-groovy/src/main/docs/groovy-language.adoc +++ b/components/camel-groovy/src/main/docs/groovy-language.adoc @@ -163,7 +163,37 @@ XML DSL:: </filter> </route> ---- - ==== +== Pre compiling shared groovy scripts + +*Preview* support level. + +In *Camel 4.14* we have added support for loading groovy source files and pre-compile +on startup. This allows to have a common set of groovy classes and functions which can be +used by Camel and Java. + +To enable this you need to configure this in `camel-main` via + +[source,properties] +---- +camel.main.groovyScriptPattern = myscript/*.groovy +---- + +Then in the `src/main/resources/myscript` folder you can have groovy source files that Camel +will pre-compile on startup, and make global available via a special `GroovyScriptClassLoader`. + +Because this class-loader is required to be in use for being able to load the groovy pre-compiled +classes, then this feature will only work via Camel which has control of classloading when used +with Camel features that would support this such as in the route DSL and elsewhere. + +However, there may be some features in Camel where this may not work (yet). + +IMPORTANT: This feature is only intended to include smaller groovy sources as small functions, DTOs +that makes it easier to use together with Camel for low-code integrations. It is not +intended to support Groovy as a general purpose programming language for Camel. For this kind +then you can use groovy and Java together and follow best practices for this, such as +using the joint-compilation via Maven / Gradle plugins during build. + + include::spring-boot:partial$starter.adoc[] diff --git a/components/camel-groovy/src/main/java/org/apache/camel/language/groovy/DefaultGroovyScriptCompiler.java b/components/camel-groovy/src/main/java/org/apache/camel/language/groovy/DefaultGroovyScriptCompiler.java index 2ec12ed6a79..23198c28a0f 100644 --- a/components/camel-groovy/src/main/java/org/apache/camel/language/groovy/DefaultGroovyScriptCompiler.java +++ b/components/camel-groovy/src/main/java/org/apache/camel/language/groovy/DefaultGroovyScriptCompiler.java @@ -16,8 +16,7 @@ */ package org.apache.camel.language.groovy; -import java.io.File; -import java.io.FileInputStream; +import java.util.ArrayList; import java.util.List; import groovy.lang.GroovyShell; @@ -27,7 +26,10 @@ import org.apache.camel.StaticService; import org.apache.camel.api.management.ManagedAttribute; import org.apache.camel.api.management.ManagedResource; import org.apache.camel.spi.GroovyScriptCompiler; +import org.apache.camel.spi.PackageScanResourceResolver; +import org.apache.camel.spi.Resource; import org.apache.camel.spi.annotations.JdkService; +import org.apache.camel.support.PluginHelper; import org.apache.camel.support.service.ServiceSupport; import org.apache.camel.util.IOHelper; import org.apache.camel.util.StopWatch; @@ -100,7 +102,6 @@ public class DefaultGroovyScriptCompiler extends ServiceSupport if (scriptPattern != null) { StopWatch watch = new StopWatch(); - // TODO: ant path style LOG.info("Pre compiling groovy scripts from: {}", scriptPattern); ClassLoader cl = camelContext.getApplicationContextClassLoader(); @@ -113,20 +114,38 @@ public class DefaultGroovyScriptCompiler extends ServiceSupport // make classloader available for groovy language camelContext.getCamelContextExtension().addContextPlugin(GroovyScriptClassLoader.class, classLoader); - CompilerConfiguration cc = new CompilerConfiguration(); - cc.setClasspathList(List.of(scriptPattern)); + // scan for groovy source files to include + List<String> cps = new ArrayList<>(); + List<String> codes = new ArrayList<>(); + PackageScanResourceResolver resolver = PluginHelper.getPackageScanResourceResolver(camelContext); + for (String pattern : scriptPattern.split(",")) { + for (Resource resource : resolver.findResources(pattern)) { + if (resource.exists()) { + String loc = null; + if ("classpath".equals(resource.getScheme())) { + loc = resource.getLocation(); + } else if ("file".equals(resource.getScheme())) { + loc = resource.getLocation(); + } + if (loc != null) { + cps.add(loc); + String code = IOHelper.loadText(resource.getInputStream()); + codes.add(code); + } + } + } + } + // setup compiler via groovy shell + CompilerConfiguration cc = new CompilerConfiguration(); + cc.setClasspathList(cps); GroovyShell shell = new GroovyShell(cl, cc); - // discover each class from the folder - File[] files = new File(scriptPattern).listFiles(); - if (files != null) { - for (File f : files) { - String code = IOHelper.loadText(new FileInputStream(f)); - Class<?> clazz = shell.getClassLoader().parseClass(code); - if (clazz != null) { - classLoader.addClass(clazz.getName(), clazz); - } + // parse code into classes and add to classloader + for (String code : codes) { + Class<?> clazz = shell.getClassLoader().parseClass(code); + if (clazz != null) { + classLoader.addClass(clazz.getName(), clazz); } } elapsed = watch.taken(); diff --git a/components/camel-groovy/src/test/java/org/apache/camel/processor/groovy/DefaultGroovyCompilerTest.java b/components/camel-groovy/src/test/java/org/apache/camel/processor/groovy/DefaultGroovyCompilerTest.java index dbbdab88a49..31350946601 100644 --- a/components/camel-groovy/src/test/java/org/apache/camel/processor/groovy/DefaultGroovyCompilerTest.java +++ b/components/camel-groovy/src/test/java/org/apache/camel/processor/groovy/DefaultGroovyCompilerTest.java @@ -30,7 +30,7 @@ public class DefaultGroovyCompilerTest extends CamelTestSupport { public void testCompiler() throws Exception { DefaultGroovyScriptCompiler compiler = new DefaultGroovyScriptCompiler(); compiler.setCamelContext(context); - compiler.setScriptPattern("src/test/resources/myscript"); + compiler.setScriptPattern("myscript/*.groovy"); compiler.start(); Class<?> clazz = compiler.loadClass("Dude"); diff --git a/components/camel-groovy/src/test/java/org/apache/camel/processor/groovy/GroovyCompilerRouteTest.java b/components/camel-groovy/src/test/java/org/apache/camel/processor/groovy/GroovyCompilerRouteTest.java index e7a7a270d68..f29a827801a 100644 --- a/components/camel-groovy/src/test/java/org/apache/camel/processor/groovy/GroovyCompilerRouteTest.java +++ b/components/camel-groovy/src/test/java/org/apache/camel/processor/groovy/GroovyCompilerRouteTest.java @@ -32,7 +32,7 @@ public class GroovyCompilerRouteTest extends CamelTestSupport { DefaultGroovyScriptCompiler compiler = new DefaultGroovyScriptCompiler(); compiler.setCamelContext(context); - compiler.setScriptPattern("src/test/resources/myscript"); + compiler.setScriptPattern("file:src/test/resources/myscript/*.groovy"); context.addService(compiler); return context; diff --git a/components/camel-groovy/src/test/java/org/apache/camel/processor/groovy/MainGroovyCompilerRouteTest.java b/components/camel-groovy/src/test/java/org/apache/camel/processor/groovy/MainGroovyCompilerRouteTest.java index d8c5b7a7291..d9319504930 100644 --- a/components/camel-groovy/src/test/java/org/apache/camel/processor/groovy/MainGroovyCompilerRouteTest.java +++ b/components/camel-groovy/src/test/java/org/apache/camel/processor/groovy/MainGroovyCompilerRouteTest.java @@ -31,7 +31,7 @@ public class MainGroovyCompilerRouteTest { public void testCompilerRoute() throws Exception { Main main = new Main(); main.configure().addRoutesBuilder(createRouteBuilder()); - main.configure().withGroovyScriptPattern("src/test/resources/myscript"); + main.configure().withGroovyScriptPattern("myscript/*.groovy"); main.start(); CamelContext context = main.getCamelContext(); @@ -45,7 +45,7 @@ public class MainGroovyCompilerRouteTest { DefaultGroovyScriptCompiler compiler = context.hasService(DefaultGroovyScriptCompiler.class); Assertions.assertNotNull(compiler); - Assertions.assertEquals("src/test/resources/myscript", compiler.getScriptPattern()); + Assertions.assertEquals("myscript/*.groovy", compiler.getScriptPattern()); Assertions.assertEquals(2, compiler.getClassesSize()); Assertions.assertTrue(compiler.getCompileTime() > 0, "Should take time to compile, was: " + compiler.getCompileTime()); 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 d01b20ce547..c857def438b 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 @@ -72,7 +72,7 @@ { "name": "camel.main.extraShutdownTimeout", "description": "Extra timeout in seconds to graceful shutdown Camel. When Camel is shutting down then Camel first shutdown all the routes (shutdownTimeout). Then additional services is shutdown (extraShutdownTimeout).", "sourceType": "org.apache.camel.main.MainConfigurationProperties", "type": "integer", "javaType": "int", "defaultValue": 15 }, { "name": "camel.main.fileConfigurations", "description": "Directory to load additional configuration files that contains configuration values that takes precedence over any other configuration. This can be used to refer to files that may have secret configuration that has been mounted on the file system for containers. You can specify a pattern to load from sub directories and a name pattern such as \/var\/app\/secret\/.properties, multiple directories can be separated by comma.", " [...] { "name": "camel.main.globalOptions", "description": "Sets global options that can be referenced in the camel context Important: This has nothing to do with property placeholders, and is just a plain set of key\/value pairs which are used to configure global options on CamelContext, such as a maximum debug logging length etc.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "object", "javaType": "java.util.Map" }, - { "name": "camel.main.groovyScriptPattern", "description": "Directories to scan for groovy source to be pre-compiled. The directories are using Ant-path style pattern, and multiple directories can be specified separated by comma.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "string", "javaType": "java.lang.String" }, + { "name": "camel.main.groovyScriptPattern", "description": "Directories to scan for groovy source to be pre-compiled. For example: scripts\/.groovy will scan inside the classpath folder scripts for all groovy source files. By default, sources are scanned from the classpath, but you can prefix with file: to use file system. The directories are using Ant-path style pattern, and multiple directories can be specified separated by comma.", "sourceType": "org.apache.camel.main.DefaultConfi [...] { "name": "camel.main.inflightRepositoryBrowseEnabled", "description": "Sets whether the inflight repository should allow browsing each inflight exchange. This is by default disabled as there is a very slight performance overhead when enabled.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": "false" }, { "name": "camel.main.javaRoutesExcludePattern", "description": "Used for exclusive filtering RouteBuilder classes which are collected from the registry or via classpath scanning. The exclusive filtering takes precedence over inclusive filtering. The pattern is using Ant-path style pattern. Multiple patterns can be specified separated by comma. For example to exclude all classes starting with Bar use: **\/Bar* To exclude all routes form a specific package use: com\/mycomp [...] { "name": "camel.main.javaRoutesIncludePattern", "description": "Used for inclusive filtering RouteBuilder classes which are collected from the registry or via classpath scanning. The exclusive filtering takes precedence over inclusive filtering. The pattern is using Ant-path style pattern. Multiple patterns can be specified separated by comma. Multiple patterns can be specified separated by comma. For example to include all classes starting with Foo use: **\/Foo To include a [...] diff --git a/core/camel-main/src/main/docs/main.adoc b/core/camel-main/src/main/docs/main.adoc index 8c1452e51ee..dcbbafc5283 100644 --- a/core/camel-main/src/main/docs/main.adoc +++ b/core/camel-main/src/main/docs/main.adoc @@ -69,7 +69,7 @@ The camel.main supports 123 options, which are listed below. | *camel.main.extraShutdown{zwsp}Timeout* | Extra timeout in seconds to graceful shutdown Camel. When Camel is shutting down then Camel first shutdown all the routes (shutdownTimeout). Then additional services is shutdown (extraShutdownTimeout). | 15 | int | *camel.main.fileConfigurations* | Directory to load additional configuration files that contains configuration values that takes precedence over any other configuration. This can be used to refer to files that may have secret configuration that has been mounted on the file system for containers. You can specify a pattern to load from sub directories and a name pattern such as /var/app/secret/.properties, multiple directories can be separated by comma. | | String | *camel.main.globalOptions* | Sets global options that can be referenced in the camel context Important: This has nothing to do with property placeholders, and is just a plain set of key/value pairs which are used to configure global options on CamelContext, such as a maximum debug logging length etc. | | Map -| *camel.main.groovyScriptPattern* | Directories to scan for groovy source to be pre-compiled. The directories are using Ant-path style pattern, and multiple directories can be specified separated by comma. | | String +| *camel.main.groovyScriptPattern* | Directories to scan for groovy source to be pre-compiled. For example: scripts/.groovy will scan inside the classpath folder scripts for all groovy source files. By default, sources are scanned from the classpath, but you can prefix with file: to use file system. The directories are using Ant-path style pattern, and multiple directories can be specified separated by comma. | | String | *camel.main.inflightRepository{zwsp}BrowseEnabled* | Sets whether the inflight repository should allow browsing each inflight exchange. This is by default disabled as there is a very slight performance overhead when enabled. | false | boolean | *camel.main.javaRoutesExclude{zwsp}Pattern* | Used for exclusive filtering RouteBuilder classes which are collected from the registry or via classpath scanning. The exclusive filtering takes precedence over inclusive filtering. The pattern is using Ant-path style pattern. Multiple patterns can be specified separated by comma. For example to exclude all classes starting with Bar use: **/Bar* To exclude all routes form a specific package use: com/mycompany/bar/* To exclud [...] | *camel.main.javaRoutesInclude{zwsp}Pattern* | Used for inclusive filtering RouteBuilder classes which are collected from the registry or via classpath scanning. The exclusive filtering takes precedence over inclusive filtering. The pattern is using Ant-path style pattern. Multiple patterns can be specified separated by comma. Multiple patterns can be specified separated by comma. For example to include all classes starting with Foo use: **/Foo To include all routes form a speci [...] diff --git a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java index 1228a4f3fd9..986488440f4 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java @@ -1400,7 +1400,12 @@ public abstract class DefaultConfigurationProperties<T> { } /** - * Directories to scan for groovy source to be pre-compiled. The directories are using Ant-path style pattern, and + * Directories to scan for groovy source to be pre-compiled. + * For example: scripts/*.groovy will scan inside the classpath folder scripts for all groovy source files. + * + * By default, sources are scanned from the classpath, but you can prefix with file: to use file system. + * + * The directories are using Ant-path style pattern, and * multiple directories can be specified separated by comma. */ public void setGroovyScriptPattern(String groovyScriptPattern) { @@ -2782,7 +2787,12 @@ public abstract class DefaultConfigurationProperties<T> { } /** - * Directories to scan for groovy source to be pre-compiled. The directories are using Ant-path style pattern, and + * Directories to scan for groovy source to be pre-compiled. + * For example: scripts/*.groovy will scan inside the classpath folder scripts for all groovy source files. + * + * By default, sources are scanned from the classpath, but you can prefix with file: to use file system. + * + * The directories are using Ant-path style pattern, and * multiple directories can be specified separated by comma. */ public T withGroovyScriptPattern(String groovyScriptPattern) {
