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: &#42;&#42;\/Bar&#42; 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: &#42;&#42;\/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: &#42;&#42;\/Bar&#42; 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: &#42;&#42;\/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: 
&#42;&#42;/Bar&#42; To exclude all routes form a specific package use: 
com/mycompany/bar/&#42; 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: &#42;&#42;/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) {

Reply via email to