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


The following commit(s) were added to refs/heads/main by this push:
     new 851a72cb448 CAMEL-22214: camel-groovy - Reloading of compiled sources
851a72cb448 is described below

commit 851a72cb448976ebd05985cf5cb160da0d0c6e80
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Jul 16 16:01:16 2025 +0200

    CAMEL-22214: camel-groovy - Reloading of compiled sources
---
 .../groovy/DefaultGroovyScriptCompiler.java        |  23 ++-
 .../camel/language/groovy/GroovyDevConsole.java    |  20 ++-
 .../language/groovy/GroovyScriptClassLoader.java   |   9 ++
 .../org/apache/camel/spi/GroovyScriptCompiler.java |   8 +
 .../camel/support/RouteOnDemandReloadStrategy.java |  10 +-
 .../camel/support/RouteWatcherReloadStrategy.java  |   5 +-
 .../camel/cli/connector/LocalCliConnector.java     |   7 +
 .../dsl/jbang/core/commands/CamelJBangMain.java    |   1 +
 .../jbang/core/commands/process/ListGroovy.java    | 162 +++++++++++++++++++++
 9 files changed, 239 insertions(+), 6 deletions(-)

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 6b2a936c185..9b62e9d16b7 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
@@ -19,6 +19,7 @@ package org.apache.camel.language.groovy;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Set;
 
 import groovy.lang.GroovyShell;
 import org.apache.camel.CamelContext;
@@ -26,6 +27,7 @@ import org.apache.camel.CamelContextAware;
 import org.apache.camel.Ordered;
 import org.apache.camel.StaticService;
 import org.apache.camel.api.management.ManagedAttribute;
+import org.apache.camel.api.management.ManagedOperation;
 import org.apache.camel.api.management.ManagedResource;
 import org.apache.camel.spi.CamelEvent;
 import org.apache.camel.spi.CompileStrategy;
@@ -61,6 +63,7 @@ public class DefaultGroovyScriptCompiler extends 
ServiceSupport
     private String workDir;
     private long taken;
     private int counter;
+    private long last;
     private boolean reload;
 
     @Override
@@ -84,7 +87,7 @@ public class DefaultGroovyScriptCompiler extends 
ServiceSupport
     }
 
     @ManagedAttribute(description = "Number of times Groovy compiler has 
executed")
-    public int getCompileSize() {
+    public int getCompileCounter() {
         return counter;
     }
 
@@ -109,6 +112,23 @@ public class DefaultGroovyScriptCompiler extends 
ServiceSupport
         return reload;
     }
 
+    @ManagedOperation(description = "The class names for the compiled Groovy 
sources")
+    public Set<String> compiledClassNames() {
+        return classLoader.getCompiledClassNames();
+    }
+
+    @ManagedAttribute(description = "Last time the Groovy compiler was used")
+    public long getLastCompilationTimestamp() {
+        return last;
+    }
+
+    @Override
+    public void recompile(Resource resource) throws Exception {
+        if (resource.exists()) {
+            doCompile(List.of(resource));
+        }
+    }
+
     @Override
     protected void doBuild() throws Exception {
         // register Groovy classloader to camel, so we are able to load 
classes we have compiled
@@ -224,6 +244,7 @@ public class DefaultGroovyScriptCompiler extends 
ServiceSupport
                 }
             }
             taken += watch.taken();
+            last = System.currentTimeMillis();
         }
     }
 
diff --git 
a/components/camel-groovy/src/main/java/org/apache/camel/language/groovy/GroovyDevConsole.java
 
b/components/camel-groovy/src/main/java/org/apache/camel/language/groovy/GroovyDevConsole.java
index ff7d56dfc2d..8b3aaddc1d2 100644
--- 
a/components/camel-groovy/src/main/java/org/apache/camel/language/groovy/GroovyDevConsole.java
+++ 
b/components/camel-groovy/src/main/java/org/apache/camel/language/groovy/GroovyDevConsole.java
@@ -20,6 +20,8 @@ import java.util.Map;
 
 import org.apache.camel.spi.annotations.DevConsole;
 import org.apache.camel.support.console.AbstractDevConsole;
+import org.apache.camel.util.TimeUtils;
+import org.apache.camel.util.json.JsonArray;
 import org.apache.camel.util.json.JsonObject;
 
 @DevConsole(name = "groovy", displayName = "Groovy", description = "Groovy 
Language")
@@ -36,13 +38,22 @@ public class GroovyDevConsole extends AbstractDevConsole {
         DefaultGroovyScriptCompiler compiler = 
getCamelContext().hasService(DefaultGroovyScriptCompiler.class);
         if (compiler != null) {
             sb.append(String.format("    Groovy Script Pattern: %s", 
compiler.getScriptPattern()));
-            sb.append(String.format("\n    Compiled Size: %s", 
compiler.getCompileSize()));
+            long last = compiler.getLastCompilationTimestamp();
+            if (last != 0) {
+                sb.append(String.format("\n    Last Compilation Ago: %s",
+                        
TimeUtils.printSince(compiler.getLastCompilationTimestamp())));
+            }
+            sb.append(String.format("\n    Compiled Counter: %s", 
compiler.getCompileCounter()));
             sb.append(String.format("\n    Compiled Classes: %s", 
compiler.getClassesSize()));
             sb.append(String.format("\n    Compiled Time: %s (ms)", 
compiler.getCompileTime()));
             sb.append(String.format("\n    Re-compile Enabled: %b", 
compiler.isRecompileEnabled()));
             if (compiler.getWorkDir() != null) {
                 sb.append(String.format("\n    Work Directory: %s", 
compiler.getWorkDir()));
             }
+            sb.append("\n    Classes");
+            for (String name : compiler.compiledClassNames()) {
+                sb.append(String.format("\n        %s", name));
+            }
         }
 
         return sb.toString();
@@ -56,13 +67,18 @@ public class GroovyDevConsole extends AbstractDevConsole {
         if (compiler != null) {
             JsonObject jo = new JsonObject();
             jo.put("groovyScriptPattern", compiler.getScriptPattern());
-            jo.put("compiledSize", compiler.getCompileSize());
+            jo.put("compiledCounter", compiler.getCompileCounter());
             jo.put("compiledClasses", compiler.getClassesSize());
             jo.put("compiledTime", compiler.getCompileTime());
             jo.put("recompileEnabled", compiler.isRecompileEnabled());
+            jo.put("lastCompilationTimestamp", 
compiler.getLastCompilationTimestamp());
             if (compiler.getWorkDir() != null) {
                 jo.put("workDir", compiler.getWorkDir());
             }
+            JsonArray arr = new JsonArray(compiler.compiledClassNames());
+            if (!arr.isEmpty()) {
+                jo.put("classes", arr);
+            }
             root.put("compiler", jo);
         }
 
diff --git 
a/components/camel-groovy/src/main/java/org/apache/camel/language/groovy/GroovyScriptClassLoader.java
 
b/components/camel-groovy/src/main/java/org/apache/camel/language/groovy/GroovyScriptClassLoader.java
index 5c503ad3679..638b17cb416 100644
--- 
a/components/camel-groovy/src/main/java/org/apache/camel/language/groovy/GroovyScriptClassLoader.java
+++ 
b/components/camel-groovy/src/main/java/org/apache/camel/language/groovy/GroovyScriptClassLoader.java
@@ -20,6 +20,8 @@ import java.io.Closeable;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
 
 import org.codehaus.groovy.runtime.InvokerHelper;
 
@@ -47,6 +49,13 @@ public class GroovyScriptClassLoader extends ClassLoader 
implements Closeable {
         return classes.size();
     }
 
+    /**
+     * The names of the classes that has been compiled and added to this 
classloader.
+     */
+    public Set<String> getCompiledClassNames() {
+        return new TreeSet<>(classes.keySet());
+    }
+
     @Override
     protected Class<?> findClass(String name) throws ClassNotFoundException {
         Class<?> clazz = classes.get(name);
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/GroovyScriptCompiler.java 
b/core/camel-api/src/main/java/org/apache/camel/spi/GroovyScriptCompiler.java
index bcbfc261859..563a822b17a 100644
--- 
a/core/camel-api/src/main/java/org/apache/camel/spi/GroovyScriptCompiler.java
+++ 
b/core/camel-api/src/main/java/org/apache/camel/spi/GroovyScriptCompiler.java
@@ -37,4 +37,12 @@ public interface GroovyScriptCompiler {
      * multiple directories can be specified separated by comma.
      */
     String getScriptPattern();
+
+    /**
+     * Compiles or re-compiles the given groovy source
+     *
+     * @param  resource  the groovy source
+     * @throws Exception is thrown if compilation error
+     */
+    void recompile(Resource resource) throws Exception;
 }
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/RouteOnDemandReloadStrategy.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/RouteOnDemandReloadStrategy.java
index a178c48da8e..a0d9a50bb7b 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/support/RouteOnDemandReloadStrategy.java
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/RouteOnDemandReloadStrategy.java
@@ -87,6 +87,7 @@ public class RouteOnDemandReloadStrategy extends 
RouteWatcherReloadStrategy {
 
     protected void doOnReload(Object source) throws Exception {
         List<Resource> properties = new ArrayList<>();
+        List<Resource> groovy = new ArrayList<>();
         List<Resource> routes = new ArrayList<>();
 
         File dir = new File(getFolder());
@@ -95,14 +96,16 @@ public class RouteOnDemandReloadStrategy extends 
RouteWatcherReloadStrategy {
             String ext = FileUtil.onlyExt(path.getFileName().toString());
             if ("properties".equals(ext)) {
                 properties.add(res);
+            } else if ("groovy".equals(ext)) {
+                groovy.add(res);
             } else {
                 routes.add(res);
             }
         }
 
         if (LOG.isDebugEnabled()) {
-            LOG.debug("On-demand reload scanned {} files (properties: {}, 
routes: {})",
-                    properties.size() + routes.size(), properties.size(), 
routes.size());
+            LOG.debug("On-demand reload scanned {} files (properties: {}, 
routes: {}, groovy: {})",
+                    properties.size() + routes.size(), properties.size(), 
routes.size(), groovy.size());
         }
 
         // reload properties first
@@ -110,6 +113,9 @@ public class RouteOnDemandReloadStrategy extends 
RouteWatcherReloadStrategy {
         for (Resource res : properties) {
             reloaded |= onPropertiesReload(res, false);
         }
+        for (Resource res : groovy) {
+            reloaded |= onGroovyReload(res, false);
+        }
         boolean removeEverything = routes.isEmpty();
         if (reloaded || !routes.isEmpty()) {
             // trigger routes to also reload if properties was reloaded
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/RouteWatcherReloadStrategy.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/RouteWatcherReloadStrategy.java
index db9ddabaf0e..7463f650668 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/support/RouteWatcherReloadStrategy.java
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/RouteWatcherReloadStrategy.java
@@ -230,15 +230,18 @@ public class RouteWatcherReloadStrategy extends 
FileWatcherResourceReloadStrateg
         return null;
     }
 
-    protected void onGroovyReload(Resource resource, boolean reloadRoutes) 
throws Exception {
+    protected boolean onGroovyReload(Resource resource, boolean reloadRoutes) 
throws Exception {
         GroovyScriptCompiler compiler
                 = 
getCamelContext().getCamelContextExtension().getContextPlugin(GroovyScriptCompiler.class);
         if (compiler != null) {
+            compiler.recompile(resource);
             // trigger all routes to be reloaded (which will also trigger 
reloading this resource)
             if (reloadRoutes) {
                 onRouteReload(null, false);
             }
+            return true;
         }
+        return false;
     }
 
     @SuppressWarnings("unchecked")
diff --git 
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
 
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
index 625d0f27d8c..a4ae07a9424 100644
--- 
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
+++ 
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
@@ -1065,6 +1065,13 @@ public class LocalCliConnector extends ServiceSupport 
implements CliConnector, C
                         root.put("internal-tasks", json);
                     }
                 }
+                DevConsole dc25 = dcr.resolveById("groovy");
+                if (dc25 != null) {
+                    JsonObject json = (JsonObject) 
dc25.call(DevConsole.MediaType.JSON);
+                    if (json != null && !json.isEmpty()) {
+                        root.put("groovy", json);
+                    }
+                }
             }
             // various details
             JsonObject mem = collectMemory();
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
index b702a14ca30..d54672b0971 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
@@ -118,6 +118,7 @@ public class CamelJBangMain implements Callable<Integer> {
                         .addSubcommand("rest", new CommandLine(new 
ListRest(main)))
                         .addSubcommand("platform-http", new CommandLine(new 
ListPlatformHttp(main)))
                         .addSubcommand("kafka", new CommandLine(new 
ListKafka(main)))
+                        .addSubcommand("groovy", new CommandLine(new 
ListGroovy(main)))
                         .addSubcommand("source", new CommandLine(new 
CamelSourceAction(main)))
                         .addSubcommand("route-dump", new CommandLine(new 
CamelRouteDumpAction(main)))
                         .addSubcommand("startup-recorder", new CommandLine(new 
CamelStartupRecorderAction(main)))
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListGroovy.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListGroovy.java
new file mode 100644
index 00000000000..e9fcb7b798f
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListGroovy.java
@@ -0,0 +1,162 @@
+/*
+ * 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.dsl.jbang.core.commands.process;
+
+import com.github.freva.asciitable.AsciiTable;
+import com.github.freva.asciitable.Column;
+import com.github.freva.asciitable.HorizontalAlign;
+import com.github.freva.asciitable.OverflowBehaviour;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.ProcessHelper;
+import org.apache.camel.util.TimeUtils;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@Command(name = "groovy", description = "Groovy Sources used of Camel 
integrations", sortOptions = false, showDefaultValues = true)
+public class ListGroovy extends ProcessWatchCommand {
+
+    @CommandLine.Parameters(description = "Name or pid of running Camel 
integration", arity = "0..1")
+    String name = "*";
+
+    @CommandLine.Option(names = { "--sort" }, completionCandidates = 
CamelProcessorStatus.PidNameCompletionCandidates.class,
+                        description = "Sort by pid or name", defaultValue = 
"pid")
+    String sort;
+
+    public ListGroovy(CamelJBangMain main) {
+        super(main);
+    }
+
+    @Override
+    public Integer doProcessWatchCall() throws Exception {
+        List<Row> rows = new ArrayList<>();
+
+        List<Long> pids = findPids(name);
+        ProcessHandle.allProcesses()
+                .filter(ph -> pids.contains(ph.pid()))
+                .forEach(ph -> {
+                    JsonObject root = loadStatus(ph.pid());
+                    // there must be a status file for the running Camel 
integration
+                    if (root != null) {
+                        Row row = new Row();
+                        JsonObject context = (JsonObject) root.get("context");
+                        if (context == null) {
+                            return;
+                        }
+                        row.name = context.getString("name");
+                        if ("CamelJBang".equals(row.name)) {
+                            row.name = ProcessHelper.extractName(root, ph);
+                        }
+                        row.pid = Long.toString(ph.pid());
+
+                        JsonObject jo = (JsonObject) root.get("groovy");
+                        if (jo != null) {
+                            jo = (JsonObject) jo.get("compiler");
+                        }
+                        if (jo != null) {
+                            row = row.copy();
+                            row.counter = jo.getInteger("compiledCounter");
+                            row.classesSize = jo.getInteger("compiledClasses");
+                            row.time = jo.getLong("compiledTime");
+                            row.last = jo.getLong("lastCompilationTimestamp");
+                            row.compiledClasses.clear();
+                            JsonArray arr = jo.getCollection("classes");
+                            for (int i = 0; arr != null && i < arr.size(); 
i++) {
+                                row.compiledClasses.add(arr.getString(i));
+                            }
+                            rows.add(row);
+                        }
+                    }
+                });
+
+        // sort rows
+        rows.sort(this::sortRow);
+
+        if (!rows.isEmpty()) {
+            printer().println(AsciiTable.getTable(AsciiTable.NO_BORDERS, rows, 
Arrays.asList(
+                    new 
Column().header("PID").headerAlign(HorizontalAlign.CENTER).with(r -> r.pid),
+                    new 
Column().header("NAME").dataAlign(HorizontalAlign.LEFT).maxWidth(30, 
OverflowBehaviour.ELLIPSIS_RIGHT)
+                            .with(r -> r.name),
+                    new 
Column().header("COMPILE").headerAlign(HorizontalAlign.CENTER).with(r -> "" + 
r.counter),
+                    new 
Column().header("TIME").headerAlign(HorizontalAlign.CENTER).with(this::getTime),
+                    new 
Column().header("SINCE").headerAlign(HorizontalAlign.CENTER).with(this::getLast),
+                    new 
Column().header("CLASSES").headerAlign(HorizontalAlign.LEFT).dataAlign(HorizontalAlign.LEFT)
+                            .maxWidth(60, 
OverflowBehaviour.ELLIPSIS_LEFT).with(this::getClasses))));
+        }
+
+        return 0;
+    }
+
+    private String getTime(Row r) {
+        return TimeUtils.printDuration(r.time, true);
+    }
+
+    private String getLast(Row r) {
+        if (r.last > 0) {
+            return TimeUtils.printSince(r.last);
+        }
+        return null;
+    }
+
+    private String getClasses(Row r) {
+        if (r.compiledClasses.isEmpty()) {
+            return null;
+        }
+        return String.join("\n", r.compiledClasses);
+    }
+
+    protected int sortRow(Row o1, Row o2) {
+        String s = sort;
+        int negate = 1;
+        if (s.startsWith("-")) {
+            s = s.substring(1);
+            negate = -1;
+        }
+        switch (s) {
+            case "pid":
+                return Long.compare(Long.parseLong(o1.pid), 
Long.parseLong(o2.pid)) * negate;
+            case "name":
+                return o1.name.compareToIgnoreCase(o2.name) * negate;
+            default:
+                return 0;
+        }
+    }
+
+    private static class Row implements Cloneable {
+        String pid;
+        String name;
+        int counter;
+        int classesSize;
+        long time;
+        long last;
+        List<String> compiledClasses = new ArrayList<>();
+
+        Row copy() {
+            try {
+                return (Row) clone();
+            } catch (CloneNotSupportedException e) {
+                return null;
+            }
+        }
+    }
+
+}

Reply via email to