Skip to content

Conversation

@dilyanpalauzov
Copy link

This is how it works:

openhab/openhab-docs#2567https://deploy-preview-2567--openhab-docs-preview.netlify.app/docs/configuration/jsr223.html#the-scriptloaded-and-scriptunloaded-functions shows how scriptLoaded() and scriptUnloaded() work in different OH JSR223 languages. In particular in Groovy the functions are not executed, if they are private, or if scriptUnloaded() is defined with at least one parameter, or if scriptLoaded() does not have exactly one parameter of type String. This is how now it is in Java223.

The other OH-JSR223 add-ons do not implement the loadScript/undloadScript functionality as helper library service, with the exception of JRuby (see .script_loaded - no parameter - and .script_unloaded at https://openhab.github.io/openhab-jruby/main/OpenHAB/Core/ScriptHandling.html ; and on_load and on_unload at https://openhab.github.io/openhab-jruby/main/OpenHAB/DSL/Rules/BuilderDSL.html) - which does not pass the filename parameter of scriptLoaded(). Implementing the functionality of scriptUnloaded() in a helper library should be easy using lifecycleTracker, so there is no special handling for this necessary here.

In any case, as there is no more a class ScriptUnloadedTrigger the system cannot complain anymore about "NoClassDefFoundError: java223/common/ScriptUnloadedTrigger" when the Java223 bundle is reloaded: https://community.openhab.org/t/java223-scripting-script-with-java-on-openhab-5-0-1-0-6-0-0-0/159853/25 .

So back to Groovy, which has a very thin layer in the openhab-addons, to integrate JSR223 with openHAB and the scriptLoaded(String) and scriptUnloaded() function calls just work out of the box. The other OH Automation JSR223 languages are similar - they are not much lines of code, handle invokeFunction() and do nothing special about it, yet still work.

In Groovy, if a file contains all logic between class { and }, then this is called a Groovy Class, otherwise it is a Groovy Script. Groovy Scripts are implicitly converted to Groovy Classes. The equivalent Groovy class for the example from https://deploy-preview-2567--openhab-docs-preview.netlify.app/docs/configuration/jsr223.html#the-scriptloaded-and-scriptunloaded-functions is:

import org.codehaus.groovy.runtime.InvokerHelper
class L extends Script {
  static logger = org.slf4j.LoggerFactory.getLogger("org.openhab.automation.example")
  void scriptLoaded(String filename) { // can be static
    logger.error("I am logged second and I am from ${filename}.")
  }
  static void scriptUnloaded() { // can be non-static
    logger.error("I am logged third.")
  }
  def run() {
    lifecycleTracker.addDisposeHook(new org.openhab.core.automation.module.script.LifecycleScriptExtensionProvider.Disposable() {
        public void dispose() {
           logger.error("I am logged fourth.")
        }
    })
    lifecycleTracker.addDisposeHook{ logger.error("I am logged fifth. Bye!") }
    logger.error("I am logged first.")
  }
  static void main(String[] args) {
    InvokerHelper.runScript(L, args)
  }
}

The above does indeed work as the Groovy example from https://deploy-preview-2567--openhab-docs-preview.netlify.app/docs/configuration/jsr223.html#the-scriptloaded-and-scriptunloaded-functions when put in a automation/jsr223/.groovy file. As Groovy and Java are very similar, I think the next excerpt from the example above should also work in Java, the modifications from the above were to add public on three places, add main, add semi-colons, and do not use formatted string in scriptLoaded, but append filename.

public class L {
  static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger("org.openhab.automation.example");
  public void scriptLoaded(String filename) { // can be static
    logger.error("I am logged second and I am from " + filename + ".");
  }
  public static void scriptUnloaded() { // can be non-static
    logger.error("I am logged third.");
  }
  //below line is required by Java223 - cannot execute: L doesn't have a method named eval/main/run,
  // or a RunScript annotated method
  public Object main() { return null; }
}

That works now in Java223!

After } catch (IllegalAccessException | InvocationTargetException e) { there is no need to log a message, when the scriptLoaded() or scriptUnloaded() function is private. The reason is that scriptLoaded() is called from org.openhab.core.automation.module.script.internal.ScriptEngineManager.scriptLoaded() and it does log the received exceptions. Likewise for scriptUnloaded() called from ScriptEngineManager.removeEngine(). Okay, the latter logs ScriptException.toMessage() instead of scriptException.getCause().toMessage(), but as nobody noticed a problem so far, this should be fine. These are the only two places in openhab-core, which call invokeFunction().

In any case, if I make the functions loadScript() or unloadScript() private openhab.log by default does say nothing.

In

public @Nullable Object invokeFunction(@Nullable String name, Object @Nullable... args)

the last @Nullable is wrong, args is always an array and cannot be null. But I cannot get the annotations working then.

@dilyanpalauzov dilyanpalauzov force-pushed the j223_rework_invokefunction branch from e7f1ea0 to 2303047 Compare October 12, 2025 08:45
@dalgwen
Copy link
Owner

dalgwen commented Oct 13, 2025

I think I remember making this functionality work using annotation because I thought it was better (more flexible).

But if other JSR223 JVM language all does this the "simple" way, then it's indeed better to do it like them.

@dalgwen dalgwen merged commit 7bfca9f into dalgwen:java223/main Oct 13, 2025
@dilyanpalauzov dilyanpalauzov deleted the j223_rework_invokefunction branch October 13, 2025 17:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants