mirror of
https://github.com/danieldemus/openhab-core.git
synced 2025-01-25 11:45:49 +01:00
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:
parent
48a97c1ac2
commit
5f880e1062
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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 {
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue
Block a user