From 293de9d6db51d3e13057ca4837f6ca3dd6b3be17 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Tue, 14 Jun 2022 21:15:28 +0200 Subject: [PATCH] Allow DSL scripts for script transformation (#2990) * Allow DSL scripts for script transformation Signed-off-by: Jan N. Klug --- .../script/ScriptTransformationService.java | 8 ++- .../ScriptTransformationServiceTest.java | 55 +++++++++++++------ .../internal/engine/DSLScriptEngine.java | 2 +- .../jvmmodel/ScriptJvmModelInferrer.xtend | 5 ++ 4 files changed, 50 insertions(+), 20 deletions(-) diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptTransformationService.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptTransformationService.java index d45776aa6..252ae8abf 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptTransformationService.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptTransformationService.java @@ -51,6 +51,7 @@ import org.slf4j.LoggerFactory; public class ScriptTransformationService implements TransformationService, RegistryChangeListener { public static final String OPENHAB_TRANSFORMATION_SCRIPT = "openhab-transformation-script-"; + public static final String SUPPORTED_CONFIGURATION_TYPE = "script"; private static final Pattern SCRIPT_CONFIG_PATTERN = Pattern .compile("(?.*?):(?.*?)(\\?(?.*?))?"); @@ -100,12 +101,15 @@ public class ScriptTransformationService TransformationConfiguration transformationConfiguration = transformationConfigurationRegistry .get(scriptUid); if (transformationConfiguration != null) { + if (!SUPPORTED_CONFIGURATION_TYPE.equals(transformationConfiguration.getType())) { + throw new TransformationException("Configuration does not have correct type 'script' but '" + + transformationConfiguration.getType() + "'."); + } script = transformationConfiguration.getContent(); } if (script == null) { throw new TransformationException("Could not get script for UID '" + scriptUid + "'."); } - scriptCache.put(scriptUid, script); } @@ -137,7 +141,7 @@ public class ScriptTransformationService ScriptEngine engine = compiledScript != null ? compiledScript.getEngine() : scriptEngineContainer.getScriptEngine(); ScriptContext executionContext = engine.getContext(); - executionContext.setAttribute("inputString", source, ScriptContext.ENGINE_SCOPE); + executionContext.setAttribute("input", source, ScriptContext.ENGINE_SCOPE); String params = configMatcher.group("params"); if (params != null) { diff --git a/bundles/org.openhab.core.automation.module.script/src/test/java/org/openhab/core/automation/module/script/ScriptTransformationServiceTest.java b/bundles/org.openhab.core.automation.module.script/src/test/java/org/openhab/core/automation/module/script/ScriptTransformationServiceTest.java index b907434f0..84c987ece 100644 --- a/bundles/org.openhab.core.automation.module.script/src/test/java/org/openhab/core/automation/module/script/ScriptTransformationServiceTest.java +++ b/bundles/org.openhab.core.automation.module.script/src/test/java/org/openhab/core/automation/module/script/ScriptTransformationServiceTest.java @@ -46,13 +46,18 @@ import org.openhab.core.transform.TransformationException; @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) public class ScriptTransformationServiceTest { - private static final String SCRIPT_TYPE = "script"; + private static final String SCRIPT_LANGUAGE = "customDsl"; private static final String SCRIPT_UID = "scriptUid"; + private static final String INVALID_SCRIPT_UID = "invalidScriptUid"; + private static final String SCRIPT = "script"; private static final String SCRIPT_OUTPUT = "output"; private static final TransformationConfiguration TRANSFORMATION_CONFIGURATION = new TransformationConfiguration( - SCRIPT_UID, "label", "script", null, SCRIPT); + SCRIPT_UID, "label", ScriptTransformationService.SUPPORTED_CONFIGURATION_TYPE, null, SCRIPT); + private static final TransformationConfiguration INVALID_TRANSFORMATION_CONFIGURATION = new TransformationConfiguration( + INVALID_SCRIPT_UID, "label", "invalid", null, SCRIPT); + private @Mock @NonNullByDefault({}) TransformationConfigurationRegistry transformationConfigurationRegistry; private @Mock @NonNullByDefault({}) ScriptEngineManager scriptEngineManager; private @Mock @NonNullByDefault({}) ScriptEngineContainer scriptEngineContainer; @@ -65,29 +70,37 @@ public class ScriptTransformationServiceTest { public void setUp() throws ScriptException { service = new ScriptTransformationService(transformationConfigurationRegistry, scriptEngineManager); - when(scriptEngineManager.createScriptEngine(eq(SCRIPT_TYPE), any())).thenReturn(scriptEngineContainer); + when(scriptEngineManager.createScriptEngine(eq(SCRIPT_LANGUAGE), any())).thenReturn(scriptEngineContainer); when(scriptEngineManager.isSupported(anyString())) - .thenAnswer(scriptType -> SCRIPT_TYPE.equals(scriptType.getArgument(0))); + .thenAnswer(arguments -> SCRIPT_LANGUAGE.equals(arguments.getArgument(0))); when(scriptEngineContainer.getScriptEngine()).thenReturn(scriptEngine); when(scriptEngine.eval(SCRIPT)).thenReturn("output"); when(scriptEngine.getContext()).thenReturn(scriptContext); - when(transformationConfigurationRegistry.get(anyString())).thenAnswer( - scriptUid -> SCRIPT_UID.equals(scriptUid.getArgument(0)) ? TRANSFORMATION_CONFIGURATION : null); + when(transformationConfigurationRegistry.get(anyString())).thenAnswer(arguments -> { + String scriptUid = arguments.getArgument(0); + if (SCRIPT_UID.equals(scriptUid)) { + return TRANSFORMATION_CONFIGURATION; + } else if (INVALID_SCRIPT_UID.equals(scriptUid)) { + return INVALID_TRANSFORMATION_CONFIGURATION; + } else { + return null; + } + }); } @Test public void success() throws TransformationException { - String returnValue = Objects.requireNonNull(service.transform(SCRIPT_TYPE + ":" + SCRIPT_UID, "input")); + String returnValue = Objects.requireNonNull(service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID, "input")); assertThat(returnValue, is(SCRIPT_OUTPUT)); } @Test public void scriptExecutionParametersAreInjectedIntoEngineContext() throws TransformationException { - service.transform(SCRIPT_TYPE + ":" + SCRIPT_UID + "?param1=value1¶m2=value2", "input"); + service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID + "?param1=value1¶m2=value2", "input"); - verify(scriptContext).setAttribute(eq("inputString"), eq("input"), eq(ScriptContext.ENGINE_SCOPE)); + verify(scriptContext).setAttribute(eq("input"), eq("input"), eq(ScriptContext.ENGINE_SCOPE)); verify(scriptContext).setAttribute(eq("param1"), eq("value1"), eq(ScriptContext.ENGINE_SCOPE)); verify(scriptContext).setAttribute(eq("param2"), eq("value2"), eq(ScriptContext.ENGINE_SCOPE)); verifyNoMoreInteractions(scriptContext); @@ -95,26 +108,26 @@ public class ScriptTransformationServiceTest { @Test public void invalidScriptExecutionParametersAreDiscarded() throws TransformationException { - service.transform(SCRIPT_TYPE + ":" + SCRIPT_UID + "?param1=value1&invalid", "input"); + service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID + "?param1=value1&invalid", "input"); - verify(scriptContext).setAttribute(eq("inputString"), eq("input"), eq(ScriptContext.ENGINE_SCOPE)); + verify(scriptContext).setAttribute(eq("input"), eq("input"), eq(ScriptContext.ENGINE_SCOPE)); verify(scriptContext).setAttribute(eq("param1"), eq("value1"), eq(ScriptContext.ENGINE_SCOPE)); verifyNoMoreInteractions(scriptContext); } @Test public void scriptsAreCached() throws TransformationException { - service.transform(SCRIPT_TYPE + ":" + SCRIPT_UID, "input"); - service.transform(SCRIPT_TYPE + ":" + SCRIPT_UID, "input"); + service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID, "input"); + service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID, "input"); verify(transformationConfigurationRegistry).get(SCRIPT_UID); } @Test public void scriptCacheInvalidatedAfterChange() throws TransformationException { - service.transform(SCRIPT_TYPE + ":" + SCRIPT_UID, "input"); + service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID, "input"); service.updated(TRANSFORMATION_CONFIGURATION, TRANSFORMATION_CONFIGURATION); - service.transform(SCRIPT_TYPE + ":" + SCRIPT_UID, "input"); + service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID, "input"); verify(transformationConfigurationRegistry, times(2)).get(SCRIPT_UID); } @@ -138,7 +151,7 @@ public class ScriptTransformationServiceTest { @Test public void unknownScriptUidThrowsException() { TransformationException e = assertThrows(TransformationException.class, - () -> service.transform(SCRIPT_TYPE + ":" + "foo", "input")); + () -> service.transform(SCRIPT_LANGUAGE + ":" + "foo", "input")); assertThat(e.getMessage(), is("Could not get script for UID 'foo'.")); } @@ -148,10 +161,18 @@ public class ScriptTransformationServiceTest { when(scriptEngine.eval(SCRIPT)).thenThrow(new ScriptException("exception")); TransformationException e = assertThrows(TransformationException.class, - () -> service.transform(SCRIPT_TYPE + ":" + SCRIPT_UID, "input")); + () -> service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID, "input")); assertThat(e.getMessage(), is("Failed to execute script.")); assertThat(e.getCause(), instanceOf(ScriptException.class)); assertThat(e.getCause().getMessage(), is("exception")); } + + @Test + public void invalidConfigurationTypeThrowsTransformationException() { + TransformationException e = assertThrows(TransformationException.class, + () -> service.transform(SCRIPT_LANGUAGE + ":" + INVALID_SCRIPT_UID, "input")); + + assertThat(e.getMessage(), is("Configuration does not have correct type 'script' but 'invalid'.")); + } } diff --git a/bundles/org.openhab.core.model.script.runtime/src/org/openhab/core/model/script/runtime/internal/engine/DSLScriptEngine.java b/bundles/org.openhab.core.model.script.runtime/src/org/openhab/core/model/script/runtime/internal/engine/DSLScriptEngine.java index cc3733fb9..efe05dd9b 100644 --- a/bundles/org.openhab.core.model.script.runtime/src/org/openhab/core/model/script/runtime/internal/engine/DSLScriptEngine.java +++ b/bundles/org.openhab.core.model.script.runtime/src/org/openhab/core/model/script/runtime/internal/engine/DSLScriptEngine.java @@ -62,7 +62,7 @@ public class DSLScriptEngine implements javax.script.ScriptEngine { private static final Map IMPLICIT_VARS = Map.of("command", ScriptJvmModelInferrer.VAR_RECEIVED_COMMAND, "state", ScriptJvmModelInferrer.VAR_NEW_STATE, "newState", ScriptJvmModelInferrer.VAR_NEW_STATE, "oldState", ScriptJvmModelInferrer.VAR_PREVIOUS_STATE, - "triggeringItem", ScriptJvmModelInferrer.VAR_TRIGGERING_ITEM); + "triggeringItem", ScriptJvmModelInferrer.VAR_TRIGGERING_ITEM, "input", ScriptJvmModelInferrer.VAR_INPUT); private final Logger logger = LoggerFactory.getLogger(DSLScriptEngine.class); diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/jvmmodel/ScriptJvmModelInferrer.xtend b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/jvmmodel/ScriptJvmModelInferrer.xtend index 0ace83e3c..b5034c0cb 100644 --- a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/jvmmodel/ScriptJvmModelInferrer.xtend +++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/jvmmodel/ScriptJvmModelInferrer.xtend @@ -40,6 +40,9 @@ class ScriptJvmModelInferrer extends AbstractModelInferrer { static private final Logger logger = LoggerFactory.getLogger(ScriptJvmModelInferrer) + /** Variable name for the input string in a "script transformation" or "script profile" */ + public static final String VAR_INPUT = "input"; + /** Variable name for the item in a "state triggered" or "command triggered" rule */ public static final String VAR_TRIGGERING_ITEM = "triggeringItem"; @@ -121,6 +124,8 @@ class ScriptJvmModelInferrer extends AbstractModelInferrer { members += script.toMethod("_script", null) [ static = true + val inputTypeRef = script.newTypeRef(String) + parameters += script.toParameter(VAR_INPUT, inputTypeRef) val itemTypeRef = script.newTypeRef(Item) parameters += script.toParameter(VAR_TRIGGERING_ITEM, itemTypeRef) val itemNameRef = script.newTypeRef(String)