OH 3 Build #2006: Accessing Actions changed, do not work when not triggered by Item

I’ve been exploring creating JavaScript rules through MainUI and I’ve run into something that appears to have changed. I can’t say how recently it changed, in the past couple of weeks at least.

Problem:
Before now I was able to access openHAB actions using something like:

var Exec = Java.type("org.openhab.core.model.script.actions.Exec");
var Duration = Java.type("java.time.Duration");
var results = Exec.executeCommandLine(Duration.ofSeconds(1), "echo", "hello");

This now fails on the Java.type line. When I run the rule triggered by an Item I get an error like

2020-11-12 09:14:55.695 [ERROR] [e.automation.internal.RuleEngineImpl] - Failed to execute rule 'experiment': Fail to execute action

: 1

When I trigger the rule by pressing the play button I get the following on the same line:

2020-11-12 09:14:46.843 [WARN ] [e.automation.internal.RuleEngineImpl] - Fail to execute action: 1
java.lang.RuntimeException: java.lang.ClassNotFoundException: org.openhab.core.model.script.actions.Log cannot be found by org.apache.aries.jax.rs.whiteboard_1.0.9
        at jdk.nashorn.internal.runtime.ScriptRuntime.apply(ScriptRuntime.java:531) ~[jdk.scripting.nashorn:?]
        at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:456) ~[jdk.scripting.nashorn:?]

This used to work.

As I’ve experimented to figure out what is going on I did find that the following works, but only when the rule is triggered by an Item:

var Log = this.Log;
Log.logError("Experiments", "This is an OH error log");

When the rule is triggered manually it complains that Log is undefined. When the rule is triggered from another rule, even when that other rule was triggered by an Item Log is undefined.

@ysc, should this be working or is the Docker builds behind am I’m running a version of OH 3 prior to the change you recently made that should have put the Action into the scope when triggered manually. I’m running Build #2006.

I wanted to open this on the forum to see if others are seeing the same and make sure I’m not doing something wrong before opening an issue.

Update:

It looks like what I’m seeing is a little different than expected.

When a rule is first executed after it is first loaded/updated it gets a context. That context is reused for each subsequent run of the rule. If the rule is first run manually, the context is missing that magical special sauce that allows access to the Actions. So even if later on the Rule is triggered by an Item it won’t have access to the Actions.

However, if the rule is first triggered using an Item event the full context is received. This allows the rule access to the Actions even if it is later run manually by pressing the play button.

So the root problem is how the rule is triggered that very first time.

This is what the change I proposed (https://github.com/openhab/openhab-core/pull/1799) is supposed to solve - forcing the Nashorn script engine to use the OSGi class loader every time instead of the current thread’s (the default). In that case when the rule is run from the UI through the API it happens to be org.apache.aries.jax.rs.whiteboard_1.0.9 which has apparently no access to these action classes.

It’s still open though, so it isn’t in any build yet.

I don’t know if it’ll solve the other cases you describe, but it’s not unlikely.

Thanks, I thought the change was merged. I got lost in all the activity.

That assumption threw me off a bit but now that I know it’s not there everything makes sense.

The context for the rule gets created the first time it’s run and reused on each subsequent run. So if the first time the rule is run is through the play button, because you change isn’t merged yet, the Actions are unavailable.

If the first time it’s run is through an Item event, the Actions are available. And since the context is reused, the Actions will be available thereafter for that rule no matter how it’s invoked.

I also see that the context gets passed to Script Actions. So, for example, I created a Script (your new rule with only a Script Action) with a bunch of code that tests a library I’m working on. If I just hit play after editing it complains that ScriptExecution doesn’t exist. However, if I use “run a rule” as the Action from a rule that was triggered by an Item, everything works. And I can later come and click play and it will still work.

Right, I had another look and what you call “context” is probably the instance of the ScriptEngine that gets created with its associated ScriptEngineFactory and then kept loaded inside a “ScriptEngineContainer”:

This way it doesn’t have to be recreated every time the rule is run.
The problem with this, of course, is that it has to be created correctly the first time.

I didn’t check but I’ll suppose if a script engine associated with a given script module was created following a manual run of the rule with the Run button (i.e. the API), and has therefore the wrong class loader that fails to load the classes, all subsequent runs of that same script will still fail - even if the script is run because the rule has been triggered - because the loaded script engine instance will simply be reused. So that’s pretty bad - the script engines must be created in a deterministic way.

I can confirm this and it’s the main source for why I’ve been chasing my tail the last few days. If we can have everything we need to get to the Actions no matter how the rule is triggered, which should be the case with your update, than the problem should go away. It will also greatly improve the write/test/update loop when creating new rules in a lot of cases.

And I don’t want to create a new one for every run of the rule, beyond the inefficiency, as that will wipe out the ability to store variables from one run of the rule to the next which almost completely wipes out the utility of Timers, among other things. IIRC, this also impacts the other languages too.

BTW, I’m really loving the Scripts organization for Script Actions. I know they are just rules without triggers or conditions, but having them separated makes building examples and tests a whole lot easier. It will also be nice to centralize some functions without needing to resort to .js files all the time.