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;
+ }
+ }
+ }
+
+}