-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[jsscripting] ES6+ Support #8516
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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! | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you look into embedding this content with BND? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have not, but I will do... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reason that this is in here is because Graal uses ServiceLoaders to load its language definitions, and has one per language per jar. This means that for each jar there is a single It seems that there are a few options here:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds like something non-trivial to rework. So let's stick with what works. 😉 |
||
<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> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to also add this to the bnd.bnd file?