This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch CAMEL-20134
in repository https://gitbox.apache.org/repos/asf/camel.git

commit f9c9c77d8420f1a8759426bbeef9a63b08fa7bfd
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Nov 21 09:58:03 2023 +0100

    CAMEL-20134: camel-joor - Allow to compile scrip with classloader from Camel
---
 .../main/camel-main-configuration-metadata.json    |  1 +
 .../camel/language/joor/CamelJoorClassLoader.java  | 90 ++++++++++++++++++++++
 .../camel/language/joor/JoorScriptingCompiler.java | 13 +++-
 .../java/org/apache/camel/spi/CompileStrategy.java | 38 ++++-----
 .../camel/impl/engine/DefaultCompileStrategy.java  | 32 +++-----
 .../MainConfigurationPropertiesConfigurer.java     |  6 ++
 .../camel-main-configuration-metadata.json         |  1 +
 core/camel-main/src/main/docs/main.adoc            |  3 +-
 .../org/apache/camel/main/BaseMainSupport.java     | 16 +++-
 .../camel/main/DefaultConfigurationConfigurer.java | 11 +++
 .../camel/main/DefaultConfigurationProperties.java | 21 +++++
 .../apache/camel/main/MainCommandLineSupport.java  |  8 ++
 .../camel/dsl/java/joor/JavaJoorClassLoader.java   | 57 +++++++++++++-
 .../dsl/java/joor/JavaRoutesBuilderLoader.java     |  9 ++-
 .../apache/camel/dsl/jbang/core/commands/Run.java  |  1 +
 .../java/org/apache/camel/main/KameletMain.java    | 21 ++++-
 16 files changed, 275 insertions(+), 53 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 b5d6c587081..72f4180cad8 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
@@ -35,6 +35,7 @@
     { "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 [...]
     { "name": "camel.main.camelEventsTimestampEnabled", "description": 
"Whether to include timestamps for all emitted Camel Events. Enabling this 
allows to know fine-grained at what time each event was emitted, which can be 
used for reporting to report exactly the time of the events. This is by default 
false to avoid the overhead of including this information.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": [...]
     { "name": "camel.main.caseInsensitiveHeaders", "description": "Whether to 
use case sensitive or insensitive headers. Important: When using case sensitive 
(this is set to false). Then the map is case sensitive which means headers such 
as content-type and Content-Type are two different keys which can be a problem 
for some protocols such as HTTP based, which rely on case insensitive headers. 
However case sensitive implementations can yield faster performance. Therefore 
use case sensitiv [...]
+    { "name": "camel.main.compileWorkDir", "description": "Work directory for 
compiler. Can be used to write compiled classes or other resources.", 
"sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": 
"string", "javaType": "java.lang.String" },
     { "name": "camel.main.configurationClasses", "description": "Sets classes 
names that will be used to configure the camel context as example by providing 
custom beans through org.apache.camel.BindToRegistry annotation.", 
"sourceType": "org.apache.camel.main.MainConfigurationProperties", "type": 
"string", "javaType": "java.lang.String" },
     { "name": "camel.main.configurations", "description": "Sets the 
configuration objects used to configure the camel context.", "sourceType": 
"org.apache.camel.main.MainConfigurationProperties", "type": "object", 
"javaType": "java.util.List" },
     { "name": "camel.main.consumerTemplateCacheSize", "description": "Consumer 
template endpoints cache size.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "integer", 
"javaType": "int", "defaultValue": 1000 },
diff --git 
a/components/camel-joor/src/main/java/org/apache/camel/language/joor/CamelJoorClassLoader.java
 
b/components/camel-joor/src/main/java/org/apache/camel/language/joor/CamelJoorClassLoader.java
new file mode 100644
index 00000000000..8cad0472c06
--- /dev/null
+++ 
b/components/camel-joor/src/main/java/org/apache/camel/language/joor/CamelJoorClassLoader.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.language.joor;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.CompileStrategy;
+
+/**
+ * ClassLoader using {@link org.apache.camel.spi.ClassResolver} from Camel
+ */
+public class CamelJoorClassLoader extends URLClassLoader {
+
+    private final CamelContext camelContext;
+    private final ClassLoader parent;
+
+    public CamelJoorClassLoader(URLClassLoader parent, CamelContext 
camelContext) {
+        super(parent.getURLs(), parent);
+        this.parent = parent;
+        this.camelContext = camelContext;
+    }
+
+    @Override
+    public String getName() {
+        return "CamelJoorClassLoader";
+    }
+
+    @Override
+    public URL[] getURLs() {
+        List<URL> answer = new ArrayList<>();
+        // add compile work-dir as URL so jOOR is able to dynamic load classes 
that has been compiled
+        CompileStrategy cs = 
camelContext.getCamelContextExtension().getContextPlugin(CompileStrategy.class);
+        if (cs != null && cs.getWorkDir() != null) {
+            try {
+                File dir = new File(cs.getWorkDir());
+                answer.add(dir.toURI().toURL());
+            } catch (MalformedURLException e) {
+                // ignore
+            }
+        }
+        answer.addAll(Arrays.asList(super.getURLs()));
+        URL[] arr = answer.toArray(new URL[0]);
+        return arr;
+    }
+
+    @Override
+    protected Class<?> findClass(String name) throws ClassNotFoundException {
+        for (ClassLoader cl : 
camelContext.getClassResolver().getClassLoaders()) {
+            try {
+                return cl.loadClass(name);
+            } catch (ClassNotFoundException e) {
+                // ignore
+            }
+        }
+        return parent.loadClass(name);
+    }
+
+    @Override
+    public Class<?> loadClass(String name) throws ClassNotFoundException {
+        for (ClassLoader cl : 
camelContext.getClassResolver().getClassLoaders()) {
+            try {
+                return cl.loadClass(name);
+            } catch (ClassNotFoundException e) {
+                // ignore
+            }
+        }
+        return parent.loadClass(name);
+    }
+}
diff --git 
a/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorScriptingCompiler.java
 
b/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorScriptingCompiler.java
index 5e5a9252fbf..ab57d41999b 100644
--- 
a/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorScriptingCompiler.java
+++ 
b/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorScriptingCompiler.java
@@ -16,6 +16,8 @@
  */
 package org.apache.camel.language.joor;
 
+import java.lang.invoke.MethodHandles;
+import java.net.URLClassLoader;
 import java.util.HashMap;
 import java.util.LinkedHashSet;
 import java.util.Map;
@@ -32,6 +34,7 @@ import org.apache.camel.support.ScriptHelper;
 import org.apache.camel.support.service.ServiceSupport;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.StopWatch;
+import org.joor.CompileOptions;
 import org.joor.Reflect;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -82,7 +85,15 @@ public class JoorScriptingCompiler extends ServiceSupport 
implements StaticServi
             if (LOG.isDebugEnabled()) {
                 LOG.debug("Compiling code:\n\n{}\n", code);
             }
-            Reflect ref = Reflect.compile(className, code);
+            // include classloader from Camel, so we can load any already 
compiled and loaded classes
+            CompileOptions co = new CompileOptions();
+            ClassLoader parent = 
MethodHandles.lookup().lookupClass().getClassLoader();
+            if (parent instanceof URLClassLoader ucl) {
+                ClassLoader cl = new CamelJoorClassLoader(ucl, camelContext);
+                co = new CompileOptions();
+                co = co.classLoader(cl);
+            }
+            Reflect ref = Reflect.compile(className, code, co);
             Class<?> clazz = ref.type();
             answer = (JoorScriptingMethod) 
clazz.getConstructor(CamelContext.class).newInstance(camelContext);
         } catch (Exception e) {
diff --git 
a/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaJoorClassLoader.java
 b/core/camel-api/src/main/java/org/apache/camel/spi/CompileStrategy.java
similarity index 52%
copy from 
dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaJoorClassLoader.java
copy to core/camel-api/src/main/java/org/apache/camel/spi/CompileStrategy.java
index f83ba0953f7..31111f571f3 100644
--- 
a/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaJoorClassLoader.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/CompileStrategy.java
@@ -14,32 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.dsl.java.joor;
+package org.apache.camel.spi;
 
-import java.util.HashMap;
-import java.util.Map;
-
-public class JavaJoorClassLoader extends ClassLoader {
-
-    private final Map<String, Class<?>> classes = new HashMap<>();
-
-    public JavaJoorClassLoader() {
-        super(JavaJoorClassLoader.class.getClassLoader());
-    }
-
-    @Override
-    public Class<?> loadClass(String name) throws ClassNotFoundException {
-        Class<?> clazz = classes.get(name);
-        if (clazz != null) {
-            return clazz;
-        }
-        throw new ClassNotFoundException(name);
-    }
+/**
+ * Strategy for runtime compiled languages and DSLs.
+ */
+public interface CompileStrategy {
 
-    public void addClass(String name, Class<?> clazz) {
-        if (name != null && clazz != null) {
-            classes.put(name, clazz);
-        }
-    }
+    /**
+     * Work directory for compiler. Can be used to write compiled classes or 
other resources.
+     */
+    String getWorkDir();
 
+    /**
+     * Work directory for compiler. Can be used to write compiled classes or 
other resources.
+     */
+    void setWorkDir(String compileWorkDir);
 }
diff --git 
a/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaJoorClassLoader.java
 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCompileStrategy.java
similarity index 54%
copy from 
dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaJoorClassLoader.java
copy to 
core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCompileStrategy.java
index f83ba0953f7..d988c244792 100644
--- 
a/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaJoorClassLoader.java
+++ 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCompileStrategy.java
@@ -14,32 +14,24 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.dsl.java.joor;
+package org.apache.camel.impl.engine;
 
-import java.util.HashMap;
-import java.util.Map;
+import org.apache.camel.spi.CompileStrategy;
 
-public class JavaJoorClassLoader extends ClassLoader {
-
-    private final Map<String, Class<?>> classes = new HashMap<>();
+/**
+ * Default {@link CompileStrategy}.
+ */
+public class DefaultCompileStrategy implements CompileStrategy {
 
-    public JavaJoorClassLoader() {
-        super(JavaJoorClassLoader.class.getClassLoader());
-    }
+    private String workDir;
 
     @Override
-    public Class<?> loadClass(String name) throws ClassNotFoundException {
-        Class<?> clazz = classes.get(name);
-        if (clazz != null) {
-            return clazz;
-        }
-        throw new ClassNotFoundException(name);
+    public String getWorkDir() {
+        return workDir;
     }
 
-    public void addClass(String name, Class<?> clazz) {
-        if (name != null && clazz != null) {
-            classes.put(name, clazz);
-        }
+    @Override
+    public void setWorkDir(String workDir) {
+        this.workDir = workDir;
     }
-
 }
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 5880968777f..fab74363ba5 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
@@ -57,6 +57,8 @@ public class MainConfigurationPropertiesConfigurer extends 
org.apache.camel.supp
         case "CamelEventsTimestampEnabled": 
target.setCamelEventsTimestampEnabled(property(camelContext, boolean.class, 
value)); return true;
         case "caseinsensitiveheaders":
         case "CaseInsensitiveHeaders": 
target.setCaseInsensitiveHeaders(property(camelContext, boolean.class, value)); 
return true;
+        case "compileworkdir":
+        case "CompileWorkDir": target.setCompileWorkDir(property(camelContext, 
java.lang.String.class, value)); return true;
         case "configurationclasses":
         case "ConfigurationClasses": 
target.setConfigurationClasses(property(camelContext, java.lang.String.class, 
value)); return true;
         case "configurations":
@@ -316,6 +318,8 @@ public class MainConfigurationPropertiesConfigurer extends 
org.apache.camel.supp
         case "CamelEventsTimestampEnabled": return boolean.class;
         case "caseinsensitiveheaders":
         case "CaseInsensitiveHeaders": return boolean.class;
+        case "compileworkdir":
+        case "CompileWorkDir": return java.lang.String.class;
         case "configurationclasses":
         case "ConfigurationClasses": return java.lang.String.class;
         case "configurations":
@@ -576,6 +580,8 @@ public class MainConfigurationPropertiesConfigurer extends 
org.apache.camel.supp
         case "CamelEventsTimestampEnabled": return 
target.isCamelEventsTimestampEnabled();
         case "caseinsensitiveheaders":
         case "CaseInsensitiveHeaders": return 
target.isCaseInsensitiveHeaders();
+        case "compileworkdir":
+        case "CompileWorkDir": return target.getCompileWorkDir();
         case "configurationclasses":
         case "ConfigurationClasses": return target.getConfigurationClasses();
         case "configurations":
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 b5d6c587081..72f4180cad8 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
@@ -35,6 +35,7 @@
     { "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 [...]
     { "name": "camel.main.camelEventsTimestampEnabled", "description": 
"Whether to include timestamps for all emitted Camel Events. Enabling this 
allows to know fine-grained at what time each event was emitted, which can be 
used for reporting to report exactly the time of the events. This is by default 
false to avoid the overhead of including this information.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": [...]
     { "name": "camel.main.caseInsensitiveHeaders", "description": "Whether to 
use case sensitive or insensitive headers. Important: When using case sensitive 
(this is set to false). Then the map is case sensitive which means headers such 
as content-type and Content-Type are two different keys which can be a problem 
for some protocols such as HTTP based, which rely on case insensitive headers. 
However case sensitive implementations can yield faster performance. Therefore 
use case sensitiv [...]
+    { "name": "camel.main.compileWorkDir", "description": "Work directory for 
compiler. Can be used to write compiled classes or other resources.", 
"sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": 
"string", "javaType": "java.lang.String" },
     { "name": "camel.main.configurationClasses", "description": "Sets classes 
names that will be used to configure the camel context as example by providing 
custom beans through org.apache.camel.BindToRegistry annotation.", 
"sourceType": "org.apache.camel.main.MainConfigurationProperties", "type": 
"string", "javaType": "java.lang.String" },
     { "name": "camel.main.configurations", "description": "Sets the 
configuration objects used to configure the camel context.", "sourceType": 
"org.apache.camel.main.MainConfigurationProperties", "type": "object", 
"javaType": "java.util.List" },
     { "name": "camel.main.consumerTemplateCacheSize", "description": "Consumer 
template endpoints cache size.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "integer", 
"javaType": "int", "defaultValue": 1000 },
diff --git a/core/camel-main/src/main/docs/main.adoc 
b/core/camel-main/src/main/docs/main.adoc
index beac1024996..cf27329b981 100644
--- a/core/camel-main/src/main/docs/main.adoc
+++ b/core/camel-main/src/main/docs/main.adoc
@@ -19,7 +19,7 @@ The following tables lists all the options:
 
 // main options: START
 === Camel Main configurations
-The camel.main supports 126 options, which are listed below.
+The camel.main supports 127 options, which are listed below.
 
 [width="100%",cols="2,5,^1,2",options="header"]
 |===
@@ -42,6 +42,7 @@ The camel.main supports 126 options, which are listed below.
 | *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 [...]
 | *camel.main.camelEvents{zwsp}TimestampEnabled* | Whether to include 
timestamps for all emitted Camel Events. Enabling this allows to know 
fine-grained at what time each event was emitted, which can be used for 
reporting to report exactly the time of the events. This is by default false to 
avoid the overhead of including this information. | false | boolean
 | *camel.main.caseInsensitive{zwsp}Headers* | Whether to use case sensitive or 
insensitive headers. Important: When using case sensitive (this is set to 
false). Then the map is case sensitive which means headers such as content-type 
and Content-Type are two different keys which can be a problem for some 
protocols such as HTTP based, which rely on case insensitive headers. However 
case sensitive implementations can yield faster performance. Therefore use case 
sensitive implementation with [...]
+| *camel.main.compileWorkDir* | Work directory for compiler. Can be used to 
write compiled classes or other resources. |  | String
 | *camel.main.configuration{zwsp}Classes* | Sets classes names that will be 
used to configure the camel context as example by providing custom beans 
through org.apache.camel.BindToRegistry annotation. |  | String
 | *camel.main.configurations* | Sets the configuration objects used to 
configure the camel context. |  | List
 | *camel.main.consumerTemplate{zwsp}CacheSize* | Consumer template endpoints 
cache size. | 1000 | int
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 e04d05609b9..47935f61e0e 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
@@ -51,6 +51,7 @@ import org.apache.camel.health.HealthCheck;
 import org.apache.camel.health.HealthCheckRegistry;
 import org.apache.camel.health.HealthCheckRepository;
 import org.apache.camel.impl.debugger.DefaultBacklogDebugger;
+import org.apache.camel.impl.engine.DefaultCompileStrategy;
 import org.apache.camel.impl.engine.DefaultRoutesLoader;
 import org.apache.camel.saga.CamelSagaService;
 import org.apache.camel.spi.AutowiredLifecycleStrategy;
@@ -59,6 +60,7 @@ import org.apache.camel.spi.CamelBeanPostProcessor;
 import org.apache.camel.spi.CamelEvent;
 import org.apache.camel.spi.CamelMetricsService;
 import org.apache.camel.spi.CamelTracingService;
+import org.apache.camel.spi.CompileStrategy;
 import org.apache.camel.spi.ContextReloadStrategy;
 import org.apache.camel.spi.DataFormat;
 import org.apache.camel.spi.Language;
@@ -568,9 +570,21 @@ public abstract class BaseMainSupport extends BaseService {
 
     protected void configureRoutesLoader(CamelContext camelContext) {
         // use main based routes loader
+        ExtendedCamelContext ecc = camelContext.getCamelContextExtension();
+
+        // need to configure compile work dir as its used from routes loader 
when it discovered code to dynamic compile
+        if (mainConfigurationProperties.getCompileWorkDir() != null) {
+            CompileStrategy cs = 
camelContext.getCamelContextExtension().getContextPlugin(CompileStrategy.class);
+            if (cs == null) {
+                cs = new DefaultCompileStrategy();
+                ecc.addContextPlugin(CompileStrategy.class, cs);
+            }
+            cs.setWorkDir(mainConfigurationProperties.getCompileWorkDir());
+        }
+
         RoutesLoader loader = new DefaultRoutesLoader();
         
loader.setIgnoreLoadingError(mainConfigurationProperties.isRoutesCollectorIgnoreLoadingError());
-        
camelContext.getCamelContextExtension().addContextPlugin(RoutesLoader.class, 
loader);
+        ecc.addContextPlugin(RoutesLoader.class, loader);
     }
 
     protected void modelineRoutes(CamelContext camelContext) throws Exception {
diff --git 
a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java
 
b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java
index 7b6f4bcedff..a8c0a980f04 100644
--- 
a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java
+++ 
b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java
@@ -35,6 +35,7 @@ import org.apache.camel.console.DevConsoleRegistry;
 import org.apache.camel.health.HealthCheckRegistry;
 import org.apache.camel.health.HealthCheckRepository;
 import org.apache.camel.impl.debugger.BacklogTracer;
+import org.apache.camel.impl.engine.DefaultCompileStrategy;
 import org.apache.camel.impl.engine.PooledExchangeFactory;
 import org.apache.camel.impl.engine.PooledProcessorExchangeFactory;
 import org.apache.camel.impl.engine.PrototypeExchangeFactory;
@@ -47,6 +48,7 @@ import org.apache.camel.spi.BacklogDebugger;
 import org.apache.camel.spi.BeanIntrospection;
 import org.apache.camel.spi.ClassResolver;
 import org.apache.camel.spi.CliConnectorFactory;
+import org.apache.camel.spi.CompileStrategy;
 import org.apache.camel.spi.ContextReloadStrategy;
 import org.apache.camel.spi.Debugger;
 import org.apache.camel.spi.DumpRoutesStrategy;
@@ -330,6 +332,15 @@ public final class DefaultConfigurationConfigurer {
             
camelContext.getExecutorServiceManager().setThreadNamePattern(config.getThreadNamePattern());
         }
 
+        if (config.getCompileWorkDir() != null) {
+            CompileStrategy cs = ecc.getContextPlugin(CompileStrategy.class);
+            if (cs == null) {
+                cs = new DefaultCompileStrategy();
+                ecc.addContextPlugin(CompileStrategy.class, cs);
+            }
+            cs.setWorkDir(config.getCompileWorkDir());
+        }
+
         if (config.getRouteFilterIncludePattern() != null || 
config.getRouteFilterExcludePattern() != null) {
             
camelContext.getCamelContextExtension().getContextPlugin(Model.class).setRouteFilterPattern(
                     config.getRouteFilterIncludePattern(),
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 ef4d92347d1..d65aaecc9d1 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
@@ -112,6 +112,8 @@ public abstract class DefaultConfigurationProperties<T> {
     private boolean contextReloadEnabled;
     private boolean routesCollectorEnabled = true;
     private boolean routesCollectorIgnoreLoadingError;
+    @Metadata(label = "advanced")
+    private String compileWorkDir;
     private String javaRoutesIncludePattern;
     private String javaRoutesExcludePattern;
     private String routesIncludePattern = 
"classpath:camel/*,classpath:camel-template/*,classpath:camel-rest/*";
@@ -1166,6 +1168,17 @@ public abstract class DefaultConfigurationProperties<T> {
         this.routesCollectorIgnoreLoadingError = 
routesCollectorIgnoreLoadingError;
     }
 
+    public String getCompileWorkDir() {
+        return compileWorkDir;
+    }
+
+    /**
+     * Work directory for compiler. Can be used to write compiled classes or 
other resources.
+     */
+    public void setCompileWorkDir(String compileWorkDir) {
+        this.compileWorkDir = compileWorkDir;
+    }
+
     public String getJavaRoutesIncludePattern() {
         return javaRoutesIncludePattern;
     }
@@ -2482,6 +2495,14 @@ public abstract class DefaultConfigurationProperties<T> {
         return (T) this;
     }
 
+    /**
+     * Work directory for compiler. Can be used to write compiled classes or 
other resources.
+     */
+    public T withCompileWorkDir(String compileWorkDir) {
+        this.compileWorkDir = compileWorkDir;
+        return (T) this;
+    }
+
     /**
      * Used for inclusive filtering component scanning of RouteBuilder classes 
with @Component annotation. The exclusive
      * filtering takes precedence over inclusive filtering. The pattern is 
using Ant-path style pattern.
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 0504bcecf95..caa145f5a89 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
@@ -161,6 +161,14 @@ public abstract class MainCommandLineSupport extends 
MainSupport {
                 setPropertyPlaceholderLocations(parameter);
             }
         });
+        addOption(new ParameterOption(
+                "cwd", "compileWorkDir",
+                "Work directory for compiler. Can be used to write compiled 
classes or other resources.",
+                "compileWorkDir") {
+            protected void doProcess(String arg, String parameter, 
LinkedList<String> remainingArgs) {
+                configure().withCompileWorkDir(parameter);
+            }
+        });
     }
 
     /**
diff --git 
a/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaJoorClassLoader.java
 
b/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaJoorClassLoader.java
index f83ba0953f7..c1fa8a50573 100644
--- 
a/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaJoorClassLoader.java
+++ 
b/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaJoorClassLoader.java
@@ -16,17 +16,48 @@
  */
 package org.apache.camel.dsl.java.joor;
 
+import java.io.File;
+import java.io.FileOutputStream;
 import java.util.HashMap;
 import java.util.Map;
 
+import org.apache.camel.util.FileUtil;
+import org.apache.camel.util.IOHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 public class JavaJoorClassLoader extends ClassLoader {
 
+    private static final Logger LOG = 
LoggerFactory.getLogger(JavaJoorClassLoader.class);
     private final Map<String, Class<?>> classes = new HashMap<>();
+    private String compileDirectory;
 
     public JavaJoorClassLoader() {
         super(JavaJoorClassLoader.class.getClassLoader());
     }
 
+    public String getCompileDirectory() {
+        return compileDirectory;
+    }
+
+    public void setCompileDirectory(String compileDirectory) {
+        this.compileDirectory = compileDirectory;
+    }
+
+    @Override
+    public String getName() {
+        return "JavaJoorClassLoader";
+    }
+
+    @Override
+    protected Class<?> findClass(String name) throws ClassNotFoundException {
+        Class<?> clazz = classes.get(name);
+        if (clazz != null) {
+            return clazz;
+        }
+        throw new ClassNotFoundException(name);
+    }
+
     @Override
     public Class<?> loadClass(String name) throws ClassNotFoundException {
         Class<?> clazz = classes.get(name);
@@ -36,10 +67,34 @@ public class JavaJoorClassLoader extends ClassLoader {
         throw new ClassNotFoundException(name);
     }
 
-    public void addClass(String name, Class<?> clazz) {
+    public void addClass(String name, Class<?> clazz, byte[] code) {
         if (name != null && clazz != null) {
             classes.put(name, clazz);
         }
+        if (name != null && code != null && compileDirectory != null) {
+            saveByteCodeToDisk(compileDirectory, name, code);
+        }
+    }
+
+    private static void saveByteCodeToDisk(String outputDirectory, String 
name, byte[] byteCode) {
+        // write to disk (can be triggered multiple times so only write once)
+        String fname = name.replace('.', '/');
+        fname = outputDirectory + "/" + fname + ".class";
+        File target = new File(fname);
+        if (!target.exists()) {
+            // create work-dir if needed
+            String dir = FileUtil.onlyPath(fname);
+            new File(dir).mkdirs();
+            try {
+                FileOutputStream fos = new FileOutputStream(target);
+                LOG.debug("Writing compiled class: {} as bytecode to file: 
{}", name, target);
+                fos.write(byteCode);
+                IOHelper.close(fos);
+            } catch (Exception e) {
+                LOG.warn("Error writing compiled class: " + name + " as 
bytecode to file: " + target + " due to "
+                         + e.getMessage() + ". This exception is ignored.");
+            }
+        }
     }
 
 }
diff --git 
a/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaRoutesBuilderLoader.java
 
b/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaRoutesBuilderLoader.java
index c96ff16c786..54ea61bf897 100644
--- 
a/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaRoutesBuilderLoader.java
+++ 
b/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaRoutesBuilderLoader.java
@@ -35,6 +35,7 @@ import org.apache.camel.api.management.ManagedResource;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.dsl.support.ExtendedRouteBuilderLoaderSupport;
 import org.apache.camel.spi.CompilePostProcessor;
+import org.apache.camel.spi.CompileStrategy;
 import org.apache.camel.spi.Resource;
 import org.apache.camel.spi.ResourceAware;
 import org.apache.camel.spi.annotations.RoutesLoader;
@@ -70,6 +71,11 @@ public class JavaRoutesBuilderLoader extends 
ExtendedRouteBuilderLoaderSupport {
         CamelContext context = getCamelContext();
         if (context != null) {
             context.getClassResolver().addClassLoader(classLoader);
+            // use work dir for classloader as it writes compiled classes to 
disk
+            CompileStrategy cs = 
context.getCamelContextExtension().getContextPlugin(CompileStrategy.class);
+            if (cs != null && cs.getWorkDir() != null) {
+                classLoader.setCompileDirectory(cs.getWorkDir());
+            }
         }
     }
 
@@ -185,7 +191,8 @@ public class JavaRoutesBuilderLoader extends 
ExtendedRouteBuilderLoaderSupport {
 
         for (String className : result.getClassNames()) {
             Class<?> clazz = result.getClass(className);
-            classLoader.addClass(className, clazz);
+            byte[] code = result.getByteCode(className);
+            classLoader.addClass(className, clazz, code);
         }
 
         return result;
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
index 273169ddcbb..3df54e87611 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
@@ -458,6 +458,7 @@ public class Run extends CamelCommand {
         if (ignoreLoadingError) {
             writeSetting(main, profileProperties, 
"camel.jbang.ignoreLoadingError", "true");
         }
+        writeSetting(main, profileProperties, "camel.jbang.compileWorkDir", 
WORK_DIR + File.separator + "compile");
 
         if (gav != null) {
             writeSetting(main, profileProperties, "camel.jbang.gav", gav);
diff --git 
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java 
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
index b92b8561ebd..51fa8585d42 100644
--- 
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
+++ 
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
@@ -27,11 +27,13 @@ import java.util.TreeMap;
 import org.w3c.dom.Document;
 
 import org.apache.camel.CamelContext;
+import org.apache.camel.ExtendedCamelContext;
 import org.apache.camel.ManagementStatisticsLevel;
 import org.apache.camel.ProducerTemplate;
 import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.dsl.support.SourceLoader;
 import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.impl.engine.DefaultCompileStrategy;
 import org.apache.camel.main.download.AutoConfigureDownloadListener;
 import org.apache.camel.main.download.BasePackageScanDownloadListener;
 import org.apache.camel.main.download.CamelCustomClassLoader;
@@ -68,6 +70,7 @@ import org.apache.camel.reifier.ProcessorReifier;
 import org.apache.camel.spi.ClassResolver;
 import org.apache.camel.spi.CliConnector;
 import org.apache.camel.spi.CliConnectorFactory;
+import org.apache.camel.spi.CompileStrategy;
 import org.apache.camel.spi.ComponentResolver;
 import org.apache.camel.spi.DataFormatResolver;
 import org.apache.camel.spi.FactoryFinder;
@@ -498,7 +501,7 @@ public class KameletMain extends MainCommandLineSupport {
             if (jfrProfile != null) {
                 recorder.setRecordingProfile(jfrProfile.toString());
             }
-            answer.setStartupStepRecorder(recorder);
+            answer.getCamelContextExtension().setStartupStepRecorder(recorder);
         }
 
         // special for source compilation to a specific package based on Maven 
GAV
@@ -678,6 +681,19 @@ public class KameletMain extends MainCommandLineSupport {
 
     @Override
     protected void configureRoutesLoader(CamelContext camelContext) {
+        ExtendedCamelContext ecc = camelContext.getCamelContextExtension();
+
+        // need to configure compile work dir as its used from routes loader 
when it discovered code to dynamic compile
+        String dir = 
getInitialProperties().getProperty("camel.jbang.compileWorkDir");
+        if (dir != null) {
+            CompileStrategy cs = 
camelContext.getCamelContextExtension().getContextPlugin(CompileStrategy.class);
+            if (cs == null) {
+                cs = new DefaultCompileStrategy();
+                ecc.addContextPlugin(CompileStrategy.class, cs);
+            }
+            cs.setWorkDir(dir);
+        }
+
         if (download) {
             DependencyDownloaderRoutesLoader routesLoader;
 
@@ -690,8 +706,7 @@ public class KameletMain extends MainCommandLineSupport {
             
routesLoader.setIgnoreLoadingError(this.mainConfigurationProperties.isRoutesCollectorIgnoreLoadingError());
 
             // use resolvers that can auto downloaded
-            camelContext.getCamelContextExtension()
-                    .addContextPlugin(RoutesLoader.class, routesLoader);
+            ecc.addContextPlugin(RoutesLoader.class, routesLoader);
         } else {
             super.configureRoutesLoader(camelContext);
         }


Reply via email to