From 7de3009ecf09d5a3467ac341300056065e1aa85e Mon Sep 17 00:00:00 2001
From: Raphael Schweikert
Date: Thu, 31 Oct 2024 12:28:49 +0100
Subject: [PATCH 1/3] feat(runner): make ScriptEngineManager instance
configurable on runner
---
README.md | 1 +
.../java/com/swisscom/aem/tools/impl/HopContextImpl.java | 6 ++++++
.../java/com/swisscom/aem/tools/impl/RunnerImpl.java | 4 ++++
.../java/com/swisscom/aem/tools/impl/hops/RunScript.java | 2 +-
.../com/swisscom/aem/tools/jcrhopper/RunnerBuilder.java | 9 +++++++++
.../swisscom/aem/tools/jcrhopper/context/HopContext.java | 6 ++++++
6 files changed, 27 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index dfbdc4c..d4ab5c0 100644
--- a/README.md
+++ b/README.md
@@ -42,6 +42,7 @@ The builder offers various methods to configure the runner:
- `#registerFile(String, Function)`, `#registerFiles(Map>)`: Register file creators that allow scripts to aggregate information into output files.
- `#addDefaultUtils(boolean)`: if set to `true`, The script will know about the `arrays`, `stream`, `class`, and `collections` utility namespaces.
- `#runHandler(com.swisscom.aem.tools.jcrhopper.config.RunHandler)`: Set a listener that is informed about script output and events. The default run handler does nothing (but log messages are logged using Slf4j in any case).
+- `#scriptEngineManager(javax.script.ScriptEngineManager)`: Set the script engine manager to use when searching for non-JEXL scripting engines.
Once the builder is configured, it can be turned into a runner with either `#build(Script)` or `#build(String)` (for JSON scripts).
diff --git a/src/main/java/com/swisscom/aem/tools/impl/HopContextImpl.java b/src/main/java/com/swisscom/aem/tools/impl/HopContextImpl.java
index 0f6705b..1f2073d 100644
--- a/src/main/java/com/swisscom/aem/tools/impl/HopContextImpl.java
+++ b/src/main/java/com/swisscom/aem/tools/impl/HopContextImpl.java
@@ -11,6 +11,7 @@
import java.util.Map;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
+import javax.script.ScriptEngineManager;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -123,6 +124,11 @@ public Map getVariables() {
return Collections.unmodifiableMap(variables);
}
+ @Override
+ public ScriptEngineManager getScriptEngineManager() {
+ return runner.getScriptEngineManager();
+ }
+
// region Logging functions
@Override
public String getName() {
diff --git a/src/main/java/com/swisscom/aem/tools/impl/RunnerImpl.java b/src/main/java/com/swisscom/aem/tools/impl/RunnerImpl.java
index 79316c7..0b3cca0 100644
--- a/src/main/java/com/swisscom/aem/tools/impl/RunnerImpl.java
+++ b/src/main/java/com/swisscom/aem/tools/impl/RunnerImpl.java
@@ -25,6 +25,7 @@
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
+import javax.script.ScriptEngineManager;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.apache.commons.jexl3.JexlBuilder;
@@ -43,6 +44,9 @@ public class RunnerImpl implements Runner {
private final Map variables;
private final Map> fileTypeSuppliers;
+ @Getter
+ private final ScriptEngineManager scriptEngineManager;
+
@Getter
private final RunHandler runHandler;
diff --git a/src/main/java/com/swisscom/aem/tools/impl/hops/RunScript.java b/src/main/java/com/swisscom/aem/tools/impl/hops/RunScript.java
index fc168fb..481a11d 100644
--- a/src/main/java/com/swisscom/aem/tools/impl/hops/RunScript.java
+++ b/src/main/java/com/swisscom/aem/tools/impl/hops/RunScript.java
@@ -73,7 +73,7 @@ private static void runJexl(Config config, HopContext context, Map params, String extension, Writer writer)
throws HopperException {
- final ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
+ final ScriptEngineManager scriptEngineManager = context.getScriptEngineManager();
final ScriptEngine engine = scriptEngineManager.getEngineByExtension(extension);
if (engine == null) {
throw new IllegalArgumentException("Script type " + extension + " not valid");
diff --git a/src/main/java/com/swisscom/aem/tools/jcrhopper/RunnerBuilder.java b/src/main/java/com/swisscom/aem/tools/jcrhopper/RunnerBuilder.java
index 8cca803..a76a2ec 100644
--- a/src/main/java/com/swisscom/aem/tools/jcrhopper/RunnerBuilder.java
+++ b/src/main/java/com/swisscom/aem/tools/jcrhopper/RunnerBuilder.java
@@ -28,6 +28,7 @@
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
+import javax.script.ScriptEngineManager;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
@@ -43,6 +44,13 @@ public class RunnerBuilder {
private final Map variables = new HashMap<>();
private final Map> fileTypeSuppliers = new HashMap<>();
+ /**
+ * The script engine manager for running script types other than JEXL.
+ */
+ @Setter
+ @Getter
+ private ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
+
private final Gson gson;
/**
@@ -237,6 +245,7 @@ public Runner build(Script script) {
utils,
new HashMap<>(variables),
new HashMap<>(fileTypeSuppliers),
+ scriptEngineManager,
runHandler,
script
);
diff --git a/src/main/java/com/swisscom/aem/tools/jcrhopper/context/HopContext.java b/src/main/java/com/swisscom/aem/tools/jcrhopper/context/HopContext.java
index 3f84fa7..3117497 100644
--- a/src/main/java/com/swisscom/aem/tools/jcrhopper/context/HopContext.java
+++ b/src/main/java/com/swisscom/aem/tools/jcrhopper/context/HopContext.java
@@ -6,6 +6,7 @@
import java.util.Map;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
+import javax.script.ScriptEngineManager;
import org.slf4j.Logger;
public interface HopContext extends Logger {
@@ -102,4 +103,9 @@ public interface HopContext extends Logger {
* @return a helper for JCR property manipulation
*/
JcrFunctions getJcrFunctions();
+
+ /**
+ * @return the script engine manager for running various script types
+ */
+ ScriptEngineManager getScriptEngineManager();
}
From 38c42dcde6752a1b914a6b7888b44e97370dc2c0 Mon Sep 17 00:00:00 2001
From: Raphael Schweikert
Date: Thu, 31 Oct 2024 12:37:01 +0100
Subject: [PATCH 2/3] feat(runner): use script engine from OSGi if available
---
.../ScriptEngineProviderExtension.java | 20 +++++++++++++++++++
1 file changed, 20 insertions(+)
create mode 100644 src/main/java/com/swisscom/aem/tools/impl/extension/ScriptEngineProviderExtension.java
diff --git a/src/main/java/com/swisscom/aem/tools/impl/extension/ScriptEngineProviderExtension.java b/src/main/java/com/swisscom/aem/tools/impl/extension/ScriptEngineProviderExtension.java
new file mode 100644
index 0000000..41cf190
--- /dev/null
+++ b/src/main/java/com/swisscom/aem/tools/impl/extension/ScriptEngineProviderExtension.java
@@ -0,0 +1,20 @@
+package com.swisscom.aem.tools.impl.extension;
+
+import com.swisscom.aem.tools.jcrhopper.RunnerBuilder;
+import com.swisscom.aem.tools.jcrhopper.osgi.RunnerBuilderExtension;
+import javax.script.ScriptEngineManager;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+
+@Component(service = RunnerBuilderExtension.class)
+public class ScriptEngineProviderExtension implements RunnerBuilderExtension {
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ private ScriptEngineManager scriptEngineManager;
+
+ @Override
+ public void configure(RunnerBuilder builder) {
+ builder.scriptEngineManager(scriptEngineManager);
+ }
+}
From 7c427196c8c1c9985a0e14e3e9355eedab07b32c Mon Sep 17 00:00:00 2001
From: Raphael Schweikert
Date: Thu, 31 Oct 2024 13:29:53 +0100
Subject: [PATCH 3/3] feat(editor): pass all possible scripting languages to
the editor
---
mock/script-builder.html | 1 +
.../script-builder/build-area/build-area.html | 7 +++++-
src/main/frontend/App.tsx | 11 +++++---
src/main/frontend/editor.tsx | 4 +--
src/main/frontend/model/hops/runScript.ts | 6 ++---
src/main/frontend/sections/RunControls.tsx | 13 +++++++---
src/main/frontend/sections/Runner.tsx | 6 ++---
.../sections/editor/types/RunScriptStep.tsx | 25 +++++++++++--------
src/main/frontend/widgets/CodeEditor.tsx | 4 ++-
.../tools/jcrhopper/impl/HopRunnerInfo.java | 23 +++++++++++++++++
10 files changed, 72 insertions(+), 28 deletions(-)
diff --git a/mock/script-builder.html b/mock/script-builder.html
index 88d704a..d2f45b5 100644
--- a/mock/script-builder.html
+++ b/mock/script-builder.html
@@ -42,6 +42,7 @@
diff --git a/src/main/content/jcr_root/apps/jcr-hopper/script-builder/build-area/build-area.html b/src/main/content/jcr_root/apps/jcr-hopper/script-builder/build-area/build-area.html
index 1bba759..7dc5731 100644
--- a/src/main/content/jcr_root/apps/jcr-hopper/script-builder/build-area/build-area.html
+++ b/src/main/content/jcr_root/apps/jcr-hopper/script-builder/build-area/build-area.html
@@ -1 +1,6 @@
-
+
diff --git a/src/main/frontend/App.tsx b/src/main/frontend/App.tsx
index 8ec40fc..a0991b9 100644
--- a/src/main/frontend/App.tsx
+++ b/src/main/frontend/App.tsx
@@ -9,7 +9,10 @@ import { HistoryUpdater, useHistoryImmutable } from './hooks/useHistoryImmutable
import { useOnce } from './hooks/useOnce';
import { ScriptEditor } from './sections/ScriptEditor';
-export const RunEndpointContext = createContext('');
+export const EnvironmentContext = createContext({
+ runEndpoint: '/',
+ validScriptingLanguages: {} as Record,
+});
export const ScriptContext = createContext>(null!);
const RootElement = styled('div')`
@@ -78,7 +81,7 @@ const RootElement = styled('div')`
}
`;
-export const App: FC<{ runEndpoint: string }> = props => {
+export const App: FC<{ runEndpoint: string; validScriptingLanguages: Record }> = props => {
const initialScript = useOnce(getInitialScript);
const scriptContext = useHistoryImmutable(initialScript, current => {
@@ -86,7 +89,7 @@ export const App: FC<{ runEndpoint: string }> = props => {
});
return (
-
+
@@ -94,6 +97,6 @@ export const App: FC<{ runEndpoint: string }> = props => {
-
+
);
};
diff --git a/src/main/frontend/editor.tsx b/src/main/frontend/editor.tsx
index 2fb82ae..862ec5b 100644
--- a/src/main/frontend/editor.tsx
+++ b/src/main/frontend/editor.tsx
@@ -11,11 +11,11 @@ patchCoralUiCreateElement();
function init() {
const target = document.querySelector('.jcr-hopper-builder')!;
- const { runEndpoint } = target.dataset;
+ const { runEndpoint, validScriptingLanguages } = target.dataset;
const root = createRoot(target.parentElement!.parentElement!);
root.render(
-
+
,
);
}
diff --git a/src/main/frontend/model/hops/runScript.ts b/src/main/frontend/model/hops/runScript.ts
index d97e51c..ef2297f 100644
--- a/src/main/frontend/model/hops/runScript.ts
+++ b/src/main/frontend/model/hops/runScript.ts
@@ -1,6 +1,6 @@
import { AnyHop } from '.';
-export const SCRIPT_LANGUAGES = {
+export const SCRIPT_LANGUAGES: Record = {
jexl: 'JEXL',
js: 'JavaScript',
};
@@ -8,7 +8,7 @@ export const SCRIPT_LANGUAGES = {
export interface Type extends AnyHop {
type: 'runScript';
code: string;
- extension: keyof typeof SCRIPT_LANGUAGES;
+ extension: string;
putLocalsBackIntoScope?: boolean;
}
@@ -22,7 +22,7 @@ export const title = 'Run a Script';
export function shortDescription(config: Type) {
const lang = config.extension ?? 'js';
- const name = SCRIPT_LANGUAGES[lang] ?? lang.toUpperCase();
+ const name = lang in SCRIPT_LANGUAGES ? SCRIPT_LANGUAGES[lang] : lang.toUpperCase();
const lines = config.code?.split(/\n/).filter(Boolean).length;
if (!lines) {
diff --git a/src/main/frontend/sections/RunControls.tsx b/src/main/frontend/sections/RunControls.tsx
index 8c920c3..5065836 100644
--- a/src/main/frontend/sections/RunControls.tsx
+++ b/src/main/frontend/sections/RunControls.tsx
@@ -1,7 +1,7 @@
import React, { FC, FormEvent, useContext, useRef } from 'react';
import { styled } from 'goober';
-import { RunEndpointContext, ScriptContext } from '../App';
+import { EnvironmentContext, ScriptContext } from '../App';
const Elm = styled('form', React.forwardRef)`
grid-auto-flow: row;
@@ -13,7 +13,7 @@ const Elm = styled('form', React.forwardRef)`
export const RunControls: FC<{ runWith: (data: FormData) => Promise }> = ({ runWith }) => {
const { current: script } = useContext(ScriptContext);
- const endpoint = useContext(RunEndpointContext);
+ const environmentContext = useContext(EnvironmentContext);
const formRef = useRef(null);
@@ -31,7 +31,14 @@ export const RunControls: FC<{ runWith: (data: FormData) => Promise }> = (
}
return (
-
+
{script.parameters.length ? (
diff --git a/src/main/frontend/widgets/CodeEditor.tsx b/src/main/frontend/widgets/CodeEditor.tsx
index 9a28f42..2c67631 100644
--- a/src/main/frontend/widgets/CodeEditor.tsx
+++ b/src/main/frontend/widgets/CodeEditor.tsx
@@ -5,10 +5,12 @@ import { useDebounce } from '@uidotdev/usehooks';
import { Editor } from '@monaco-editor/react';
import type * as monaco from 'monaco-editor';
+export type EditorLanguage = 'json' | 'groovy' | 'jexl' | 'sql' | 'js';
+
export const CodeEditor: React.FC<{
value: string;
onChange(val: string, hasErrors: boolean): void;
- language: 'json' | 'groovy' | 'jexl' | 'sql' | 'js';
+ language: EditorLanguage;
lines?: number;
}> = ({ value: outsideValue, onChange, language, lines = 13 }) => {
const editorRef = useRef(null);
diff --git a/src/main/java/com/swisscom/aem/tools/jcrhopper/impl/HopRunnerInfo.java b/src/main/java/com/swisscom/aem/tools/jcrhopper/impl/HopRunnerInfo.java
index 518fadb..346f963 100644
--- a/src/main/java/com/swisscom/aem/tools/jcrhopper/impl/HopRunnerInfo.java
+++ b/src/main/java/com/swisscom/aem/tools/jcrhopper/impl/HopRunnerInfo.java
@@ -1,7 +1,12 @@
package com.swisscom.aem.tools.jcrhopper.impl;
+import com.google.gson.Gson;
import com.swisscom.aem.tools.jcrhopper.osgi.ConfigInfo;
+import java.util.Map;
+import java.util.stream.Collectors;
import javax.inject.Inject;
+import javax.script.ScriptEngineFactory;
+import javax.script.ScriptEngineManager;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.OSGiService;
@@ -9,6 +14,10 @@
@Model(adaptables = Resource.class)
public class HopRunnerInfo {
+ @Inject
+ @OSGiService
+ private ScriptEngineManager scriptEngineManager;
+
@Inject
@OSGiService
private ConfigInfo info;
@@ -16,4 +25,18 @@ public class HopRunnerInfo {
public String getEndpoint() {
return info.getRunnerServletPath();
}
+
+ /**
+ * @return a map of script extensions to names that can be used with the runScript hop type
+ */
+ public String getValidScriptingLanguages() {
+ final Map extensions = scriptEngineManager
+ .getEngineFactories()
+ .stream()
+ .collect(Collectors.toMap(fac -> fac.getExtensions().get(0), ScriptEngineFactory::getLanguageName));
+
+ extensions.put("jexl", "JEXL"); // JEXL is always supported
+
+ return new Gson().toJson(extensions);
+ }
}