This is an automated email from the ASF dual-hosted git repository.
shuber pushed a commit to branch UNOMI-897-groovy-fixes
in repository https://gitbox.apache.org/repos/asf/unomi.git
The following commit(s) were added to refs/heads/UNOMI-897-groovy-fixes by this
push:
new 3bcf4da51 UNOMI-897 Code cleanup
3bcf4da51 is described below
commit 3bcf4da514a717b307c0c2b56f0cb6ff73c31c10
Author: Serge Huber <[email protected]>
AuthorDate: Sun Jul 13 08:20:13 2025 +0200
UNOMI-897 Code cleanup
---
.../services/impl/GroovyActionsServiceImpl.java | 206 +++++++++++----------
1 file changed, 113 insertions(+), 93 deletions(-)
diff --git
a/extensions/groovy-actions/services/src/main/java/org/apache/unomi/groovy/actions/services/impl/GroovyActionsServiceImpl.java
b/extensions/groovy-actions/services/src/main/java/org/apache/unomi/groovy/actions/services/impl/GroovyActionsServiceImpl.java
index 25a42ffbb..effc2be67 100644
---
a/extensions/groovy-actions/services/src/main/java/org/apache/unomi/groovy/actions/services/impl/GroovyActionsServiceImpl.java
+++
b/extensions/groovy-actions/services/src/main/java/org/apache/unomi/groovy/actions/services/impl/GroovyActionsServiceImpl.java
@@ -74,7 +74,7 @@ public class GroovyActionsServiceImpl implements
GroovyActionsService {
private GroovyScriptEngine groovyScriptEngine;
private CompilerConfiguration compilerConfiguration;
private ScheduledFuture<?> scheduledFuture;
-
+
private final Object compilationLock = new Object();
private GroovyShell compilationShell;
private volatile Map<String, ScriptMetadata> scriptMetadataCache = new
ConcurrentHashMap<>();
@@ -117,16 +117,16 @@ public class GroovyActionsServiceImpl implements
GroovyActionsService {
// Initialize Groovy compiler and compilation shell
initializeGroovyCompiler();
-
+
try {
loadBaseScript();
} catch (IOException e) {
LOGGER.error("Failed to load base script", e);
}
-
+
// PRE-COMPILE ALL SCRIPTS AT STARTUP (no on-demand compilation)
preloadAllScripts();
-
+
initializeTimers();
LOGGER.info("Groovy action service initialized with {} scripts",
scriptMetadataCache.size());
}
@@ -140,7 +140,15 @@ public class GroovyActionsServiceImpl implements
GroovyActionsService {
}
/**
- * Loads the base script that provides utility functions for Groovy
actions.
+ * Load the Base script.
+ * It's a script which provides utility functions that we can use in other
groovy script
+ * The functions added by the base script could be called by the groovy
actions executed in
+ * {@link org.apache.unomi.groovy.actions.GroovyActionDispatcher#execute}
+ * The base script would be added in the configuration of the {@link
GroovyActionsServiceImpl#groovyShell GroovyShell} , so when a
+ * script will be parsed with the GroovyShell (groovyShell.parse(...)),
the action will extends the base script, so the functions
+ * could be called
+ *
+ * @throws IOException
*/
private void loadBaseScript() throws IOException {
URL groovyBaseScriptURL =
bundleContext.getBundle().getEntry("META-INF/base/BaseScript.groovy");
@@ -161,52 +169,51 @@ public class GroovyActionsServiceImpl implements
GroovyActionsService {
compilerConfiguration.addCompilationCustomizers(createImportCustomizer());
compilerConfiguration.setScriptBaseClass(BASE_SCRIPT_NAME);
groovyScriptEngine.setConfig(compilerConfiguration);
-
+
// Create single shared shell for compilation only
this.compilationShell = new
GroovyShell(groovyScriptEngine.getGroovyClassLoader(), compilerConfiguration);
}
-
+
/**
* Pre-compiles all scripts at startup to eliminate runtime compilation
overhead.
*/
private void preloadAllScripts() {
long startTime = System.currentTimeMillis();
LOGGER.info("Pre-compiling all Groovy scripts at startup...");
-
+
int successCount = 0;
int failureCount = 0;
long totalCompilationTime = 0;
-
+
for (GroovyAction groovyAction :
persistenceService.getAllItems(GroovyAction.class)) {
try {
String actionName = groovyAction.getName();
String scriptContent = groovyAction.getScript();
-
+
long scriptStartTime = System.currentTimeMillis();
- Class<? extends Script> scriptClass =
compileScript(actionName, scriptContent);
+ ScriptMetadata metadata = compileAndCreateMetadata(actionName,
scriptContent);
long scriptCompilationTime = System.currentTimeMillis() -
scriptStartTime;
totalCompilationTime += scriptCompilationTime;
-
- ScriptMetadata metadata = new ScriptMetadata(actionName,
scriptContent, scriptClass);
+
scriptMetadataCache.put(actionName, metadata);
-
+
successCount++;
LOGGER.debug("Pre-compiled script: {} ({}ms)", actionName,
scriptCompilationTime);
-
+
} catch (Exception e) {
failureCount++;
LOGGER.error("Failed to pre-compile script: {}",
groovyAction.getName(), e);
}
}
-
+
long totalTime = System.currentTimeMillis() - startTime;
- LOGGER.info("Pre-compilation completed: {} scripts successfully
compiled, {} failures. Total time: {}ms",
+ LOGGER.info("Pre-compilation completed: {} scripts successfully
compiled, {} failures. Total time: {}ms",
successCount, failureCount, totalTime);
- LOGGER.debug("Pre-compilation metrics: Average per script: {}ms,
Compilation overhead: {}ms",
+ LOGGER.debug("Pre-compilation metrics: Average per script: {}ms,
Compilation overhead: {}ms",
successCount > 0 ? totalCompilationTime / successCount : 0,
totalTime - totalCompilationTime);
}
-
+
/**
* Thread-safe script compilation using synchronized shared shell.
*/
@@ -227,48 +234,76 @@ public class GroovyActionsServiceImpl implements
GroovyActionsService {
return importCustomizer;
}
+ /**
+ * Validates that a string parameter is not null or empty.
+ */
+ private void validateNotEmpty(String value, String parameterName) {
+ if (value == null || value.trim().isEmpty()) {
+ throw new IllegalArgumentException(parameterName + " cannot be
null or empty");
+ }
+ }
+
+ /**
+ * Compiles a script and creates metadata with timing information.
+ */
+ private ScriptMetadata compileAndCreateMetadata(String actionName, String
scriptContent) {
+ long compilationStartTime = System.currentTimeMillis();
+ Class<? extends Script> scriptClass = compileScript(actionName,
scriptContent);
+ long compilationTime = System.currentTimeMillis() -
compilationStartTime;
+
+ LOGGER.debug("Script {} compiled in {}ms", actionName,
compilationTime);
+ return new ScriptMetadata(actionName, scriptContent, scriptClass);
+ }
+
+ /**
+ * Extracts Action annotation from script class if present.
+ */
+ private Action getActionAnnotation(Class<? extends Script> scriptClass) {
+ try {
+ return
scriptClass.getMethod("execute").getAnnotation(Action.class);
+ } catch (Exception e) {
+ LOGGER.error("Failed to extract action annotation", e);
+ return null;
+ }
+ }
+
/**
* {@inheritDoc}
* Implementation performs hash-based change detection to skip unnecessary
recompilation.
*/
@Override
public void save(String actionName, String groovyScript) {
- if (actionName == null || actionName.trim().isEmpty()) {
- throw new IllegalArgumentException("Action name cannot be null or
empty");
- }
- if (groovyScript == null || groovyScript.trim().isEmpty()) {
- throw new IllegalArgumentException("Groovy script cannot be null
or empty");
- }
-
+ validateNotEmpty(actionName, "Action name");
+ validateNotEmpty(groovyScript, "Groovy script");
+
long startTime = System.currentTimeMillis();
LOGGER.info("Saving script: {}", actionName);
-
+
try {
ScriptMetadata existingMetadata =
scriptMetadataCache.get(actionName);
if (existingMetadata != null &&
!existingMetadata.hasChanged(groovyScript)) {
- LOGGER.info("Script {} unchanged, skipping recompilation
({}ms)", actionName,
+ LOGGER.info("Script {} unchanged, skipping recompilation
({}ms)", actionName,
System.currentTimeMillis() - startTime);
return;
}
-
+
long compilationStartTime = System.currentTimeMillis();
- Class<? extends Script> scriptClass = compileScript(actionName,
groovyScript);
+ ScriptMetadata metadata = compileAndCreateMetadata(actionName,
groovyScript);
long compilationTime = System.currentTimeMillis() -
compilationStartTime;
-
- Action actionAnnotation =
scriptClass.getMethod("execute").getAnnotation(Action.class);
+
+ Action actionAnnotation =
getActionAnnotation(metadata.getCompiledClass());
if (actionAnnotation != null) {
saveActionType(actionAnnotation);
}
-
+
saveScript(actionName, groovyScript);
-
- ScriptMetadata metadata = new ScriptMetadata(actionName,
groovyScript, scriptClass);
+
scriptMetadataCache.put(actionName, metadata);
-
+
long totalTime = System.currentTimeMillis() - startTime;
- LOGGER.info("Script {} saved and compiled successfully (total:
{}ms, compilation: {}ms)",
+ LOGGER.info("Script {} saved and compiled successfully (total:
{}ms, compilation: {}ms)",
actionName, totalTime, compilationTime);
-
+
} catch (Exception e) {
long totalTime = System.currentTimeMillis() - startTime;
LOGGER.error("Failed to save script: {} ({}ms)", actionName,
totalTime, e);
@@ -298,27 +333,20 @@ public class GroovyActionsServiceImpl implements
GroovyActionsService {
*/
@Override
public void remove(String id) {
- if (id == null || id.trim().isEmpty()) {
- throw new IllegalArgumentException("Script ID cannot be null or
empty");
- }
-
+ validateNotEmpty(id, "Script ID");
+
LOGGER.info("Removing script: {}", id);
-
+
ScriptMetadata removedMetadata = scriptMetadataCache.remove(id);
persistenceService.remove(id, GroovyAction.class);
-
- try {
- if (removedMetadata != null) {
- Class<? extends Script> cachedClass =
removedMetadata.getCompiledClass();
- Action actionAnnotation =
cachedClass.getMethod("execute").getAnnotation(Action.class);
- if (actionAnnotation != null) {
- definitionsService.removeActionType(actionAnnotation.id());
- }
+
+ if (removedMetadata != null) {
+ Action actionAnnotation =
getActionAnnotation(removedMetadata.getCompiledClass());
+ if (actionAnnotation != null) {
+ definitionsService.removeActionType(actionAnnotation.id());
}
- } catch (Exception e) {
- LOGGER.error("Failed to remove action type for script: {}", id, e);
}
-
+
LOGGER.info("Script {} removed successfully", id);
}
@@ -329,53 +357,48 @@ public class GroovyActionsServiceImpl implements
GroovyActionsService {
*/
@Override
public Class<? extends Script> getOrCompileScript(String id) {
- if (id == null || id.trim().isEmpty()) {
- throw new IllegalArgumentException("Script ID cannot be null or
empty");
- }
-
+ validateNotEmpty(id, "Script ID");
+
ScriptMetadata metadata = scriptMetadataCache.get(id);
if (metadata != null) {
return metadata.getCompiledClass();
}
-
+
long startTime = System.currentTimeMillis();
LOGGER.warn("Script {} not found in cache, compiling on-demand
(performance warning)", id);
-
+
GroovyAction groovyAction = persistenceService.load(id,
GroovyAction.class);
if (groovyAction == null) {
- LOGGER.warn("Script {} not found in persistence, returning null
({}ms)", id,
+ LOGGER.warn("Script {} not found in persistence, returning null
({}ms)", id,
System.currentTimeMillis() - startTime);
return null;
}
-
+
try {
long compilationStartTime = System.currentTimeMillis();
- Class<? extends Script> scriptClass = compileScript(id,
groovyAction.getScript());
+ ScriptMetadata newMetadata = compileAndCreateMetadata(id,
groovyAction.getScript());
long compilationTime = System.currentTimeMillis() -
compilationStartTime;
-
- ScriptMetadata newMetadata = new ScriptMetadata(id,
groovyAction.getScript(), scriptClass);
+
scriptMetadataCache.put(id, newMetadata);
-
+
long totalTime = System.currentTimeMillis() - startTime;
- LOGGER.warn("On-demand compilation completed for {} (total: {}ms,
compilation: {}ms)",
+ LOGGER.warn("On-demand compilation completed for {} (total: {}ms,
compilation: {}ms)",
id, totalTime, compilationTime);
- return scriptClass;
+ return newMetadata.getCompiledClass();
} catch (Exception e) {
long totalTime = System.currentTimeMillis() - startTime;
LOGGER.error("Failed to compile script {} on-demand ({}ms)", id,
totalTime, e);
return null;
}
}
-
+
/**
* {@inheritDoc}
*/
@Override
public Class<? extends Script> getCompiledScript(String id) {
- if (id == null || id.trim().isEmpty()) {
- throw new IllegalArgumentException("Script ID cannot be null or
empty");
- }
-
+ validateNotEmpty(id, "Script ID");
+
ScriptMetadata metadata = scriptMetadataCache.get(id);
if (metadata == null) {
LOGGER.warn("Script {} not found in cache", id);
@@ -383,16 +406,14 @@ public class GroovyActionsServiceImpl implements
GroovyActionsService {
}
return metadata.getCompiledClass();
}
-
+
/**
* {@inheritDoc}
*/
@Override
public ScriptMetadata getScriptMetadata(String actionName) {
- if (actionName == null || actionName.trim().isEmpty()) {
- throw new IllegalArgumentException("Action name cannot be null or
empty");
- }
-
+ validateNotEmpty(actionName, "Action name");
+
return scriptMetadataCache.get(actionName);
}
@@ -418,17 +439,17 @@ public class GroovyActionsServiceImpl implements
GroovyActionsService {
*/
private void refreshGroovyActions() {
long startTime = System.currentTimeMillis();
-
+
Map<String, ScriptMetadata> newMetadataCache = new
ConcurrentHashMap<>();
int unchangedCount = 0;
int recompiledCount = 0;
int errorCount = 0;
long totalCompilationTime = 0;
-
+
for (GroovyAction groovyAction :
persistenceService.getAllItems(GroovyAction.class)) {
String actionName = groovyAction.getName();
String scriptContent = groovyAction.getScript();
-
+
try {
ScriptMetadata existingMetadata =
scriptMetadataCache.get(actionName);
if (existingMetadata != null &&
!existingMetadata.hasChanged(scriptContent)) {
@@ -439,26 +460,25 @@ public class GroovyActionsServiceImpl implements
GroovyActionsService {
if (recompiledCount == 0) {
LOGGER.info("Refreshing scripts from persistence
layer...");
}
-
+
long compilationStartTime = System.currentTimeMillis();
- Class<? extends Script> scriptClass =
compileScript(actionName, scriptContent);
+ ScriptMetadata metadata =
compileAndCreateMetadata(actionName, scriptContent);
long compilationTime = System.currentTimeMillis() -
compilationStartTime;
totalCompilationTime += compilationTime;
-
- ScriptMetadata metadata = new ScriptMetadata(actionName,
scriptContent, scriptClass);
+
newMetadataCache.put(actionName, metadata);
recompiledCount++;
LOGGER.info("Script {} recompiled during refresh ({}ms)",
actionName, compilationTime);
}
-
+
} catch (Exception e) {
if (errorCount == 0 && recompiledCount == 0) {
LOGGER.info("Refreshing scripts from persistence
layer...");
}
-
+
errorCount++;
LOGGER.error("Failed to refresh script: {}", actionName, e);
-
+
ScriptMetadata existingMetadata =
scriptMetadataCache.get(actionName);
if (existingMetadata != null) {
newMetadataCache.put(actionName, existingMetadata);
@@ -466,17 +486,17 @@ public class GroovyActionsServiceImpl implements
GroovyActionsService {
}
}
}
-
+
this.scriptMetadataCache = newMetadataCache;
-
+
if (recompiledCount > 0 || errorCount > 0) {
long totalTime = System.currentTimeMillis() - startTime;
- LOGGER.info("Script refresh completed: {} unchanged, {}
recompiled, {} errors. Total time: {}ms",
+ LOGGER.info("Script refresh completed: {} unchanged, {}
recompiled, {} errors. Total time: {}ms",
unchangedCount, recompiledCount, errorCount, totalTime);
- LOGGER.debug("Refresh metrics: Recompilation time: {}ms, Cache
update overhead: {}ms",
+ LOGGER.debug("Refresh metrics: Recompilation time: {}ms, Cache
update overhead: {}ms",
totalCompilationTime, totalTime - totalCompilationTime);
} else {
- LOGGER.debug("Script refresh completed: {} scripts checked, no
changes detected ({}ms)",
+ LOGGER.debug("Script refresh completed: {} scripts checked, no
changes detected ({}ms)",
unchangedCount, System.currentTimeMillis() - startTime);
}
}