Added ScriptModuleTypeProvider (#635)

Added ScriptModuleTypeProvider, which dynamically adds available script
languages to the ParameterOptions used in Paper UI when configuring a
ScriptAction or ScriptCondition.

Signed-off-by: Scott Rushworth <openhab@5iver.com>
This commit is contained in:
Scott Rushworth 2019-04-11 00:57:39 -04:00 committed by Markus Rathgeb
parent 48a97c1ac2
commit 5f880e1062
11 changed files with 444 additions and 331 deletions

View File

@ -16,24 +16,34 @@ import java.util.List;
import java.util.Map;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
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.)
*
* @author Simon Merschjohann
* 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 - Initial contribution
* @author Scott Rushworth - added/changed methods and parameters when implementing {@link ScriptModuleTypeProvider}
*/
@NonNullByDefault
public interface ScriptEngineFactory {
/**
* @return the list of supported language endings e.g. py, jy
*/
List<String> getLanguages();
final static ScriptEngineManager engineManager = new ScriptEngineManager();
/**
* "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.
*
* @return List of supported script types
*/
List<String> getScriptTypes();
/**
* This method "scopes" new values into the given ScriptEngine.
*
* @param scriptEngine
* @param scopeValues
@ -41,19 +51,12 @@ public interface ScriptEngineFactory {
void scopeValues(ScriptEngine scriptEngine, Map<String, Object> scopeValues);
/**
* created a new ScriptEngine
* 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 or null
*/
ScriptEngine createScriptEngine(String fileExtension);
/**
* checks if the script is supported. Does not necessarily be equal to getLanguages()
*
* @param fileExtension
* @return
*/
boolean isSupported(String fileExtension);
@Nullable
ScriptEngine createScriptEngine(String scriptType);
}

View File

@ -16,47 +16,48 @@ import java.io.InputStreamReader;
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
*/
boolean isSupported(String fileExtension);
/**
* Creates a new ScriptEngine based on the given fileExtension
*
* @param fileExtension
* @param scriptIdentifier
* @return
* @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
*/
@Nullable
ScriptEngineContainer createScriptEngine(String fileExtension, String scriptIdentifier);
ScriptEngineContainer createScriptEngine(String scriptType, String engineIdentifier);
/**
* Loads a script and initializes its scope variables
*
* @param fileExtension
* @param scriptIdentifier
* @param scriptData
* @return
* @param engineIdentifier the unique identifier for the ScriptEngine (script file path or UUID)
* @param scriptData the content of the script
*/
void loadScript(String scriptIdentifier, InputStreamReader scriptData);
void loadScript(String engineIdentifier, InputStreamReader scriptData);
/**
* Unloads the ScriptEngine loaded with the scriptIdentifer
* Unloads the ScriptEngine loaded with the engineIdentifier
*
* @param scriptIdentifier
* @param engineIdentifier the unique identifier for the ScriptEngine (script file path or UUID)
*/
void removeEngine(String scriptIdentifier);
void removeEngine(String engineIdentifier);
/**
* Checks if the supplied file extension or MimeType is supported by the existing ScriptEngineFactories
*
* @param scriptType a file extension (script) or MimeType (ScriptAction or ScriptCondition)
* @return true, if supported, else false
*/
boolean isSupported(String scriptType);
}

View File

@ -0,0 +1,69 @@
/**
* 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.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.script.ScriptEngine;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
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
*/
@NonNullByDefault
public abstract class AbstractScriptEngineFactory implements ScriptEngineFactory {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public List<String> getScriptTypes() {
List<String> scriptTypes = new ArrayList<>();
for (javax.script.ScriptEngineFactory f : engineManager.getEngineFactories()) {
scriptTypes.addAll(f.getExtensions());
scriptTypes.addAll(f.getMimeTypes());
}
return Collections.unmodifiableList(scriptTypes);
}
@Override
public void scopeValues(ScriptEngine scriptEngine, Map<String, Object> scopeValues) {
for (Entry<String, Object> entry : scopeValues.entrySet()) {
scriptEngine.put(entry.getKey(), entry.getValue());
}
}
@Override
public @Nullable 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;
}
}

View File

@ -12,78 +12,18 @@
*/
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.eclipse.jdt.annotation.NonNullByDefault;
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 methods now inherited from AbstractScriptEngineFactory
*/
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<String> getLanguages() {
ArrayList<String> languages = new ArrayList<>();
for (javax.script.ScriptEngineFactory f : engineManager.getEngineFactories()) {
languages.addAll(f.getExtensions());
}
return languages;
}
@Override
public void scopeValues(ScriptEngine scriptEngine, Map<String, Object> scopeValues) {
for (Entry<String, Object> 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;
}
@NonNullByDefault
@Component(service = ScriptEngineFactory.class)
public class GenericScriptEngineFactory extends AbstractScriptEngineFactory {
}

View File

@ -12,7 +12,8 @@
*/
package org.openhab.core.automation.module.script.internal;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@ -20,66 +21,55 @@ import java.util.Map.Entry;
import java.util.Set;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import org.eclipse.jdt.annotation.NonNullByDefault;
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
*/
@NonNullByDefault
@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<String> getLanguages() {
return Arrays.asList("js", "javascript", "application/javascript");
public List<String> getScriptTypes() {
List<String> scriptTypes = new ArrayList<>();
for (javax.script.ScriptEngineFactory f : engineManager.getEngineFactories()) {
List<String> extensions = f.getExtensions();
if (extensions.contains(SCRIPT_TYPE)) {
scriptTypes.addAll(extensions);
scriptTypes.addAll(f.getMimeTypes());
}
}
return Collections.unmodifiableList(scriptTypes);
}
@Override
public void scopeValues(ScriptEngine engine, Map<String, Object> scopeValues) {
Set<String> expressions = new HashSet<String>();
public void scopeValues(ScriptEngine scriptEngine, Map<String, Object> scopeValues) {
Set<String> expressions = new HashSet<>();
for (Entry<String, Object> entry : scopeValues.entrySet()) {
engine.put(entry.getKey(), entry.getValue());
scriptEngine.put(entry.getKey(), entry.getValue());
if (entry.getValue() instanceof Class) {
expressions.add(String.format("%s = %s.static;", entry.getKey(), entry.getKey()));
expressions.add(String.format("%s = %<s.static;", entry.getKey()));
}
}
String scriptToEval = String.join("\n", expressions);
try {
engine.eval(scriptToEval);
} catch (ScriptException e) {
logger.error("ScriptException while importing scope: {}", e.getMessage());
scriptEngine.eval(scriptToEval);
} catch (ScriptException ex) {
logger.error("ScriptException while importing scope: {}", ex.getMessage());
}
}
@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) {
return getLanguages().contains(fileExtension);
}
}

View File

@ -14,13 +14,13 @@ package org.openhab.core.automation.module.script.internal;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.List;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.automation.module.script.ScriptEngineContainer;
@ -34,22 +34,18 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The ScriptManager allows to load and unloading of script files using a script engines script type
*
* @author Simon Merschjohann
*
* @author Simon Merschjohann - Initial contribution
* @author Scott Rushworth - replaced GenericScriptEngineFactory with a service and cleaned up logging
*/
@NonNullByDefault
@Component(service = ScriptEngineManager.class)
public class ScriptEngineManagerImpl implements ScriptEngineManager {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private Set<ScriptEngineFactory> scriptEngineFactories = new HashSet<>();
private HashMap<String, @Nullable ScriptEngineContainer> loadedScriptEngineInstances = new HashMap<>();
private HashMap<String, @Nullable ScriptEngineFactory> supportedLanguages = new HashMap<>();
private GenericScriptEngineFactory genericScriptEngineFactory = new GenericScriptEngineFactory();
private HashMap<String, @Nullable ScriptEngineFactory> customSupport = new HashMap<>();
private HashMap<String, @Nullable ScriptEngineFactory> genericSupport = new HashMap<>();
private @NonNullByDefault({}) ScriptExtensionManager scriptExtensionManager;
@Reference
@ -62,63 +58,89 @@ public class ScriptEngineManagerImpl implements ScriptEngineManager {
}
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
public void addScriptEngineFactory(ScriptEngineFactory provider) {
this.scriptEngineFactories.add(provider);
public void addScriptEngineFactory(ScriptEngineFactory engineFactory) {
List<String> scriptTypes = engineFactory.getScriptTypes();
for (String language : provider.getLanguages()) {
this.supportedLanguages.put(language, provider);
}
}
public void removeScriptEngineFactory(ScriptEngineFactory provider) {
this.scriptEngineFactories.remove(provider);
for (String language : provider.getLanguages()) {
this.supportedLanguages.remove(language, provider);
}
}
@Override
public boolean isSupported(String fileExtension) {
return findEngineFactory(fileExtension) != null;
}
@Override
public @Nullable ScriptEngineContainer createScriptEngine(String fileExtension, String scriptIdentifier) {
ScriptEngineContainer result = null;
ScriptEngineFactory engineProvider = findEngineFactory(fileExtension);
if (engineProvider == null) {
logger.error("loadScript(): scriptengine for language '{}' could not be found for identifier: {}",
fileExtension, scriptIdentifier);
} else {
try {
ScriptEngine engine = engineProvider.createScriptEngine(fileExtension);
HashMap<String, Object> scriptExManager = new HashMap<>();
result = new ScriptEngineContainer(engine, engineProvider, scriptIdentifier);
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);
} catch (Exception ex) {
logger.error("Error while creating ScriptEngine", ex);
removeScriptExtensions(scriptIdentifier);
logger.trace("{}.getScriptTypes(): {}", engineFactory.getClass().getSimpleName(), scriptTypes);
for (String scriptType : scriptTypes) {
if (isCustomFactory(engineFactory)) {
this.customSupport.put(scriptType, engineFactory);
} else {
this.genericSupport.put(scriptType, engineFactory);
}
}
logger.debug("Added {}", engineFactory.getClass().getSimpleName());
for (javax.script.ScriptEngineFactory f : ScriptEngineFactory.engineManager.getEngineFactories()) {
logger.debug(
"ScriptEngineFactory details for {} ({}): supports {} ({}) with file extensions {}, names {}, and mimetypes {}",
f.getEngineName(), f.getEngineVersion(), f.getLanguageName(), f.getLanguageVersion(),
f.getExtensions(), f.getNames(), f.getMimeTypes());
}
}
public void removeScriptEngineFactory(ScriptEngineFactory engineFactory) {
List<String> scriptTypes = engineFactory.getScriptTypes();
logger.trace("{}.getScriptTypes(): {}", engineFactory.getClass().getSimpleName(), scriptTypes);
for (String scriptType : scriptTypes) {
if (isCustomFactory(engineFactory)) {
this.customSupport.remove(scriptType, engineFactory);
} else {
this.genericSupport.remove(scriptType, engineFactory);
}
}
logger.debug("Removed {}", engineFactory.getClass().getSimpleName());
}
/**
* This method is used to determine if a given {@link ScriptEngineFactory} is generic or customized.
*
* @param engineFactory {@link ScriptEngineFactory}
* @return true, if the {@link ScriptEngineFactory} is custom, otherwise false
*/
private boolean isCustomFactory(ScriptEngineFactory engineFactory) {
return !(engineFactory instanceof GenericScriptEngineFactory);
}
@Override
public @Nullable ScriptEngineContainer createScriptEngine(String scriptType, @NonNull String engineIdentifier) {
ScriptEngineContainer result = null;
ScriptEngineFactory engineFactory = findEngineFactory(scriptType);
if (engineFactory == null) {
logger.error("ScriptEngine for language '{}' could not be found for identifier: {}", scriptType,
engineIdentifier);
} else {
try {
ScriptEngine engine = engineFactory.createScriptEngine(scriptType);
if (engine != null) {
HashMap<String, Object> scriptExManager = new HashMap<>();
result = new ScriptEngineContainer(engine, engineFactory, engineIdentifier);
ScriptExtensionManagerWrapper wrapper = new ScriptExtensionManagerWrapper(scriptExtensionManager,
result);
scriptExManager.put("scriptExtension", wrapper);
scriptExManager.put("se", wrapper);
engineFactory.scopeValues(engine, scriptExManager);
scriptExtensionManager.importDefaultPresets(engineFactory, engine, engineIdentifier);
loadedScriptEngineInstances.put(engineIdentifier, result);
logger.debug("Added ScriptEngine for language '{}' with identifier: {}", scriptType,
engineIdentifier);
} else {
logger.error("ScriptEngine for language '{}' could not be found for identifier: {}", scriptType,
engineIdentifier);
}
} catch (Exception ex) {
logger.error("Error while creating ScriptEngine", ex);
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 +149,36 @@ public class ScriptEngineManagerImpl implements ScriptEngineManager {
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 +186,32 @@ public class ScriptEngineManagerImpl implements ScriptEngineManager {
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;
/**
* 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;
}
for (ScriptEngineFactory provider : supportedLanguages.values()) {
if (provider != null && provider.isSupported(fileExtension)) {
return provider;
}
ScriptEngineFactory genericFactory = genericSupport.get(scriptType);
if (genericFactory != null) {
return genericFactory;
}
if (genericScriptEngineFactory.isSupported(fileExtension)) {
return genericScriptEngineFactory;
}
return null;
}
@Override
public boolean isSupported(String scriptType) {
return findEngineFactory(scriptType) != null;
}
}

View File

@ -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 @@ import org.slf4j.LoggerFactory;
* 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<String> TYPES = unmodifiableList(
asList(ScriptActionHandler.TYPE_ID, ScriptConditionHandler.TYPE_ID));
private @NonNullByDefault({}) ScriptEngineManager scriptEngineManager;
private static final Collection<String> TYPES = Arrays
.asList(new String[] { ScriptActionHandler.SCRIPT_ACTION_ID, ScriptConditionHandler.SCRIPT_CONDITION });
@Override
@Deactivate
protected void deactivate() {
@ -61,6 +61,23 @@ public class ScriptModuleHandlerFactory extends BaseModuleHandlerFactory {
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 class ScriptModuleHandlerFactory extends BaseModuleHandlerFactory {
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;
}
}

View File

@ -26,13 +26,13 @@ import org.slf4j.LoggerFactory;
/**
* This handler can execute script actions.
*
* @author Kai Kreuzer - Initial contribution and API
* @author Kai Kreuzer - Initial contribution
* @author Simon Merschjohann
*
*/
public class ScriptActionHandler extends AbstractScriptModuleHandler<Action> 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);

View File

@ -33,9 +33,9 @@ import org.slf4j.LoggerFactory;
*/
public class ScriptConditionHandler extends AbstractScriptModuleHandler<Condition> 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";
private final Logger logger = LoggerFactory.getLogger(ScriptConditionHandler.class);
public ScriptConditionHandler(Condition module, String ruleUID, ScriptEngineManager scriptEngineManager) {
super(module, ruleUID, scriptEngineManager);

View File

@ -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
public class ScriptModuleTypeProvider implements ModuleTypeProvider {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final List<ParameterOption> 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<Output> outputs = new ArrayList<Output>();
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<ConfigDescriptionParameter> 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<ModuleType> getModuleTypes(Locale locale) {
return Stream.of(getScriptActionType(locale), getScriptConditionType(locale)).collect(Collectors.toList());
}
@Override
public Collection<ModuleType> getAll() {
return getModuleTypes(null);
}
@Override
public void addProviderChangeListener(ProviderChangeListener<ModuleType> listener) {
// does nothing because this provider does not change
}
@Override
public void removeProviderChangeListener(ProviderChangeListener<ModuleType> 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<String> 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();
}
}

View File

@ -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"
}
]
}
]
}