[jsscripting] ES6+ Support (#8516)

Signed-off-by: Jonathan Gilbert <jpg@trillica.com>
This commit is contained in:
Jonathan Gilbert 2021-05-06 17:08:47 +10:00 committed by GitHub
parent a1ebab11c5
commit 9912e1afc2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1345 additions and 0 deletions

View File

@ -6,6 +6,7 @@
# Add-on maintainers:
/bundles/org.openhab.automation.groovyscripting/ @wborn
/bundles/org.openhab.automation.jsscripting/ @jpg0
/bundles/org.openhab.automation.jythonscripting/ @openhab/add-ons-maintainers
/bundles/org.openhab.automation.pidcontroller/ @fwolter
/bundles/org.openhab.binding.adorne/ @theiding

View File

@ -31,6 +31,11 @@
<artifactId>org.openhab.automation.pidcontroller</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.automation.jsscripting</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.adorne</artifactId>

View File

@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@ -0,0 +1,43 @@
# JavaScript Scripting
This add-on provides support for JavaScript (ECMAScript 2021+) that can be used as a scripting language within automation rules.
## Creating JavaScript Scripts
When this add-on is installed, JavaScript script actions will be run by this add-on and allow ECMAScript 2021+ features.
Alternatively, you can create scripts in the `automation/jsr223` configuration directory.
If you create an empty file called `test.js`, you will see a log line with information similar to:
```text
... [INFO ] [.a.m.s.r.i.l.ScriptFileWatcher:150 ] - Loading script 'test.js'
```
To enable debug logging, use the [console logging]({{base}}/administration/logging.html) commands to enable debug logging for the automation functionality:
```text
log:set DEBUG org.openhab.core.automation
```
For more information on the available APIs in scripts see the [JSR223 Scripting]({{base}}/configuration/jsr223.html) documentation.
## Script Examples
JavaScript scripts provide access to almost all the functionality in an openHAB runtime environment.
As a simple example, the following script logs "Hello, World!".
Note that `console.log` will usually not work since the output has no terminal to display the text.
The openHAB server uses the [SLF4J](https://www.slf4j.org/) library for logging.
```js
const LoggerFactory = Java.type('org.slf4j.LoggerFactory');
LoggerFactory.getLogger("org.openhab.core.automation.examples").info("Hello world!");
```
Depending on the openHAB logging configuration, you may need to prefix logger names with `org.openhab.core.automation` for them to show up in the log file (or you modify the logging configuration).
The script uses the [LoggerFactory](https://www.slf4j.org/apidocs/org/slf4j/Logger.html) to obtain a named logger and then logs a message like:
```text
... [INFO ] [.openhab.core.automation.examples:-2 ] - Hello world!
```

View File

@ -0,0 +1,13 @@
Bundle-SymbolicName: ${project.artifactId}
DynamicImport-Package: *
Import-Package: org.openhab.core.automation.module.script,javax.management,javax.script,javax.xml.datatype,javax.xml.stream;version="[1.0,2)",org.osgi.framework;version="[1.8,2)",org.slf4j;version="[1.7,2)"
Require-Capability: osgi.extender;
filter:="(osgi.extender=osgi.serviceloader.processor)",
osgi.serviceloader;
filter:="(osgi.serviceloader=org.graalvm.polyglot.impl.AbstractPolyglotImpl)";
cardinality:=multiple
SPI-Provider: *
SPI-Consumer: *
-fixupmessages "Classes found in the wrong directory"; restrict:=error; is:=warning

View File

@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.automation.jsscripting</artifactId>
<name>openHAB Add-ons :: Bundles :: Automation :: JSScripting</name>
<properties>
<bnd.importpackage>
!sun.misc.*,
!sun.reflect.*,
!com.sun.management.*,
!jdk.internal.reflect.*,
!jdk.vm.ci.services
</bnd.importpackage>
<graal.version>20.1.0</graal.version>
<asm.version>6.2.1</asm.version>
<oh.version>${project.version}</oh.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>embed-dependencies</id>
<goals>
<goal>unpack-dependencies</goal>
</goals>
<configuration>
<excludes>META-INF/services/com.oracle.truffle.api.TruffleLanguage$Provider</excludes> <!-- we'll provide this -->
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.graalvm.truffle</groupId>
<artifactId>truffle-api</artifactId>
<version>${graal.version}</version>
</dependency>
<dependency>
<groupId>org.graalvm.js</groupId>
<artifactId>js-scriptengine</artifactId>
<version>${graal.version}</version>
</dependency>
<dependency>
<groupId>org.graalvm.js</groupId>
<artifactId>js-launcher</artifactId>
<version>${graal.version}</version>
</dependency>
<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>graal-sdk</artifactId>
<version>${graal.version}</version>
</dependency>
<dependency>
<groupId>org.graalvm.regex</groupId>
<artifactId>regex</artifactId>
<version>${graal.version}</version>
</dependency>
<dependency> <!-- this must come AFTER the regex lib -->
<groupId>org.graalvm.js</groupId>
<artifactId>js</artifactId>
<version>${graal.version}</version>
</dependency>
<dependency>
<groupId>com.ibm.icu</groupId>
<artifactId>icu4j</artifactId>
<version>62.1</version>
</dependency>
<!-- include as version required is older than OH provides -->
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-commons</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-tree</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-util</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-analysis</artifactId>
<version>${asm.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.automation.jsscripting-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-automation-jsscripting" description="JSScripting" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.automation.jsscripting/${project.version}</bundle>
</feature>
</features>

View File

@ -0,0 +1,37 @@
/**
* Copyright (c) 2010-2021 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.automation.jsscripting;
import com.oracle.truffle.js.runtime.java.adapter.JavaAdapterFactory;
/**
* Class utility to allow creation of 'extendable' classes with a classloader of the GraalJS bundle, rather than the
* classloader of the file being extended.
*
* @author Jonathan Gilbert - Initial contribution
*/
public class ClassExtender {
private static ClassLoader classLoader = ClassExtender.class.getClassLoader();
public static Object extend(String className) {
try {
return extend(Class.forName(className));
} catch (ClassNotFoundException e) {
throw new RuntimeException("Cannot find class " + className, e);
}
}
public static Object extend(Class<?> clazz) {
return JavaAdapterFactory.getAdapterClassFor(clazz, null, classLoader);
}
}

View File

@ -0,0 +1,49 @@
/**
* Copyright (c) 2010-2021 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.automation.jsscripting.internal;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.graalvm.polyglot.PolyglotException;
import org.openhab.automation.jsscripting.internal.scriptengine.InvocationInterceptingScriptEngineWithInvocable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Wraps ScriptEngines provided by Graal to provide error messages and stack traces for scripts.
*
* @author Jonathan Gilbert - Initial contribution
*/
@NonNullByDefault
class DebuggingGraalScriptEngine<T extends ScriptEngine & Invocable>
extends InvocationInterceptingScriptEngineWithInvocable<T> {
private static final Logger stackLogger = LoggerFactory.getLogger("org.openhab.automation.script.javascript.stack");
public DebuggingGraalScriptEngine(T delegate) {
super(delegate);
}
@Override
public ScriptException afterThrowsInvocation(ScriptException se) {
Throwable cause = se.getCause();
if (cause instanceof PolyglotException) {
stackLogger.error("Failed to execute script:", cause);
}
return se;
}
}

View File

@ -0,0 +1,56 @@
/**
* Copyright (c) 2010-2021 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.automation.jsscripting.internal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.script.ScriptEngine;
import org.openhab.core.automation.module.script.ScriptEngineFactory;
import org.osgi.service.component.annotations.Component;
import com.oracle.truffle.js.scriptengine.GraalJSEngineFactory;
/**
* An implementation of {@link ScriptEngineFactory} with customizations for GraalJS ScriptEngines.
*
* @author Jonathan Gilbert - Initial contribution
*/
@Component(service = ScriptEngineFactory.class)
public final class GraalJSScriptEngineFactory implements ScriptEngineFactory {
@Override
public List<String> getScriptTypes() {
List<String> scriptTypes = new ArrayList<>();
GraalJSEngineFactory graalJSEngineFactory = new GraalJSEngineFactory();
scriptTypes.addAll(graalJSEngineFactory.getMimeTypes());
scriptTypes.addAll(graalJSEngineFactory.getExtensions());
return Collections.unmodifiableList(scriptTypes);
}
@Override
public void scopeValues(ScriptEngine scriptEngine, Map<String, Object> scopeValues) {
// noop; the are retrieved via modules, not injected
}
@Override
public ScriptEngine createScriptEngine(String scriptType) {
OpenhabGraalJSScriptEngine engine = new OpenhabGraalJSScriptEngine();
return new DebuggingGraalScriptEngine<>(engine);
}
}

View File

@ -0,0 +1,27 @@
/**
* Copyright (c) 2010-2021 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.automation.jsscripting.internal;
import java.util.Optional;
import org.graalvm.polyglot.Value;
/**
* Locates modules from a module name
*
* @author Jonathan Gilbert - Initial contribution
*/
public interface ModuleLocator {
Optional<Value> locateModule(String name);
}

View File

@ -0,0 +1,134 @@
/**
* Copyright (c) 2010-2021 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.automation.jsscripting.internal;
import static org.openhab.core.automation.module.script.ScriptEngineFactory.CONTEXT_KEY_ENGINE_IDENTIFIER;
import static org.openhab.core.automation.module.script.ScriptEngineFactory.CONTEXT_KEY_EXTENSION_ACCESSOR;
import java.io.File;
import java.io.IOException;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.FileSystems;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.attribute.FileAttribute;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.script.ScriptContext;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.graalvm.polyglot.Context;
import org.openhab.automation.jsscripting.internal.fs.DelegatingFileSystem;
import org.openhab.automation.jsscripting.internal.fs.PrefixedSeekableByteChannel;
import org.openhab.automation.jsscripting.internal.scriptengine.InvocationInterceptingScriptEngineWithInvocable;
import org.openhab.core.OpenHAB;
import org.openhab.core.automation.module.script.ScriptExtensionAccessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.oracle.truffle.js.scriptengine.GraalJSScriptEngine;
/**
* GraalJS Script Engine implementation
*
* @author Jonathan Gilbert - Initial contribution
*/
public class OpenhabGraalJSScriptEngine extends InvocationInterceptingScriptEngineWithInvocable<GraalJSScriptEngine> {
private static final Logger logger = LoggerFactory.getLogger(OpenhabGraalJSScriptEngine.class);
private static final String REQUIRE_WRAPPER_NAME = "__wraprequire__";
private static final String MODULE_DIR = String.join(File.separator, OpenHAB.getConfigFolder(), "automation", "lib",
"javascript", "personal");
// these fields start as null because they are populated on first use
@NonNullByDefault({})
private String engineIdentifier;
@NonNullByDefault({})
private Consumer<String> scriptDependencyListener;
private boolean initialized = false;
/**
* Creates an implementation of ScriptEngine (& Invocable), wrapping the contained engine, that tracks the script
* lifecycle and provides hooks for scripts to do so too.
*/
public OpenhabGraalJSScriptEngine() {
super(null); // delegate depends on fields not yet initialised, so we cannot set it immediately
delegate = GraalJSScriptEngine.create(null,
Context.newBuilder("js").allowExperimentalOptions(true).allowAllAccess(true)
.option("js.commonjs-require-cwd", MODULE_DIR).option("js.nashorn-compat", "true") // to ease
// migration
.option("js.commonjs-require", "true") // enable CommonJS module support
.fileSystem(new DelegatingFileSystem(FileSystems.getDefault().provider()) {
@Override
public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options,
FileAttribute<?>... attrs) throws IOException {
if (scriptDependencyListener != null) {
scriptDependencyListener.accept(path.toString());
}
if (path.toString().endsWith(".js")) {
return new PrefixedSeekableByteChannel(
("require=" + REQUIRE_WRAPPER_NAME + "(require);").getBytes(),
super.newByteChannel(path, options, attrs));
} else {
return super.newByteChannel(path, options, attrs);
}
}
}));
}
@Override
protected void beforeInvocation() {
if (initialized) {
return;
}
ScriptContext ctx = delegate.getContext();
// these are added post-construction, so we need to fetch them late
this.engineIdentifier = (String) ctx.getAttribute(CONTEXT_KEY_ENGINE_IDENTIFIER);
if (this.engineIdentifier == null) {
throw new IllegalStateException("Failed to retrieve engine identifier from engine bindings");
}
ScriptExtensionAccessor scriptExtensionAccessor = (ScriptExtensionAccessor) ctx
.getAttribute(CONTEXT_KEY_EXTENSION_ACCESSOR);
if (scriptExtensionAccessor == null) {
throw new IllegalStateException("Failed to retrieve script extension accessor from engine bindings");
}
scriptDependencyListener = (Consumer<String>) ctx
.getAttribute("oh.dependency-listener"/* CONTEXT_KEY_DEPENDENCY_LISTENER */);
if (scriptDependencyListener == null) {
logger.warn(
"Failed to retrieve script script dependency listener from engine bindings. Script dependency tracking will be disabled.");
}
ScriptExtensionModuleProvider scriptExtensionModuleProvider = new ScriptExtensionModuleProvider(
scriptExtensionAccessor);
Function<Function<Object[], Object>, Function<String, Object>> wrapRequireFn = originalRequireFn -> moduleName -> scriptExtensionModuleProvider
.locatorFor(delegate.getPolyglotContext(), engineIdentifier).locateModule(moduleName)
.map(m -> (Object) m).orElseGet(() -> originalRequireFn.apply(new Object[] { moduleName }));
delegate.getBindings(ScriptContext.ENGINE_SCOPE).put(REQUIRE_WRAPPER_NAME, wrapRequireFn);
delegate.put("require", wrapRequireFn.apply((Function<Object[], Object>) delegate.get("require")));
initialized = true;
}
}

View File

@ -0,0 +1,104 @@
/**
* Copyright (c) 2010-2021 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.automation.jsscripting.internal;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;
import org.openhab.automation.jsscripting.internal.threading.ThreadsafeWrappingScriptedAutomationManagerDelegate;
import org.openhab.core.automation.module.script.ScriptExtensionAccessor;
import org.openhab.core.automation.module.script.rulesupport.shared.ScriptedAutomationManager;
/**
* Class providing script extensions via CommonJS modules.
*
* @author Jonathan Gilbert - Initial contribution
*/
@NonNullByDefault
public class ScriptExtensionModuleProvider {
private static final String RUNTIME_MODULE_PREFIX = "@runtime";
private static final String DEFAULT_MODULE_NAME = "Defaults";
private final ScriptExtensionAccessor scriptExtensionAccessor;
public ScriptExtensionModuleProvider(ScriptExtensionAccessor scriptExtensionAccessor) {
this.scriptExtensionAccessor = scriptExtensionAccessor;
}
public ModuleLocator locatorFor(Context ctx, String engineIdentifier) {
return name -> {
String[] segments = name.split("/");
if (segments[0].equals(RUNTIME_MODULE_PREFIX)) {
if (segments.length == 1) {
return runtimeModule(DEFAULT_MODULE_NAME, engineIdentifier, ctx);
} else {
return runtimeModule(segments[1], engineIdentifier, ctx);
}
}
return Optional.empty();
};
}
private Optional<Value> runtimeModule(String name, String scriptIdentifier, Context ctx) {
Map<String, Object> symbols;
if (DEFAULT_MODULE_NAME.equals(name)) {
symbols = scriptExtensionAccessor.findDefaultPresets(scriptIdentifier);
} else {
symbols = scriptExtensionAccessor.findPreset(name, scriptIdentifier);
}
return Optional.of(symbols).map(this::processValues).map(v -> toValue(ctx, v));
}
private Value toValue(Context ctx, Map<String, Object> map) {
try {
return ctx.eval(Source.newBuilder( // convert to Map to JS Object
"js",
"(function (mapOfValues) {\n" + "let rv = {};\n" + "for (var key in mapOfValues) {\n"
+ " rv[key] = mapOfValues.get(key);\n" + "}\n" + "return rv;\n" + "})",
"<generated>").build()).execute(map);
} catch (IOException e) {
throw new IllegalArgumentException("Failed to generate exports", e);
}
}
/**
* Some specific objects need wrapping when exposed to a GraalJS environment. This method does this.
*
* @param values the map of names to values of things to process
* @return a map of the processed keys and values
*/
private Map<String, Object> processValues(Map<String, Object> values) {
Map<String, Object> rv = new HashMap<>(values);
for (Map.Entry<String, Object> entry : rv.entrySet()) {
if (entry.getValue() instanceof ScriptedAutomationManager) {
entry.setValue(new ThreadsafeWrappingScriptedAutomationManagerDelegate(
(ScriptedAutomationManager) entry.getValue()));
}
}
return rv;
}
}

View File

@ -0,0 +1,95 @@
/**
* Copyright (c) 2010-2021 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.automation.jsscripting.internal.fs;
import java.io.IOException;
import java.net.URI;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.AccessMode;
import java.nio.file.DirectoryStream;
import java.nio.file.LinkOption;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.spi.FileSystemProvider;
import java.util.Map;
import java.util.Set;
import org.graalvm.polyglot.io.FileSystem;
/**
* Delegate wrapping a {@link FileSystem}
*
* @author Jonathan Gilbert - Initial contribution
*/
public class DelegatingFileSystem implements FileSystem {
private FileSystemProvider delegate;
public DelegatingFileSystem(FileSystemProvider delegate) {
this.delegate = delegate;
}
@Override
public Path parsePath(URI uri) {
return Paths.get(uri);
}
@Override
public Path parsePath(String path) {
return Paths.get(path);
}
@Override
public void checkAccess(Path path, Set<? extends AccessMode> modes, LinkOption... linkOptions) throws IOException {
delegate.checkAccess(path, modes.toArray(new AccessMode[0]));
}
@Override
public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
delegate.createDirectory(dir, attrs);
}
@Override
public void delete(Path path) throws IOException {
delegate.delete(path);
}
@Override
public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs)
throws IOException {
return delegate.newByteChannel(path, options, attrs);
}
@Override
public DirectoryStream<Path> newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter)
throws IOException {
return delegate.newDirectoryStream(dir, filter);
}
@Override
public Path toAbsolutePath(Path path) {
return path.toAbsolutePath();
}
@Override
public Path toRealPath(Path path, LinkOption... linkOptions) throws IOException {
return path.toRealPath(linkOptions);
}
@Override
public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
return delegate.readAttributes(path, attributes, options);
}
}

View File

@ -0,0 +1,95 @@
/**
* Copyright (c) 2010-2021 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.automation.jsscripting.internal.fs;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.util.Arrays;
/**
* Wrapper for a {@link SeekableByteChannel} allowing prefixing the stream with a fixed array of bytes
*
* @author Jonathan Gilbert - Initial contribution
*/
public class PrefixedSeekableByteChannel implements SeekableByteChannel {
private byte[] prefix;
private SeekableByteChannel source;
private long position;
public PrefixedSeekableByteChannel(byte[] prefix, SeekableByteChannel source) {
this.prefix = prefix;
this.source = source;
}
@Override
public int read(ByteBuffer dst) throws IOException {
int read = 0;
if (position < prefix.length) {
dst.put(Arrays.copyOfRange(prefix, (int) position, prefix.length));
read += prefix.length - position;
}
read += source.read(dst);
position += read;
return read;
}
@Override
public int write(ByteBuffer src) throws IOException {
throw new IOException("Read only!");
}
@Override
public long position() throws IOException {
return position;
}
@Override
public SeekableByteChannel position(long newPosition) throws IOException {
this.position = newPosition;
if (newPosition > prefix.length) {
source.position(newPosition - prefix.length);
}
return this;
}
@Override
public long size() throws IOException {
return source.size() + prefix.length;
}
@Override
public SeekableByteChannel truncate(long size) throws IOException {
throw new IOException("Read only!");
}
@Override
public boolean isOpen() {
return source.isOpen();
}
@Override
public void close() throws IOException {
source.close();
}
}

View File

@ -0,0 +1,128 @@
/**
* Copyright (c) 2010-2021 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.automation.jsscripting.internal.scriptengine;
import java.io.Reader;
import javax.script.Bindings;
import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptException;
/**
* {@link ScriptEngine} implementation that delegates to a supplied ScriptEngine instance. Allows overriding specific
* methods.
*
* @author Jonathan Gilbert - Initial contribution
*/
public abstract class DelegatingScriptEngineWithInvocable<T extends ScriptEngine & Invocable>
implements ScriptEngine, Invocable {
protected T delegate;
public DelegatingScriptEngineWithInvocable(T delegate) {
this.delegate = delegate;
}
@Override
public Object eval(String s, ScriptContext scriptContext) throws ScriptException {
return delegate.eval(s, scriptContext);
}
@Override
public Object eval(Reader reader, ScriptContext scriptContext) throws ScriptException {
return delegate.eval(reader, scriptContext);
}
@Override
public Object eval(String s) throws ScriptException {
return delegate.eval(s);
}
@Override
public Object eval(Reader reader) throws ScriptException {
return delegate.eval(reader);
}
@Override
public Object eval(String s, Bindings bindings) throws ScriptException {
return delegate.eval(s, bindings);
}
@Override
public Object eval(Reader reader, Bindings bindings) throws ScriptException {
return delegate.eval(reader, bindings);
}
@Override
public void put(String s, Object o) {
delegate.put(s, o);
}
@Override
public Object get(String s) {
return delegate.get(s);
}
@Override
public Bindings getBindings(int i) {
return delegate.getBindings(i);
}
@Override
public void setBindings(Bindings bindings, int i) {
delegate.setBindings(bindings, i);
}
@Override
public Bindings createBindings() {
return delegate.createBindings();
}
@Override
public ScriptContext getContext() {
return delegate.getContext();
}
@Override
public void setContext(ScriptContext scriptContext) {
delegate.setContext(scriptContext);
}
@Override
public ScriptEngineFactory getFactory() {
return delegate.getFactory();
}
@Override
public Object invokeMethod(Object o, String s, Object... objects) throws ScriptException, NoSuchMethodException {
return delegate.invokeMethod(o, s, objects);
}
@Override
public Object invokeFunction(String s, Object... objects) throws ScriptException, NoSuchMethodException {
return delegate.invokeFunction(s, objects);
}
@Override
public <T> T getInterface(Class<T> aClass) {
return delegate.getInterface(aClass);
}
@Override
public <T> T getInterface(Object o, Class<T> aClass) {
return delegate.getInterface(o, aClass);
}
}

View File

@ -0,0 +1,127 @@
/**
* Copyright (c) 2010-2021 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.automation.jsscripting.internal.scriptengine;
import java.io.Reader;
import javax.script.Bindings;
import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Delegate allowing AOP-style interception of calls, either before Invocation, or upon a {@link ScriptException}.
* being thrown.
*
* @param <T> The delegate class
* @author Jonathan Gilbert - Initial contribution
*/
@NonNullByDefault
public abstract class InvocationInterceptingScriptEngineWithInvocable<T extends ScriptEngine & Invocable>
extends DelegatingScriptEngineWithInvocable<T> {
public InvocationInterceptingScriptEngineWithInvocable(T delegate) {
super(delegate);
}
protected void beforeInvocation() {
}
protected ScriptException afterThrowsInvocation(ScriptException se) {
return se;
}
@Override
public Object eval(String s, ScriptContext scriptContext) throws ScriptException {
try {
beforeInvocation();
return super.eval(s, scriptContext);
} catch (ScriptException se) {
throw afterThrowsInvocation(se);
}
}
@Override
public Object eval(Reader reader, ScriptContext scriptContext) throws ScriptException {
try {
beforeInvocation();
return super.eval(reader, scriptContext);
} catch (ScriptException se) {
throw afterThrowsInvocation(se);
}
}
@Override
public Object eval(String s) throws ScriptException {
try {
beforeInvocation();
return super.eval(s);
} catch (ScriptException se) {
throw afterThrowsInvocation(se);
}
}
@Override
public Object eval(Reader reader) throws ScriptException {
try {
beforeInvocation();
return super.eval(reader);
} catch (ScriptException se) {
throw afterThrowsInvocation(se);
}
}
@Override
public Object eval(String s, Bindings bindings) throws ScriptException {
try {
beforeInvocation();
return super.eval(s, bindings);
} catch (ScriptException se) {
throw afterThrowsInvocation(se);
}
}
@Override
public Object eval(Reader reader, Bindings bindings) throws ScriptException {
try {
beforeInvocation();
return super.eval(reader, bindings);
} catch (ScriptException se) {
throw afterThrowsInvocation(se);
}
}
@Override
public Object invokeMethod(Object o, String s, Object... objects) throws ScriptException, NoSuchMethodException {
try {
beforeInvocation();
return super.invokeMethod(o, s, objects);
} catch (ScriptException se) {
throw afterThrowsInvocation(se);
}
}
@Override
public Object invokeFunction(String s, Object... objects) throws ScriptException, NoSuchMethodException {
try {
beforeInvocation();
return super.invokeFunction(s, objects);
} catch (ScriptException se) {
throw afterThrowsInvocation(se);
}
}
}

View File

@ -0,0 +1,185 @@
/**
* Copyright (c) 2010-2021 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.automation.jsscripting.internal.threading;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.automation.Action;
import org.openhab.core.automation.Condition;
import org.openhab.core.automation.Module;
import org.openhab.core.automation.Rule;
import org.openhab.core.automation.Trigger;
import org.openhab.core.automation.Visibility;
import org.openhab.core.automation.module.script.rulesupport.shared.simple.SimpleRule;
import org.openhab.core.automation.module.script.rulesupport.shared.simple.SimpleRuleActionHandler;
import org.openhab.core.config.core.ConfigDescriptionParameter;
import org.openhab.core.config.core.Configuration;
/**
* An version of {@link SimpleRule} which controls multithreaded execution access to this specific rule. This is useful
* for rules which wrap GraalJS Contexts, which are not multithreaded.
*
* @author Jonathan Gilbert - Initial contribution
*/
@NonNullByDefault
class ThreadsafeSimpleRuleDelegate implements Rule, SimpleRuleActionHandler {
private final Object lock;
private final SimpleRule delegate;
/**
* Constructor requires a lock object and delegate to forward invocations to.
*
* @param lock rule executions will synchronize on this object
* @param delegate the delegate to forward invocations to
*/
ThreadsafeSimpleRuleDelegate(Object lock, SimpleRule delegate) {
this.lock = lock;
this.delegate = delegate;
}
@Override
@NonNullByDefault({})
public Object execute(Action module, Map<String, ?> inputs) {
synchronized (lock) {
return delegate.execute(module, inputs);
}
}
@Override
public String getUID() {
return delegate.getUID();
}
@Override
@Nullable
public String getTemplateUID() {
return delegate.getTemplateUID();
}
public void setTemplateUID(@Nullable String templateUID) {
delegate.setTemplateUID(templateUID);
}
@Override
@Nullable
public String getName() {
return delegate.getName();
}
public void setName(@Nullable String ruleName) {
delegate.setName(ruleName);
}
@Override
public Set<String> getTags() {
return delegate.getTags();
}
public void setTags(@Nullable Set<String> ruleTags) {
delegate.setTags(ruleTags);
}
@Override
@Nullable
public String getDescription() {
return delegate.getDescription();
}
public void setDescription(@Nullable String ruleDescription) {
delegate.setDescription(ruleDescription);
}
@Override
public Visibility getVisibility() {
return delegate.getVisibility();
}
public void setVisibility(@Nullable Visibility visibility) {
delegate.setVisibility(visibility);
}
@Override
public Configuration getConfiguration() {
return delegate.getConfiguration();
}
public void setConfiguration(@Nullable Configuration ruleConfiguration) {
delegate.setConfiguration(ruleConfiguration);
}
@Override
public List<ConfigDescriptionParameter> getConfigurationDescriptions() {
return delegate.getConfigurationDescriptions();
}
public void setConfigurationDescriptions(@Nullable List<ConfigDescriptionParameter> configDescriptions) {
delegate.setConfigurationDescriptions(configDescriptions);
}
@Override
public List<Condition> getConditions() {
return delegate.getConditions();
}
public void setConditions(@Nullable List<Condition> conditions) {
delegate.setConditions(conditions);
}
@Override
public List<Action> getActions() {
return delegate.getActions();
}
@Override
public List<Trigger> getTriggers() {
return delegate.getTriggers();
}
public void setActions(@Nullable List<Action> actions) {
delegate.setActions(actions);
}
public void setTriggers(@Nullable List<Trigger> triggers) {
delegate.setTriggers(triggers);
}
@Override
public List<Module> getModules() {
return delegate.getModules();
}
public <T extends Module> List<T> getModules(@Nullable Class<T> moduleClazz) {
return delegate.getModules(moduleClazz);
}
@Override
public int hashCode() {
return delegate.hashCode();
}
@Override
public boolean equals(@Nullable Object obj) {
return delegate.equals(obj);
}
@Override
@Nullable
public Module getModule(String moduleId) {
return delegate.getModule(moduleId);
}
}

View File

@ -0,0 +1,105 @@
/**
* Copyright (c) 2010-2021 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.automation.jsscripting.internal.threading;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.automation.Rule;
import org.openhab.core.automation.module.script.rulesupport.shared.ScriptedAutomationManager;
import org.openhab.core.automation.module.script.rulesupport.shared.ScriptedHandler;
import org.openhab.core.automation.module.script.rulesupport.shared.simple.SimpleActionHandler;
import org.openhab.core.automation.module.script.rulesupport.shared.simple.SimpleConditionHandler;
import org.openhab.core.automation.module.script.rulesupport.shared.simple.SimpleRule;
import org.openhab.core.automation.module.script.rulesupport.shared.simple.SimpleTriggerHandler;
import org.openhab.core.automation.type.ActionType;
import org.openhab.core.automation.type.ConditionType;
import org.openhab.core.automation.type.TriggerType;
/**
* A replacement for {@link ScriptedAutomationManager} which wraps all rule registrations in a
* {@link ThreadsafeSimpleRuleDelegate}. This means that all rules registered via this class with be run in serial per
* instance of this class that they are registered with.
*
* @author Jonathan Gilbert - Initial contribution
*/
@NonNullByDefault
public class ThreadsafeWrappingScriptedAutomationManagerDelegate {
private ScriptedAutomationManager delegate;
private Object lock = new Object();
public ThreadsafeWrappingScriptedAutomationManagerDelegate(ScriptedAutomationManager delegate) {
this.delegate = delegate;
}
public void removeModuleType(String UID) {
delegate.removeModuleType(UID);
}
public void removeHandler(String typeUID) {
delegate.removeHandler(typeUID);
}
public void removePrivateHandler(String privId) {
delegate.removePrivateHandler(privId);
}
public void removeAll() {
delegate.removeAll();
}
public Rule addRule(Rule element) {
// wrap in a threadsafe version, safe per context
if (element instanceof SimpleRule) {
element = new ThreadsafeSimpleRuleDelegate(lock, (SimpleRule) element);
}
return delegate.addRule(element);
}
public void addConditionType(ConditionType condititonType) {
delegate.addConditionType(condititonType);
}
public void addConditionHandler(String uid, ScriptedHandler conditionHandler) {
delegate.addConditionHandler(uid, conditionHandler);
}
public String addPrivateConditionHandler(SimpleConditionHandler conditionHandler) {
return delegate.addPrivateConditionHandler(conditionHandler);
}
public void addActionType(ActionType actionType) {
delegate.addActionType(actionType);
}
public void addActionHandler(String uid, ScriptedHandler actionHandler) {
delegate.addActionHandler(uid, actionHandler);
}
public String addPrivateActionHandler(SimpleActionHandler actionHandler) {
return delegate.addPrivateActionHandler(actionHandler);
}
public void addTriggerType(TriggerType triggerType) {
delegate.addTriggerType(triggerType);
}
public void addTriggerHandler(String uid, ScriptedHandler triggerHandler) {
delegate.addTriggerHandler(uid, triggerHandler);
}
public String addPrivateTriggerHandler(SimpleTriggerHandler triggerHandler) {
return delegate.addPrivateTriggerHandler(triggerHandler);
}
}

View File

@ -0,0 +1,2 @@
com.oracle.truffle.regex.RegexLanguageProvider
com.oracle.truffle.js.lang.JavaScriptLanguageProvider

View File

@ -21,6 +21,7 @@
<module>org.openhab.automation.groovyscripting</module>
<module>org.openhab.automation.jythonscripting</module>
<module>org.openhab.automation.pidcontroller</module>
<module>org.openhab.automation.jsscripting</module>
<!-- io -->
<module>org.openhab.io.homekit</module>
<module>org.openhab.io.hueemulation</module>