November 29, 2021
The more Automation Scripts you write, the more you will find code repeating across those scripts. This can cause bits of similar code copied across many, many scripts. It also means that if a problem is found in this code, you must hunt through the system and find all the occurrences, which is an activity no one enjoys.
In this post we will see how functions from one Automation Script can be imported and used in another and how this allows for creating libraries for common functions. If you are unfamiliar with functions in Automation Scripts, check out our previous post here.
The basic pattern for invoking another script is quite simple: Using the implicit service
variable, call the invokeScript
function, passing in the target script name. In the example below, we are invoking an Automation Script named SHARPTREE.LIB
.
service.invokeScript("SHARPTREE.LIB");
When you invoke the script as we did above, the invokeScript
function creates a new context, puts the current mbo
implicit variable in that context, and then invokes the script. After invoking the script, the context is returned from the function and now contains the called script's functions and defined variables. The implicit variables however, are only available to the script as it is invoked and are not available to functions that are returned on the context. To access to the implicit variables within a function you must either assign the implicit variable to a local variable as part of the non-function execution or add them as arguments to the function.
Note that all code not within a function will be executed when
invokeScript
is called. For library scripts I recommend that you ensure all your code is encapsulated in functions.
We will start with a simple example with a basic function call and then move on to more complex scenarios with a custom context and variable handling.
For the first example we are going to assume we have an Automation Script called SHARPTREE.LIB
that has a function called add
that takes two values and returns their sum as shown below.
function add(value1, value2) {return value1 + value2;}
Knowing that invokeScript
returns the context with the script's functions, we can now call the add function as shown below.
var result = service.invokeScript("SHARPTREE.LIB").add(1, 2);
After the SHARPTREE.LIB
add
function is called, our variable result
will have a value of 3
.
In the previous example, the script will be invoked with only the mbo
implicit variable passed in, creating and returning a new context for the script execution. If you want to pass in other variables to the context, such as including the service
implicit variable you need to first create an execution context, then put the service
variable into context and finally invoke the script with the context. The context is a Java Map
type, so an import is necessary. Details on importing is covered in our previous blog here.
// import the Java HashMapHashMap = Java.type('java.util.HashMap');// create a new HashMap to use as the execution contextvar library = new HashMap();// put the service in the HashMaplibrary.put('service', service);// invoke the library with our contextservice.invokeScript("SHARPTREE.LIB", library);
This form of invokeScript
does not return a context, since one has been provided. As a matter of design, I would argue that it should for consistency, but IBM didn't ask me so it doesn't.
After the invoking the script, the script's functions are available on the provided context, so rewriting our previous example to call the add
function would look like the following.
// import the Java HashMapHashMap = Java.type('java.util.HashMap');// create a new HashMap to use as the execution contextvar library = new HashMap();// put the service in the HashMaplibrary.put('service', service);// invoke the library with our contextservice.invokeScript("SHARPTREE.LIB", library);// we can now invoke the desired function on our contextvar result = library.add(1, 2);
With the example above, our invoked script will now have access to the service
object during initial invocation, but if we want the service
object to be available for other functions we will need to assign it to a local variable.
For example if we want to use the log_info
function from the service
object to log every time our add
function is called we need to update our library script with the following.
var internalService = service;function add(value1, value2) {internalService.log_info("Add function called");return value1 + value2;}
Alternatively we can design our functions to require all the variables as parameters and not bother with the custom context, below is the add
function rewritten to take the service
object as a parameter.
function add(value1, value2, service) {service.log_info("Add function called");return value1 + value2;}
The calling function can then be simplified to the following.
var result = service.invokeScript("SHARPTREE.LIB").add(1, 2, service);
The two forms achieve the same functionality and choosing one over the other is a matter of personal preference. I prefer the clarity of declaring what a function requires with its parameters and also prefer the compact syntax for invoking the function.
As noted in the previous section, the invokeScript
function returns the context of the invoked script. This can be used directly as in the previous example, or can be captured in a local variable and then invoked multiple times or to call other functions on the script.
Using the previous library example that contains the add
function, let us now add a subtract
function.
function add(value1, value2, service) {service.log_info("Add function called");return value1 + value2;}function subtract(value1, value2, service) {service.log_info("Subtract function called");return value1 - value2;}
Our calling script can now be updated to capture the resulting context of calling invokeScript
and then call both the add
and subtract
functions, as well as call either multiple times as shown below. In this example the context returned by invokeScript
is assigned to mathLib
and then the add
and subtract
functions are called.
var sharptreeLib = service.invokeScript("SHARPTREE.LIB");var firstCall = sharptreeLib.add(1, 2, service);var secondCall = sharptreeLib.add(3, 4, service);var subtractionCall = sharptreeLib.subtract(4, 3, service);
Last week we looked at ensuring MboSet are closed properly, in case you didn't read it you can find it here. We ended up with the function below to close our MboSets.
MboSet = Java.type("psdi.mbo.MboSet");function close(mboSet) {// make sure the provided MboSet is defined and is an instance of psdi.mbo.MboSetif (typeof mboSet !== 'undefined' and mboSet instanceof MboSet) {try {// release any pending Maximo transactions.mboSet.cleanup();} catch(error) {// log the error, but there is nothing we can do to respond to it.service.log_error(error);}try {// remove any references and close the underlying JDBC ResultSet.mboSet.close();} catch(error) {// log the error, but there is nothing we can do to respond to it.service.log_error(error);}}}
As you can see, that is a lot to include in every script. However if we were to move this function into our SHARPTREE.LIB
script from before we can close our MboSet as shown below.
main();function main() {// define the MboSet outside the try/finally so it is available in the finally blockvar mboSet;try {mboSet = service.getMboSet("WORKORDER", userInfo);// perform operations with the MboSet.} finally {// close the MboSet using the function in our library scriptservice.invokeScript("SHARPTREE.LIB").close(mboSet);}}
If later we decided to extend the close
method to include other types like ResultSets
, Statements
, or even Connections
we can do so without having to update code in multiple scripts.
By extracting common functions to a library Automation Script we can simplify our other Automation Scripts while also making our code base easier to maintain. I have used simple examples here, but using this pattern you can compose complex and robust functionality in a manner that breaks down the functionality into manageable pieces.