From 2e5251346faf228e1f42638e5050f152fa57d90d Mon Sep 17 00:00:00 2001 From: Scott Rushworth Date: Thu, 7 Mar 2019 05:52:37 -0500 Subject: [PATCH] Added ScriptModuleTypeProvider Added ScriptModuleTypeProvider, which dynamically adds available script languages to the the ParameterOptions. Signed-off-by: Scott Rushworth --- .../module/script/ScriptEngineFactory.java | 41 +++-- .../module/script/ScriptEngineManager.java | 39 ++--- .../internal/AbstractScriptEngineFactory.java | 77 +++++++++ .../internal/GenericScriptEngineFactory.java | 72 +------- .../internal/NashornScriptEngineFactory.java | 76 ++++----- .../internal/ScriptEngineManagerImpl.java | 141 ++++++++-------- .../factory/ScriptModuleHandlerFactory.java | 48 +++--- .../internal/handler/ScriptActionHandler.java | 2 +- .../handler/ScriptConditionHandler.java | 4 +- .../provider/ScriptModuleTypeProvider.java | 157 ++++++++++++++++++ .../automation/moduletypes/ScriptTypes.json | 67 -------- 11 files changed, 410 insertions(+), 314 deletions(-) create mode 100755 bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/AbstractScriptEngineFactory.java create mode 100755 bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/provider/ScriptModuleTypeProvider.java delete mode 100644 bundles/org.openhab.core.automation.module.script/src/main/resources/ESH-INF/automation/moduletypes/ScriptTypes.json diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptEngineFactory.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptEngineFactory.java index d982cc54611..a9a534a3cd3 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptEngineFactory.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptEngineFactory.java @@ -16,44 +16,49 @@ import java.util.Map; import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; + +import org.openhab.core.automation.module.script.internal.provider.ScriptModuleTypeProvider; /** - * This class is used by the ScriptManager to load ScriptEngines. - * This is meant as a way to allow other OSGi bundles to provide custom Script-Languages with special needs (like - * Nashorn, Groovy, etc.) + * This class is used by the ScriptEngineManager to load ScriptEngines. This is meant as a way to allow other OSGi + * bundles to provide custom script-languages with special needs, e.g. Nashorn, Jython, Groovy, etc. * * @author Simon Merschjohann - * + * @author Scott Rushworth - added/changed methods and parameters when implementing {@link ScriptModuleTypeProvider} */ public interface ScriptEngineFactory { + final static ScriptEngineManager engineManager = new ScriptEngineManager(); + /** - * @return the list of supported language endings e.g. py, jy + * This method logs details about the implementations of ScriptEngineFactory. + * */ - List getLanguages(); + void logFactoryDetails(); /** - * "scopes" new values into the given ScriptEngine + * This method returns a list of file extensions and MimeTypes that are supported by the ScriptEngine, e.g. py, + * application/python, js, application/javascript, etc. * - * @param scriptEngine - * @param scopeValues + * @return List of supported script types */ - void scopeValues(ScriptEngine scriptEngine, Map scopeValues); + List getScriptTypes(); /** - * created a new ScriptEngine + * This method "scopes" new values into the given ScriptEngine. * - * @param fileExtension - * @return + * @param scriptEngine + * @param scopeValues */ - ScriptEngine createScriptEngine(String fileExtension); + void scopeValues(ScriptEngine scriptEngine, Map scopeValues); /** - * checks if the script is supported. Does not necessarily be equal to getLanguages() + * This method creates a new ScriptEngine based on the supplied file extension or MimeType. * - * @param fileExtension - * @return + * @param scriptType a file extension (script) or MimeType (ScriptAction or ScriptCondition) + * @return ScriptEngine */ - boolean isSupported(String fileExtension); + ScriptEngine createScriptEngine(String scriptType); } diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptEngineManager.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptEngineManager.java index fabeb904b95..2c4c5c14532 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptEngineManager.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptEngineManager.java @@ -16,47 +16,48 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.automation.module.script.internal.provider.ScriptModuleTypeProvider; /** + * The ScriptEngineManager provides the ability to load and unload scripts. * * @author Simon Merschjohann - Initial contribution + * @author Scott Rushworth - changed parameter names when implementing {@link ScriptModuleTypeProvider} */ @NonNullByDefault public interface ScriptEngineManager { /** - * Checks if a given fileExtension is supported + * Creates a new ScriptEngine used to execute scripts, ScriptActions or ScriptConditions * - * @param fileExtension - * @return true if supported + * @param scriptType a file extension (script) or MimeType (ScriptAction or ScriptCondition) + * @param engineIdentifier the unique identifier for the ScriptEngine (script file path or UUID) + * @return ScriptEngineContainer or null */ - boolean isSupported(String fileExtension); + @Nullable + ScriptEngineContainer createScriptEngine(String scriptType, String engineIdentifier); /** - * Creates a new ScriptEngine based on the given fileExtension + * Loads a script and initializes its scope variables * - * @param fileExtension - * @param scriptIdentifier - * @return + * @param engineIdentifier the unique identifier for the ScriptEngine (script file path or UUID) + * @param scriptData the content of the script */ - @Nullable - ScriptEngineContainer createScriptEngine(String fileExtension, String scriptIdentifier); + void loadScript(String engineIdentifier, InputStreamReader scriptData); /** - * Loads a script and initializes its scope variables + * Unloads the ScriptEngine loaded with the engineIdentifier * - * @param fileExtension - * @param scriptIdentifier - * @param scriptData - * @return + * @param engineIdentifier the unique identifier for the ScriptEngine (script file path or UUID) */ - void loadScript(String scriptIdentifier, InputStreamReader scriptData); + void removeEngine(String engineIdentifier); /** - * Unloads the ScriptEngine loaded with the scriptIdentifer + * Checks if the supplied file extension or MimeType is supported by the existing ScriptEngineFactories * - * @param scriptIdentifier + * @param scriptType a file extension (script) or MimeType (ScriptAction or ScriptCondition) + * @return boolean */ - void removeEngine(String scriptIdentifier); + boolean isSupported(String scriptType); } diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/AbstractScriptEngineFactory.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/AbstractScriptEngineFactory.java new file mode 100755 index 00000000000..fd3bc16f405 --- /dev/null +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/AbstractScriptEngineFactory.java @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2010-2019 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.automation.module.script.internal; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import javax.script.ScriptEngine; + +import org.openhab.core.automation.module.script.ScriptEngineFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This is an abstract class for implementing {@link ScriptEngineFactory}s. + * + * @author Scott Rushworth - initial contribution + */ +public abstract class AbstractScriptEngineFactory implements ScriptEngineFactory { + + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Override + public void logFactoryDetails() { + for (javax.script.ScriptEngineFactory f : engineManager.getEngineFactories()) { + logger.info("Scripting support is available for {}", f.getLanguageName()); + logger.debug( + "Scripting support is available for engine {} ({}) supporting {} ({}) with file extensions {}, names {}, and mimetypes {}", + f.getEngineName(), f.getEngineVersion(), f.getLanguageName(), f.getLanguageVersion(), + f.getExtensions(), f.getNames(), f.getMimeTypes()); + } + } + + @Override + public List getScriptTypes() { + List scriptTypes = new ArrayList<>(); + + for (javax.script.ScriptEngineFactory f : engineManager.getEngineFactories()) { + scriptTypes.addAll(f.getExtensions()); + scriptTypes.addAll(f.getMimeTypes()); + } + logger.trace("getScriptTypes(): {}", scriptTypes); + return scriptTypes; + } + + @Override + public void scopeValues(ScriptEngine scriptEngine, Map scopeValues) { + for (Entry entry : scopeValues.entrySet()) { + scriptEngine.put(entry.getKey(), entry.getValue()); + } + } + + @Override + public ScriptEngine createScriptEngine(String scriptType) { + ScriptEngine scriptEngine = engineManager.getEngineByExtension(scriptType); + if (scriptEngine == null) { + scriptEngine = engineManager.getEngineByMimeType(scriptType); + } + if (scriptEngine == null) { + scriptEngine = engineManager.getEngineByName(scriptType); + } + return scriptEngine; + } + +} diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/GenericScriptEngineFactory.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/GenericScriptEngineFactory.java index e76706241b0..72330f01305 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/GenericScriptEngineFactory.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/GenericScriptEngineFactory.java @@ -12,78 +12,16 @@ */ package org.openhab.core.automation.module.script.internal; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import javax.script.ScriptEngine; -import javax.script.ScriptEngineManager; - import org.openhab.core.automation.module.script.ScriptEngineFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.osgi.service.component.annotations.Component; /** + * An implementation of {@link ScriptEngineFactory} for ScriptEngines that do not require customizations. * * @author Simon Merschjohann - Initial contribution + * @author Scott Rushworth - added service and removed default methods provided by ScriptEngineFactory */ -public class GenericScriptEngineFactory implements ScriptEngineFactory { - private ScriptEngineManager engineManager = new ScriptEngineManager(); - private final Logger logger = LoggerFactory.getLogger(getClass()); - - public GenericScriptEngineFactory() { - for (javax.script.ScriptEngineFactory f : engineManager.getEngineFactories()) { - logger.info("Activated scripting support for {}", f.getLanguageName()); - logger.debug( - "Activated scripting support with engine {}({}) for {}({}) with mimetypes {} and file extensions {}", - f.getEngineName(), f.getEngineVersion(), f.getLanguageName(), f.getLanguageVersion(), - f.getMimeTypes(), f.getExtensions()); - } - } - - @Override - public List getLanguages() { - ArrayList languages = new ArrayList<>(); - - for (javax.script.ScriptEngineFactory f : engineManager.getEngineFactories()) { - languages.addAll(f.getExtensions()); - } - - return languages; - } - - @Override - public void scopeValues(ScriptEngine scriptEngine, Map scopeValues) { - for (Entry entry : scopeValues.entrySet()) { - scriptEngine.put(entry.getKey(), entry.getValue()); - } - } - - @Override - public ScriptEngine createScriptEngine(String fileExtension) { - ScriptEngine engine = engineManager.getEngineByExtension(fileExtension); - - if (engine == null) { - engine = engineManager.getEngineByName(fileExtension); - } - - if (engine == null) { - engine = engineManager.getEngineByMimeType(fileExtension); - } - - return engine; - } - - @Override - public boolean isSupported(String fileExtension) { - for (javax.script.ScriptEngineFactory f : engineManager.getEngineFactories()) { - if (f.getExtensions().contains(fileExtension)) { - return true; - } - } - - return false; - } +@Component(service = ScriptEngineFactory.class) +public class GenericScriptEngineFactory extends AbstractScriptEngineFactory { } diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/NashornScriptEngineFactory.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/NashornScriptEngineFactory.java index c5f7c7d2c63..7e048f685ec 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/NashornScriptEngineFactory.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/NashornScriptEngineFactory.java @@ -1,21 +1,8 @@ -<<<<<<< Upstream, based on caa9b71c21600a7541b45415c12f33f7dab2bd47 /** * Copyright (c) 2010-2019 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional -<<<<<<< Upstream, based on origin/master * information. -======= - * information regarding copyright ownership. -======= - -/** - * Copyright (c) 2010-2019 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. ->>>>>>> 6ec1344 Use openHAB license headers (#632) ->>>>>>> 798773e Use openHAB license headers (#632) * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -25,7 +12,7 @@ */ package org.openhab.core.automation.module.script.internal; -import java.util.Arrays; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -33,66 +20,59 @@ import java.util.Set; import javax.script.ScriptEngine; -import javax.script.ScriptEngineManager; import javax.script.ScriptException; import org.openhab.core.automation.module.script.ScriptEngineFactory; import org.osgi.service.component.annotations.Component; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** + * An implementation of {@link ScriptEngineFactory} with customizations for Nashorn ScriptEngines. * * @author Simon Merschjohann - Initial contribution + * @author Scott Rushworth - removed default methods provided by ScriptEngineFactory */ @Component(service = ScriptEngineFactory.class) -public class NashornScriptEngineFactory implements ScriptEngineFactory { - private final Logger logger = LoggerFactory.getLogger(this.getClass()); +public class NashornScriptEngineFactory extends AbstractScriptEngineFactory { - private ScriptEngineManager engineManager = new ScriptEngineManager(); + private static final String SCRIPT_TYPE = "js"; @Override - public List getLanguages() { - return Arrays.asList("js", "javascript", "application/javascript"); + public void logFactoryDetails() { + logger.info("Added customized ScriptEngineFactory for Nashorn"); } @Override - public void scopeValues(ScriptEngine engine, Map scopeValues) { - Set expressions = new HashSet(); + public List getScriptTypes() { + List scriptTypes = new ArrayList<>(); - for (Entry entry : scopeValues.entrySet()) { - engine.put(entry.getKey(), entry.getValue()); + for (javax.script.ScriptEngineFactory f : engineManager.getEngineFactories()) { + List extensions = f.getExtensions(); - if (entry.getValue() instanceof Class) { - expressions.add(String.format("%s = %s.static;", entry.getKey(), entry.getKey())); + if (extensions.contains(SCRIPT_TYPE)) { + scriptTypes.addAll(extensions); + scriptTypes.addAll(f.getMimeTypes()); } } - String scriptToEval = String.join("\n", expressions); - try { - engine.eval(scriptToEval); - } catch (ScriptException e) { - logger.error("ScriptException while importing scope: {}", e.getMessage()); - } + logger.trace("getScriptTypes(): {}", scriptTypes); + return scriptTypes; } @Override - public ScriptEngine createScriptEngine(String fileExtension) { - ScriptEngine engine = engineManager.getEngineByExtension(fileExtension); + public void scopeValues(ScriptEngine scriptEngine, Map scopeValues) { + Set expressions = new HashSet(); - if (engine == null) { - engine = engineManager.getEngineByName(fileExtension); + for (Entry entry : scopeValues.entrySet()) { + scriptEngine.put(entry.getKey(), entry.getValue()); + if (entry.getValue() instanceof Class) { + expressions.add(String.format("%s = % scriptEngineFactories = new HashSet<>(); private HashMap loadedScriptEngineInstances = new HashMap<>(); - private HashMap supportedLanguages = new HashMap<>(); - private GenericScriptEngineFactory genericScriptEngineFactory = new GenericScriptEngineFactory(); - + private HashMap customSupport = new HashMap<>(); + private HashMap genericSupport = new HashMap<>(); private @NonNullByDefault({}) ScriptExtensionManager scriptExtensionManager; @Reference @@ -62,63 +59,73 @@ public void unsetScriptExtensionManager(ScriptExtensionManager scriptExtensionMa } @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) - public void addScriptEngineFactory(ScriptEngineFactory provider) { - this.scriptEngineFactories.add(provider); - - for (String language : provider.getLanguages()) { - this.supportedLanguages.put(language, provider); + public void addScriptEngineFactory(ScriptEngineFactory engineFactory) { + engineFactory.logFactoryDetails(); + for (String scriptType : engineFactory.getScriptTypes()) { + if (isCustomFactory(engineFactory) == true) { + this.customSupport.put(scriptType, engineFactory); + } else { + this.genericSupport.put(scriptType, engineFactory); + } } + logger.trace("Added {}", engineFactory.getClass().getSimpleName()); } - public void removeScriptEngineFactory(ScriptEngineFactory provider) { - this.scriptEngineFactories.remove(provider); - - for (String language : provider.getLanguages()) { - this.supportedLanguages.remove(language, provider); + public void removeScriptEngineFactory(ScriptEngineFactory engineFactory) { + this.scriptEngineFactories.remove(engineFactory); + for (String scriptType : engineFactory.getScriptTypes()) { + if (isCustomFactory(engineFactory) == true) { + this.customSupport.remove(scriptType, engineFactory); + } else { + this.genericSupport.remove(scriptType, engineFactory); + } } + logger.trace("Removed {}", engineFactory.getClass().getSimpleName()); } - @Override - public boolean isSupported(String fileExtension) { - return findEngineFactory(fileExtension) != null; + /** + * This method is used to determine is a given {@link ScriptEngineFactory} is generic or customized. + * + * @param engineFactory {@link ScriptEngineFactory} + * @return boolean + */ + private boolean isCustomFactory(ScriptEngineFactory engineFactory) { + return !(engineFactory instanceof GenericScriptEngineFactory); } @Override - public @Nullable ScriptEngineContainer createScriptEngine(String fileExtension, String scriptIdentifier) { + public @Nullable ScriptEngineContainer createScriptEngine(String scriptType, String engineIdentifier) { ScriptEngineContainer result = null; - ScriptEngineFactory engineProvider = findEngineFactory(fileExtension); - - if (engineProvider == null) { - logger.error("loadScript(): scriptengine for language '{}' could not be found for identifier: {}", - fileExtension, scriptIdentifier); + ScriptEngineFactory engineFactory = findEngineFactory(scriptType); + if (engineFactory == null) { + logger.error("ScriptEngine for language '{}' could not be found for identifier: {}", scriptType, + engineIdentifier); } else { try { - ScriptEngine engine = engineProvider.createScriptEngine(fileExtension); + ScriptEngine engine = engineFactory.createScriptEngine(scriptType); HashMap scriptExManager = new HashMap<>(); - result = new ScriptEngineContainer(engine, engineProvider, scriptIdentifier); + result = new ScriptEngineContainer(engine, engineFactory, engineIdentifier); ScriptExtensionManagerWrapper wrapper = new ScriptExtensionManagerWrapper(scriptExtensionManager, result); scriptExManager.put("scriptExtension", wrapper); scriptExManager.put("se", wrapper); - engineProvider.scopeValues(engine, scriptExManager); - scriptExtensionManager.importDefaultPresets(engineProvider, engine, scriptIdentifier); - - loadedScriptEngineInstances.put(scriptIdentifier, result); + engineFactory.scopeValues(engine, scriptExManager); + scriptExtensionManager.importDefaultPresets(engineFactory, engine, engineIdentifier); + loadedScriptEngineInstances.put(engineIdentifier, result); + logger.debug("Added ScriptEngine for language '{}' with identifier: {}", scriptType, engineIdentifier); } catch (Exception ex) { logger.error("Error while creating ScriptEngine", ex); - removeScriptExtensions(scriptIdentifier); + removeScriptExtensions(engineIdentifier); } } - return result; } @Override - public void loadScript(String scriptIdentifier, InputStreamReader scriptData) { - ScriptEngineContainer container = loadedScriptEngineInstances.get(scriptIdentifier); - + public void loadScript(String engineIdentifier, InputStreamReader scriptData) { + ScriptEngineContainer container = loadedScriptEngineInstances.get(engineIdentifier); if (container == null) { - logger.error("could not load script as no engine is created"); + logger.error("Could not load script, as no ScriptEngine has been created"); } else { ScriptEngine engine = container.getScriptEngine(); try { @@ -127,38 +134,36 @@ public void loadScript(String scriptIdentifier, InputStreamReader scriptData) { if (engine instanceof Invocable) { Invocable inv = (Invocable) engine; try { - inv.invokeFunction("scriptLoaded", scriptIdentifier); + inv.invokeFunction("scriptLoaded", engineIdentifier); } catch (NoSuchMethodException e) { - logger.trace("scriptLoaded() not defined in script: {}", scriptIdentifier); + logger.trace("scriptLoaded() is not defined in the script: {}", engineIdentifier); } } else { - logger.trace("engine does not support Invocable interface"); + logger.trace("ScriptEngine does not support Invocable interface"); } } catch (Exception ex) { - logger.error("Error during evaluation of script '{}': {}", scriptIdentifier, ex.getMessage()); + logger.error("Error during evaluation of script '{}': {}", engineIdentifier, ex.getMessage()); } } } @Override - public void removeEngine(String scriptIdentifier) { - ScriptEngineContainer container = loadedScriptEngineInstances.get(scriptIdentifier); - + public void removeEngine(String engineIdentifier) { + ScriptEngineContainer container = loadedScriptEngineInstances.get(engineIdentifier); if (container != null) { if (container.getScriptEngine() instanceof Invocable) { Invocable inv = (Invocable) container.getScriptEngine(); try { inv.invokeFunction("scriptUnloaded"); } catch (NoSuchMethodException e) { - logger.trace("scriptUnloaded() not defined in script"); - } catch (ScriptException e) { - logger.error("Error while executing script", e); + logger.trace("scriptUnloaded() is not defined in the script"); + } catch (ScriptException ex) { + logger.error("Error while executing script", ex); } } else { - logger.trace("engine does not support Invocable interface"); + logger.trace("ScriptEngine does not support Invocable interface"); } - - removeScriptExtensions(scriptIdentifier); + removeScriptExtensions(engineIdentifier); } } @@ -166,28 +171,32 @@ private void removeScriptExtensions(String pathIdentifier) { try { scriptExtensionManager.dispose(pathIdentifier); } catch (Exception ex) { - logger.error("error removing engine", ex); + logger.error("Error removing ScriptEngine", ex); } } - private @Nullable ScriptEngineFactory findEngineFactory(String fileExtension) { - ScriptEngineFactory engineProvider = supportedLanguages.get(fileExtension); - - if (engineProvider != null) { - return engineProvider; - } - - for (ScriptEngineFactory provider : supportedLanguages.values()) { - if (provider != null && provider.isSupported(fileExtension)) { - return provider; - } + /** + * This method will find and return a {@link ScriptEngineFactory} capable of executing a script of the given type, + * if one exists. + * + * @param scriptType a file extension (script) or MimeType (ScriptAction or ScriptCondition) + * @return {@link ScriptEngineFactory} or null + */ + private @Nullable ScriptEngineFactory findEngineFactory(String scriptType) { + ScriptEngineFactory customFactory = customSupport.get(scriptType); + if (customFactory != null) { + return customFactory; } - - if (genericScriptEngineFactory.isSupported(fileExtension)) { - return genericScriptEngineFactory; + ScriptEngineFactory genericFactory = genericSupport.get(scriptType); + if (genericFactory != null) { + return genericFactory; } - return null; } + @Override + public boolean isSupported(String scriptType) { + return findEngineFactory(scriptType) != null; + } + } diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/factory/ScriptModuleHandlerFactory.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/factory/ScriptModuleHandlerFactory.java index ef2b74c8d3e..69be69daad2 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/factory/ScriptModuleHandlerFactory.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/factory/ScriptModuleHandlerFactory.java @@ -12,7 +12,9 @@ */ package org.openhab.core.automation.module.script.internal.factory; -import java.util.Arrays; +import static java.util.Arrays.asList; +import static java.util.Collections.unmodifiableList; + import java.util.Collection; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -37,7 +39,6 @@ * This HandlerFactory creates ModuleHandlers for scripts. * * @author Kai Kreuzer - * */ @NonNullByDefault @Component(service = ModuleHandlerFactory.class) @@ -45,11 +46,10 @@ public class ScriptModuleHandlerFactory extends BaseModuleHandlerFactory { private final Logger logger = LoggerFactory.getLogger(ScriptModuleHandlerFactory.class); + private static final Collection TYPES = unmodifiableList( + asList(ScriptActionHandler.TYPE_ID, ScriptConditionHandler.TYPE_ID)); private @NonNullByDefault({}) ScriptEngineManager scriptEngineManager; - private static final Collection TYPES = Arrays - .asList(new String[] { ScriptActionHandler.SCRIPT_ACTION_ID, ScriptConditionHandler.SCRIPT_CONDITION }); - @Override @Deactivate protected void deactivate() { @@ -61,6 +61,23 @@ public Collection getTypes() { return TYPES; } + @Override + protected @Nullable ModuleHandler internalCreate(Module module, String ruleUID) { + logger.trace("create {} -> {}", module.getId(), module.getTypeUID()); + String moduleTypeUID = module.getTypeUID(); + if (ScriptConditionHandler.TYPE_ID.equals(moduleTypeUID) && module instanceof Condition) { + ScriptConditionHandler handler = new ScriptConditionHandler((Condition) module, ruleUID, + scriptEngineManager); + return handler; + } else if (ScriptActionHandler.TYPE_ID.equals(moduleTypeUID) && module instanceof Action) { + ScriptActionHandler handler = new ScriptActionHandler((Action) module, ruleUID, scriptEngineManager); + return handler; + } else { + logger.error("The ModuleHandler is not supported: {}", moduleTypeUID); + return null; + } + } + @Reference(policy = ReferencePolicy.DYNAMIC) public void setScriptEngineManager(ScriptEngineManager scriptEngineManager) { this.scriptEngineManager = scriptEngineManager; @@ -70,25 +87,4 @@ public void unsetScriptEngineManager(ScriptEngineManager scriptEngineManager) { this.scriptEngineManager = null; } - @Override - protected @Nullable ModuleHandler internalCreate(Module module, String ruleUID) { - logger.trace("create {} -> {}", module.getId(), module.getTypeUID()); - String moduleTypeUID = module.getTypeUID(); - if (moduleTypeUID != null) { - if (ScriptConditionHandler.SCRIPT_CONDITION.equals(moduleTypeUID) && module instanceof Condition) { - ScriptConditionHandler handler = new ScriptConditionHandler((Condition) module, ruleUID, - scriptEngineManager); - return handler; - } else if (ScriptActionHandler.SCRIPT_ACTION_ID.equals(moduleTypeUID) && module instanceof Action) { - ScriptActionHandler handler = new ScriptActionHandler((Action) module, ruleUID, scriptEngineManager); - return handler; - } else { - logger.error("The ModuleHandler is not supported: {}", moduleTypeUID); - } - } else { - logger.error("ModuleType is not registered: {}", moduleTypeUID); - } - return null; - } - } diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/ScriptActionHandler.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/ScriptActionHandler.java index 5cfd8bac2fc..0422aabe184 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/ScriptActionHandler.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/ScriptActionHandler.java @@ -32,7 +32,7 @@ */ public class ScriptActionHandler extends AbstractScriptModuleHandler implements ActionHandler { - public static final String SCRIPT_ACTION_ID = "script.ScriptAction"; + public static final String TYPE_ID = "script.ScriptAction"; private final Logger logger = LoggerFactory.getLogger(ScriptActionHandler.class); diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/ScriptConditionHandler.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/ScriptConditionHandler.java index 90e662834c0..2c947dd3eca 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/ScriptConditionHandler.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/ScriptConditionHandler.java @@ -33,9 +33,9 @@ */ public class ScriptConditionHandler extends AbstractScriptModuleHandler implements ConditionHandler { - public final Logger logger = LoggerFactory.getLogger(ScriptConditionHandler.class); + public static final String TYPE_ID = "script.ScriptCondition"; - public static final String SCRIPT_CONDITION = "script.ScriptCondition"; + public final Logger logger = LoggerFactory.getLogger(ScriptConditionHandler.class); public ScriptConditionHandler(Condition module, String ruleUID, ScriptEngineManager scriptEngineManager) { super(module, ruleUID, scriptEngineManager); diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/provider/ScriptModuleTypeProvider.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/provider/ScriptModuleTypeProvider.java new file mode 100755 index 00000000000..30c65cdc776 --- /dev/null +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/provider/ScriptModuleTypeProvider.java @@ -0,0 +1,157 @@ +/** + * Copyright (c) 2010-2019 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.automation.module.script.internal.provider; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.smarthome.config.core.ConfigDescriptionParameter; +import org.eclipse.smarthome.config.core.ConfigDescriptionParameter.Type; +import org.eclipse.smarthome.config.core.ConfigDescriptionParameterBuilder; +import org.eclipse.smarthome.config.core.ParameterOption; +import org.eclipse.smarthome.core.common.registry.ProviderChangeListener; +import org.openhab.core.automation.Visibility; +import org.openhab.core.automation.module.script.ScriptEngineFactory; +import org.openhab.core.automation.module.script.internal.handler.AbstractScriptModuleHandler; +import org.openhab.core.automation.module.script.internal.handler.ScriptActionHandler; +import org.openhab.core.automation.module.script.internal.handler.ScriptConditionHandler; +import org.openhab.core.automation.type.ActionType; +import org.openhab.core.automation.type.ConditionType; +import org.openhab.core.automation.type.ModuleType; +import org.openhab.core.automation.type.ModuleTypeProvider; +import org.openhab.core.automation.type.Output; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class dynamically provides ScriptActionType and ScriptConditionType {@link ModuleType}s. This class is necessary + * because there is no other way to provide dynamic {@link ParameterOption}s for {@link ModuleType}s. + * + * @author Scott Rushworth - Initial contribution + */ +@Component(immediate = true) +public class ScriptModuleTypeProvider implements ModuleTypeProvider { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + private final List parameterOptions = new CopyOnWriteArrayList<>(); + + @SuppressWarnings("unchecked") + @Override + public ModuleType getModuleType(String UID, Locale locale) { + if (ScriptActionHandler.TYPE_ID.equals(UID)) { + return getScriptActionType(locale); + } else if (ScriptConditionHandler.TYPE_ID.equals(UID)) { + return getScriptConditionType(locale); + } else { + return null; + } + } + + private ModuleType getScriptActionType(Locale locale) { + if (parameterOptions.isEmpty()) { + return null; + } else { + List outputs = new ArrayList(); + + Output result = new Output("result", "java.lang.Object", "result", "the script result", null, null, null); + outputs.add(result); + return new ActionType(ScriptActionHandler.TYPE_ID, getConfigDescriptions(locale), "execute a given script", + "Allows the execution of a user-defined script.", null, Visibility.VISIBLE, null, outputs); + } + } + + private ModuleType getScriptConditionType(Locale locale) { + if (parameterOptions.isEmpty()) { + return null; + } else { + return new ConditionType(ScriptConditionHandler.TYPE_ID, getConfigDescriptions(locale), + "a given script evaluates to true", "Allows the definition of a condition through a script.", null, + Visibility.VISIBLE, null); + } + } + + /** + * This method creates the {@link ConfigurationDescriptionParameter}s used by the generated ScriptActionType and + * ScriptConditionType. {@link AbstractScriptModuleHandler} requires that the names of these be 'type' and 'script'. + * + * @return a list of {#link ConfigurationDescriptionParameter}s + */ + private List getConfigDescriptions(Locale locale) { + final ConfigDescriptionParameter scriptType = ConfigDescriptionParameterBuilder.create("type", Type.TEXT) + .withRequired(true).withReadOnly(true).withMultiple(false).withLabel("Script Type") + .withDescription("the scripting language used").withOptions(parameterOptions).withLimitToOptions(true) + .build(); + final ConfigDescriptionParameter script = ConfigDescriptionParameterBuilder.create("script", Type.TEXT) + .withRequired(true).withReadOnly(false).withMultiple(false).withLabel("Script").withContext("script") + .withDescription("the script to execute").build(); + return Stream.of(scriptType, script).collect(Collectors.toList()); + } + + @Override + public Collection getModuleTypes(Locale locale) { + return Stream.of(getScriptActionType(locale), getScriptConditionType(locale)).collect(Collectors.toList()); + } + + @Override + public Collection getAll() { + return getModuleTypes(null); + } + + @Override + public void addProviderChangeListener(ProviderChangeListener listener) { + // does nothing because this provider does not change + } + + @Override + public void removeProviderChangeListener(ProviderChangeListener listener) { + // does nothing because this provider does not change + } + + /** + * As {@link ScriptEngineFactory}s are added/changed, this method will create the {@link ParameterOption}s + * that are available when selecting a script type in a ScriptActionType or ScriptConditionType. + */ + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + public void setScriptEngineFactory(ScriptEngineFactory engineFactory) { + parameterOptions.clear(); + for (javax.script.ScriptEngineFactory f : ScriptEngineFactory.engineManager.getEngineFactories()) { + String languageName = String.format("%s (%s)", StringUtils.capitalize(f.getLanguageName()), + f.getLanguageVersion()); + List mimeTypes = new ArrayList<>(); + + mimeTypes.addAll(f.getMimeTypes()); + String preferredMimeType = mimeTypes.get(0); + mimeTypes.removeIf(mimeType -> !mimeType.contains("application") || mimeType.contains("x-")); + if (mimeTypes.size() > 0) { + preferredMimeType = mimeTypes.get(0); + } + parameterOptions.add(new ParameterOption(preferredMimeType, languageName)); + } + logger.trace("ParameterOptions: {}", parameterOptions); + } + + public void unsetScriptEngineFactory(ScriptEngineFactory scriptEngineFactory) { + parameterOptions.clear(); + } + +} diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/ESH-INF/automation/moduletypes/ScriptTypes.json b/bundles/org.openhab.core.automation.module.script/src/main/resources/ESH-INF/automation/moduletypes/ScriptTypes.json deleted file mode 100644 index 09814a89024..00000000000 --- a/bundles/org.openhab.core.automation.module.script/src/main/resources/ESH-INF/automation/moduletypes/ScriptTypes.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "conditions":[ - { - "uid":"script.ScriptCondition", - "label":"a given script evaluates to true", - "description":"Allows the definition of a condition through a script.", - "configDescriptions":[ - { - "name":"type", - "type":"TEXT", - "description":"the scripting language used", - "required":true, - "options":[ - { - "label": "Javascript", - "value": "application/javascript" - } - ] - }, - { - "name":"script", - "type":"TEXT", - "description":"the script to execute", - "required":true, - "context":"script" - } - ] - } - ], - "actions":[ - { - "uid":"script.ScriptAction", - "label":"execute a given script", - "description":"Allows the execution of a user-defined script.", - "configDescriptions":[ - { - "name":"type", - "type":"TEXT", - "description":"the scripting language used", - "required":true, - "options":[ - { - "label": "Javascript", - "value": "application/javascript" - } - ], - "defaultValue":"application/javascript" - }, - { - "name":"script", - "type":"TEXT", - "description":"the script to execute", - "required":true, - "context":"script" - } - ], - "outputs":[ - { - "name":"result", - "type":"java.lang.Object", - "label":"result", - "description":"the script result" - } - ] - } - ] -}