Skip to content

Commit

Permalink
[jsscripting] ES6+ Support (openhab#8516)
Browse files Browse the repository at this point in the history
Signed-off-by: Jonathan Gilbert <jpg@trillica.com>
  • Loading branch information
jpg0 authored May 6, 2021
1 parent 914f7f5 commit fea31a2
Show file tree
Hide file tree
Showing 21 changed files with 1,345 additions and 0 deletions.
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions bom/openhab-addons/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
13 changes: 13 additions & 0 deletions bundles/org.openhab.automation.jsscripting/NOTICE
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
43 changes: 43 additions & 0 deletions bundles/org.openhab.automation.jsscripting/README.md
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!
```
13 changes: 13 additions & 0 deletions bundles/org.openhab.automation.jsscripting/bnd.bnd
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
115 changes: 115 additions & 0 deletions bundles/org.openhab.automation.jsscripting/pom.xml
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>
<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);
}
Loading

0 comments on commit fea31a2

Please sign in to comment.