From b9286b0fcecdb3946809ca418b71980e5fcc1a96 Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Tue, 10 Oct 2023 22:11:59 +1000 Subject: [PATCH] [jrubyscripting] Inject script context as global (#15618) Signed-off-by: Jimmy Tanagra --- .../internal/JRubyEngineWrapper.java | 218 ++++++++++++++++++ .../internal/JRubyScriptEngineFactory.java | 2 +- 2 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 bundles/org.openhab.automation.jrubyscripting/src/main/java/org/openhab/automation/jrubyscripting/internal/JRubyEngineWrapper.java diff --git a/bundles/org.openhab.automation.jrubyscripting/src/main/java/org/openhab/automation/jrubyscripting/internal/JRubyEngineWrapper.java b/bundles/org.openhab.automation.jrubyscripting/src/main/java/org/openhab/automation/jrubyscripting/internal/JRubyEngineWrapper.java new file mode 100644 index 0000000000000..7be64da4b8f0a --- /dev/null +++ b/bundles/org.openhab.automation.jrubyscripting/src/main/java/org/openhab/automation/jrubyscripting/internal/JRubyEngineWrapper.java @@ -0,0 +1,218 @@ +/** + * Copyright (c) 2010-2023 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.jrubyscripting.internal; + +import java.io.Reader; +import java.util.Objects; + +import javax.script.Bindings; +import javax.script.Compilable; +import javax.script.CompiledScript; +import javax.script.Invocable; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineFactory; +import javax.script.ScriptException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.jruby.embed.jsr223.JRubyEngine; + +/** + * This is a wrapper for {@link JRubyEngine}. + * + * The purpose of this class is to intercept the call to eval and save the context into + * a global variable for use in the helper library. + * + * @author Jimmy Tanagra - Initial contribution + */ +@NonNullByDefault +public class JRubyEngineWrapper implements Compilable, Invocable, ScriptEngine { + + private final JRubyEngine engine; + + private static final String CONTEXT_VAR_NAME = "ctx"; + private static final String GLOBAL_VAR_NAME = "$" + CONTEXT_VAR_NAME; + + JRubyEngineWrapper(JRubyEngine engine) { + this.engine = Objects.requireNonNull(engine); + } + + @Override + public CompiledScript compile(@Nullable String script) throws ScriptException { + return engine.compile(script); + } + + @Override + public CompiledScript compile(@Nullable Reader reader) throws ScriptException { + return engine.compile(reader); + } + + @Override + public Object eval(@Nullable String script, @Nullable ScriptContext context) throws ScriptException { + Object ctx = Objects.requireNonNull(context).getBindings(ScriptContext.ENGINE_SCOPE).get(CONTEXT_VAR_NAME); + + if (ctx == null) { + return engine.eval(script, context); + } + + context.setAttribute(GLOBAL_VAR_NAME, ctx, ScriptContext.ENGINE_SCOPE); + try { + return engine.eval(script, context); + } finally { + context.removeAttribute(GLOBAL_VAR_NAME, ScriptContext.ENGINE_SCOPE); + } + } + + @Override + public Object eval(@Nullable Reader reader, @Nullable ScriptContext context) throws ScriptException { + Object ctx = Objects.requireNonNull(context).getBindings(ScriptContext.ENGINE_SCOPE).get(CONTEXT_VAR_NAME); + + if (ctx == null) { + return engine.eval(reader, context); + } + + context.setAttribute(GLOBAL_VAR_NAME, ctx, ScriptContext.ENGINE_SCOPE); + try { + return engine.eval(reader, context); + } finally { + context.removeAttribute(GLOBAL_VAR_NAME, ScriptContext.ENGINE_SCOPE); + } + } + + @Override + public Object eval(@Nullable String script, @Nullable Bindings bindings) throws ScriptException { + Object ctx = Objects.requireNonNull(bindings).get(CONTEXT_VAR_NAME); + + if (ctx == null) { + return engine.eval(script, bindings); + } + + bindings.put(GLOBAL_VAR_NAME, ctx); + try { + return engine.eval(script, bindings); + } finally { + bindings.remove(GLOBAL_VAR_NAME); + } + } + + @Override + public Object eval(@Nullable Reader reader, @Nullable Bindings bindings) throws ScriptException { + Object ctx = Objects.requireNonNull(bindings).get(CONTEXT_VAR_NAME); + + if (ctx == null) { + return engine.eval(reader, bindings); + } + + bindings.put(GLOBAL_VAR_NAME, ctx); + try { + return engine.eval(reader, bindings); + } finally { + bindings.remove(GLOBAL_VAR_NAME); + } + } + + @Override + public Object eval(@Nullable String script) throws ScriptException { + Object ctx = getBindings(ScriptContext.ENGINE_SCOPE).get(CONTEXT_VAR_NAME); + + if (ctx == null) { + return engine.eval(script); + } + + getContext().setAttribute(GLOBAL_VAR_NAME, ctx, ScriptContext.ENGINE_SCOPE); + try { + return engine.eval(script); + } finally { + getContext().removeAttribute(GLOBAL_VAR_NAME, ScriptContext.ENGINE_SCOPE); + } + } + + @Override + public Object eval(@Nullable Reader reader) throws ScriptException { + Object ctx = getBindings(ScriptContext.ENGINE_SCOPE).get(CONTEXT_VAR_NAME); + + if (ctx == null) { + return engine.eval(reader); + } + + getContext().setAttribute(GLOBAL_VAR_NAME, ctx, ScriptContext.ENGINE_SCOPE); + try { + return engine.eval(reader); + } finally { + getContext().removeAttribute(GLOBAL_VAR_NAME, ScriptContext.ENGINE_SCOPE); + } + } + + @Override + public Object get(@Nullable String key) { + return engine.get(key); + } + + @Override + public void put(@Nullable String key, @Nullable Object value) { + engine.put(key, value); + } + + @Override + public Bindings getBindings(int scope) { + return engine.getBindings(scope); + } + + @Override + public void setBindings(@Nullable Bindings bindings, int scope) { + engine.setBindings(bindings, scope); + } + + @Override + public Bindings createBindings() { + return engine.createBindings(); + } + + @Override + public ScriptContext getContext() { + return engine.getContext(); + } + + @Override + public void setContext(@Nullable ScriptContext context) { + engine.setContext(context); + } + + @Override + public ScriptEngineFactory getFactory() { + return engine.getFactory(); + } + + @Override + public Object invokeMethod(@Nullable Object receiver, @Nullable String method, Object @Nullable... args) + throws ScriptException, NoSuchMethodException { + return engine.invokeMethod(receiver, method, args); + } + + @Override + public Object invokeFunction(@Nullable String method, Object @Nullable... args) + throws ScriptException, NoSuchMethodException { + return engine.invokeFunction(method, args); + } + + @Override + public T getInterface(@Nullable Class returnType) { + return engine.getInterface(returnType); + } + + @Override + public T getInterface(@Nullable Object receiver, @Nullable Class returnType) { + return engine.getInterface(receiver, returnType); + } +} diff --git a/bundles/org.openhab.automation.jrubyscripting/src/main/java/org/openhab/automation/jrubyscripting/internal/JRubyScriptEngineFactory.java b/bundles/org.openhab.automation.jrubyscripting/src/main/java/org/openhab/automation/jrubyscripting/internal/JRubyScriptEngineFactory.java index ad7b08497f3c8..c632afee6b8ff 100644 --- a/bundles/org.openhab.automation.jrubyscripting/src/main/java/org/openhab/automation/jrubyscripting/internal/JRubyScriptEngineFactory.java +++ b/bundles/org.openhab.automation.jrubyscripting/src/main/java/org/openhab/automation/jrubyscripting/internal/JRubyScriptEngineFactory.java @@ -160,7 +160,7 @@ private void importClassesToRuby(ScriptEngine scriptEngine, Map } ScriptEngine engine = factory.getScriptEngine(); configuration.configureRubyEnvironment(engine); - return engine; + return new JRubyEngineWrapper((org.jruby.embed.jsr223.JRubyEngine) engine); } @Override