How to get the ScriptManager inside Gateway Module Java

I am using one of the Perspective examples from GitHub (the Messenger) as a base for my code and the samplequickstart perspective project to learn from.

I have a reference to the GatewayContext and now I would like to get a reference to the Project ScriptManager.

I found on another thread this call

com.inductiveautomation.ignition.common.script.ScriptManager mgr = mygatewaycontext.getProjectManager().getProjectScriptManager(projectId);

My first problem is other than being of type long, I don’t know where to get the projectId from.

The documents I read from here,
ProjectManager (inductiveautomation.com)
show me after I get a ProjectManager as in,

ProjectManger pmgr = mygatewaycontext.getProjectManager() I should be able to get a Project from

Project p = pmgr.getProject(“samplequickstart”)
and then long id = p.getId();

But, the call to pmgr.getProject(“samplequickstart”) returns a type Optional not a Project.

Is the dependency library in the example project not the same version as the documents I found? Or is my lack of understanding making this far more difficult that it needs to be?

Thanks in advance.

Optional is a wrapping class used to avoid passing nulls around. You get the project from the Optional, if the project exists.

However, if you are trying to manipulate the jython environment that your project’s event scripts will use, you need to respond to the Gateway hook method to initialize a ScriptManager, which will be called separately for each enabled project in the gateway. You inject your jython definitions at that time, tailored to the project if necessary.

Getting a separate script manager instance is for your module to run its own internal jython, if any.

What user action (if any) is going to invoke your script/what are you actually trying to do with the ScriptManager? How/where does your user author their code, how are you retrieving it, and where does it get fired?

@pturmel I see, thanks about the Optional. I should have just googled “Optional java”

I was attempting to follow this thread for a starter in my learning path here. It’s a bit old (2013)

Executing Python from Module - 3rd Party Modules - Inductive Automation Forum

Does this line work to get a ScriptManager per project?
com.inductiveautomation.ignition.common.script.ScriptManager mgr = ctx.getProjectManager().getProjectScriptManager(projectId)

@PGriffith I’m trying to call pre-defined scripts from the Project Library. I am trying to call them from the handleEvent(…) method in the MobileComponentModelDelegate. The trigger (firing) is from the React component button that sends a message in the example code.

I wouldn't try to hardcode a script name. That's an antipattern - what if you want to change the name of the script that's invoked; you need to install a new module to do so?
Instead, I would look into the extension function mechanism. You can provide an extension function with your component, invoke it when necessary from your code, and allow the end user in the designer to author whatever code they feel like.

There's some discussion on a similar problem in this thread, including some of the code required to invoke an extension function in Perspective; I'm not sure if our example module provides any samples of that:

Nope im pretty sure it doesnt,

You should check out this topic too, when i ran into some troubles on 8.1.12+
There are some screens of code in there of both versions

1 Like

Ok, thanks @PGriffith ,

I can see how to find and invoke an extension function. Can I create my own extension function for my component? I think that would fit the solution best.

How do I do this, in java code, or in the designer? Can you please point me in the direction of some documentation? Or maybe examples.

Provide ExtensionFunctionDescriptors in your ComponentDescriptor when it’s registered, and the UI will automatically have them available. Then you use the APIs mentioned in the other threads linked above to retrieve the extension function (which may or may not be present) and invoke it accordingly.

Here some exmaples of what i did:

create a json file simalar to like for the props:

{
  "extensionFunctions": [
    {
      "name": "onRowStartEdit",
      "description": "Fired when rows in the table are being triggered to edited, if not enabled the default grids update popup will show up which will trigger an event onRowUpdated \nIf you return True the default popup will also show (example to first filter on user role)",

      "default": "\tsystem.perspective.sendMessage(messageType \u003d\"handleButtonEdit\", payload\u003d{\"data\":[x.data for x in rows]})\n\treturn False",
      "arguments":[{
          "name": "rows",
          "description": "Array of data of the selected rows each row has id (index of the selected row) and data (rows[0].data)"
        },
        {
          "name": "columns",
          "description": "The columns in order which are editable"
        }]
    }
  ]
}

then add it to the schema (this is probably not the most effective way of adding it but it realy doesnt matter and it works xd

public static final JsonObject EXTENSIONS_OBJECT = (new JsonParser()).parse(new InputStreamReader(GridComponents.class.getResourceAsStream("/grid.extensions.json"), StandardCharsets.UTF_8)).getAsJsonObject();
    public static final List EXTENSIONS;
    static {
      EXTENSIONS = new ArrayList();
      JsonArray jsonExt = EXTENSIONS_OBJECT.get("extensionFunctions").getAsJsonArray();
      jsonExt.forEach(eventElem -> {
        JsonObject eventDef = eventElem.getAsJsonObject();
        List<ExtensionFunctionDescriptor.ExtensionFunctionArgument> Arguments = new ArrayList<ExtensionFunctionDescriptor.ExtensionFunctionArgument>();
        JsonArray args = eventDef.get("arguments").getAsJsonArray();
        args.forEach(argsElem -> {
          JsonObject argDef = argsElem.getAsJsonObject();
          Arguments.add(new ExtensionFunctionDescriptor.ExtensionFunctionArgument(argDef.get("name").getAsString(), argDef.get("description").getAsString()));
        });
        EXTENSIONS.add(new ExtensionFunctionDescriptor(eventDef.get("name").getAsString(), eventDef.get("description").getAsString(), eventDef.get("default").getAsString(), Arguments ));
      });
    }

    public static ComponentDescriptor DESCRIPTOR = ComponentDescriptorImpl.ComponentBuilder.newBuilder()
        .setPaletteCategory(GridComponents.COMPONENT_CATEGORY)
        .setId(COMPONENT_ID)
        .setModuleId(GridComponents.MODULE_ID)
        .setSchema(SCHEMA) 
        .setName("Grid")
        .addPaletteEntry("", "Grid", "An Grid component.", null, null)
        .setDefaultMetaName("grid")
        .setEvents(EVENTS)
        .setExtensionFunctions(EXTENSIONS)
        .setResources(GridComponents.BROWSER_RESOURCES)
        .build();

Than add it to the descriptor
.setExtensionFunctions(EXTENSIONS)

You should be able to create each ExtensionFunctionDescriptor from a Gson instance, something like:

            JsonArray extFnArray = json.getAsJsonArray("extensionFunctions");
            List<ExtensionFunctionDescriptor> extFns = Lists.newArrayList(extFnArray).stream()
                .map(element -> GSON.fromJson(element, ExtensionFunctionDescriptor.class))
                .collect(Collectors.toList());

Or you can use the TypeToken class to reify a list type parameter (which would otherwise be erased) and use that during deserialization of the entire JsonArray:

    public static final Type TYPE_TOKEN = new TypeToken<List<PerspectiveProjectData>>() {}.getType();

Or if you switch to Kotlin, the gentleman's JVM language :wink: you could use a reified function more directly:

/**
 * Deserialize [element] as [T]. Constructs a [TypeToken] automatically to capture reified type parameter.
 */
@Suppress("UnstableApiUsage")
inline fun <reified T : Any> JsonDeserializationContext.deserialize(element: JsonElement): T {
    return deserialize(element, object : TypeToken<T>() {}.type)
}

Yeah… it somehow didnt work like the events, might have been the json schema itself i forgot tbh.

There was no example of the extension functions so yeah,
Im happy that it worked so i didnt touch it anymore xD Performance wise this bit of code doesnt matter anyways, and even then its not like im doing something extremely weird (for once).

i dont really have an free moment to refactor xd