Skip to content

Commit 18237a1

Browse files
committed
Load OH JS file directly from resource, use @oh namespace
Signed-off-by: Dan Cunningham <dan@digitaldan.com>
1 parent 394de63 commit 18237a1

File tree

2 files changed

+153
-49
lines changed

2 files changed

+153
-49
lines changed

bundles/org.openhab.automation.jsscripting/src/main/java/org/openhab/automation/jsscripting/internal/OpenhabGraalJSScriptEngine.java

+60-49
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,16 @@
1818
import java.io.File;
1919
import java.io.IOException;
2020
import java.io.InputStream;
21-
import java.net.URI;
2221
import java.nio.channels.SeekableByteChannel;
22+
import java.nio.file.AccessMode;
2323
import java.nio.file.FileSystems;
24-
import java.nio.file.Files;
24+
import java.nio.file.LinkOption;
2525
import java.nio.file.OpenOption;
2626
import java.nio.file.Path;
2727
import java.nio.file.Paths;
2828
import java.nio.file.attribute.FileAttribute;
29+
import java.util.Collections;
30+
import java.util.Map;
2931
import java.util.Set;
3032
import java.util.function.Consumer;
3133
import java.util.function.Function;
@@ -36,6 +38,7 @@
3638
import org.graalvm.polyglot.Context;
3739
import org.openhab.automation.jsscripting.internal.fs.DelegatingFileSystem;
3840
import org.openhab.automation.jsscripting.internal.fs.PrefixedSeekableByteChannel;
41+
import org.openhab.automation.jsscripting.internal.fs.ReadOnlySeekableByteArrayChannel;
3942
import org.openhab.automation.jsscripting.internal.scriptengine.InvocationInterceptingScriptEngineWithInvocable;
4043
import org.openhab.core.OpenHAB;
4144
import org.openhab.core.automation.module.script.ScriptExtensionAccessor;
@@ -56,7 +59,10 @@ public class OpenhabGraalJSScriptEngine extends InvocationInterceptingScriptEngi
5659
private static final String REQUIRE_WRAPPER_NAME = "__wraprequire__";
5760
private static final String MODULE_DIR = String.join(File.separator, OpenHAB.getConfigFolder(), "automation", "lib",
5861
"javascript", "personal");
59-
62+
// final CommonJS search path for our library
63+
private static final Path OH_NODE_PATH = Paths.get("/node_modules/@oh.js");
64+
// resource path of our oh js library
65+
private static final Path OH_FILE_PATH = Paths.get("/oh.js");
6066
// these fields start as null because they are populated on first use
6167
private @NonNullByDefault({}) String engineIdentifier;
6268
private @NonNullByDefault({}) Consumer<String> scriptDependencyListener;
@@ -69,60 +75,65 @@ public class OpenhabGraalJSScriptEngine extends InvocationInterceptingScriptEngi
6975
*/
7076
public OpenhabGraalJSScriptEngine() {
7177
super(null); // delegate depends on fields not yet initialised, so we cannot set it immediately
78+
delegate = GraalJSScriptEngine.create(null,
79+
Context.newBuilder("js").allowExperimentalOptions(true).allowAllAccess(true)
80+
.option("js.commonjs-require-cwd", MODULE_DIR).option("js.nashorn-compat", "true") // to
81+
// ease
82+
// migration
83+
.option("js.commonjs-require", "true") // enable CommonJS module support
84+
.hostClassLoader(getClass().getClassLoader())
85+
.fileSystem(new DelegatingFileSystem(FileSystems.getDefault().provider()) {
86+
@Override
87+
public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options,
88+
FileAttribute<?>... attrs) throws IOException {
89+
LOGGER.debug("SeekableByteChannel {}", path);
90+
if (scriptDependencyListener != null) {
91+
scriptDependencyListener.accept(path.toString());
92+
}
7293

73-
try {
74-
File tmpDir = Files.createTempDirectory(null).toFile();
75-
tmpDir.deleteOnExit();
76-
InputStream is = getClass().getResourceAsStream("/oh.js");
77-
if (is != null) {
78-
File ohFile = new File(tmpDir, "oh.js");
79-
ohFile.deleteOnExit();
80-
LOGGER.debug("writing globals to {}", ohFile);
81-
Files.write(ohFile.toPath(), is.readAllBytes());
82-
}
83-
84-
delegate = GraalJSScriptEngine.create(null,
85-
Context.newBuilder("js").allowExperimentalOptions(true).allowAllAccess(true)
86-
.option("js.commonjs-require-cwd", MODULE_DIR).option("js.nashorn-compat", "true") // to
87-
// ease
88-
// migration
89-
.option("js.commonjs-require", "true") // enable CommonJS module support
90-
.hostClassLoader(getClass().getClassLoader())
91-
.fileSystem(new DelegatingFileSystem(FileSystems.getDefault().provider()) {
92-
@Override
93-
public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options,
94-
FileAttribute<?>... attrs) throws IOException {
95-
if (scriptDependencyListener != null) {
96-
scriptDependencyListener.accept(path.toString());
94+
if (path.toString().endsWith(".js")) {
95+
SeekableByteChannel sbc = null;
96+
if (OH_FILE_PATH.equals(path)) {
97+
InputStream is = getClass().getResourceAsStream(OH_FILE_PATH.toString());
98+
if (is != null) {
99+
sbc = new ReadOnlySeekableByteArrayChannel(is.readAllBytes());
100+
}
97101
}
98-
99-
if (path.toString().endsWith(".js")) {
100-
return new PrefixedSeekableByteChannel(
101-
("require=" + REQUIRE_WRAPPER_NAME + "(require);").getBytes(),
102-
super.newByteChannel(path, options, attrs));
103-
} else {
104-
return super.newByteChannel(path, options, attrs);
102+
if (sbc == null) {
103+
sbc = super.newByteChannel(path, options, attrs);
105104
}
105+
return new PrefixedSeekableByteChannel(
106+
("require=" + REQUIRE_WRAPPER_NAME + "(require);").getBytes(), sbc);
107+
} else {
108+
return super.newByteChannel(path, options, attrs);
106109
}
110+
}
107111

108-
@Override
109-
public Path parsePath(URI uri) {
110-
return parsePath(uri.toString());
112+
@Override
113+
public void checkAccess(Path path, Set<? extends AccessMode> modes,
114+
LinkOption... linkOptions) throws IOException {
115+
if (!OH_NODE_PATH.equals(path)) {
116+
super.checkAccess(path, modes, linkOptions);
111117
}
118+
}
112119

113-
@Override
114-
public Path parsePath(String path) {
115-
if (path.indexOf(tmpDir.getPath()) != 0
116-
&& path.indexOf(MODULE_DIR.toString()) != 0) {
117-
return tmpDir.toPath();
118-
}
119-
return Paths.get(path);
120+
@Override
121+
public Map<String, Object> readAttributes(Path path, String attributes,
122+
LinkOption... options) throws IOException {
123+
if (OH_NODE_PATH.equals(path)) {
124+
return Collections.singletonMap("isRegularFile", true);
120125
}
121-
}));
122-
} catch (IOException e) {
123-
LOGGER.error("Could not load gloabls", e);
124-
throw new RuntimeException("Could not load gloabls", e);
125-
}
126+
return super.readAttributes(path, attributes, options);
127+
}
128+
129+
@Override
130+
public Path toRealPath(Path path, LinkOption... linkOptions) throws IOException {
131+
if (OH_NODE_PATH.equals(path)) {
132+
return OH_FILE_PATH;
133+
}
134+
return super.toRealPath(path, linkOptions);
135+
}
136+
}));
126137
}
127138

128139
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* Copyright (c) 2010-2021 Contributors to the openHAB project
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0
10+
*
11+
* SPDX-License-Identifier: EPL-2.0
12+
*/
13+
14+
package org.openhab.automation.jsscripting.internal.fs;
15+
16+
import java.io.IOException;
17+
import java.nio.ByteBuffer;
18+
import java.nio.channels.ClosedChannelException;
19+
import java.nio.channels.SeekableByteChannel;
20+
21+
/**
22+
*
23+
* @author daniel
24+
*
25+
*/
26+
public class ReadOnlySeekableByteArrayChannel implements SeekableByteChannel {
27+
private byte[] data;
28+
private int position;
29+
private boolean closed;
30+
31+
public ReadOnlySeekableByteArrayChannel(byte[] data) {
32+
this.data = data;
33+
}
34+
35+
@Override
36+
public long position() {
37+
return position;
38+
}
39+
40+
@Override
41+
public SeekableByteChannel position(long newPosition) throws IOException {
42+
ensureOpen();
43+
position = (int) Math.max(0, Math.min(newPosition, size()));
44+
return this;
45+
}
46+
47+
@Override
48+
public long size() {
49+
return data.length;
50+
}
51+
52+
@Override
53+
public int read(ByteBuffer buf) throws IOException {
54+
ensureOpen();
55+
int remaining = (int) size() - position;
56+
if (remaining <= 0) {
57+
return -1;
58+
}
59+
int readBytes = buf.remaining();
60+
if (readBytes > remaining) {
61+
readBytes = remaining;
62+
}
63+
buf.put(data, position, readBytes);
64+
position += readBytes;
65+
return readBytes;
66+
}
67+
68+
@Override
69+
public void close() {
70+
closed = true;
71+
}
72+
73+
@Override
74+
public boolean isOpen() {
75+
return !closed;
76+
}
77+
78+
@Override
79+
public int write(ByteBuffer b) throws IOException {
80+
throw new UnsupportedOperationException();
81+
}
82+
83+
@Override
84+
public SeekableByteChannel truncate(long newSize) {
85+
throw new UnsupportedOperationException();
86+
}
87+
88+
private void ensureOpen() throws ClosedChannelException {
89+
if (!isOpen()) {
90+
throw new ClosedChannelException();
91+
}
92+
}
93+
}

0 commit comments

Comments
 (0)