August 23, 2022
When developing solutions with automation scripts, there are situations where you need to store configuration or state information that is more complex than can be comfortably provided with simple Maximo properties, but you may not want to add a custom table because of the down time requirements or other restrictions.
In this post we will review a script that Sharptree developed to handle this case, providing a simple REST and script invocation API for creating, reading, updating and deleting (CRUD) complex data structures that are stored within a separate automation script. Not only does this approach provide a means to store complex configuration and state information, it also makes it portable by saving it as an automation script that can be viewed, copied and managed with standard Maximo tools.
There are two automation scripts that are used as part of this approach:
sharptree.storage
script contains the API for performing operations on the data. This is the script that we interact with to access and manipulate the stored data.The storage script stores the data as a JSON object, contained in single variable named config
. This JSON object contains properties that represent key-value pairs, with the key being a unique string and the value being another JSON object. Since the stored value must be a JSON object, this does mean that simple values such as numbers, strings, or booleans must be encapsulated in a JSON object and for those simple cases, a Maximo property may be a more appropriate solution.
While there is a default storage script, sharptree.storage.configuration
, additional storage scripts can be specified to separate storage areas. This allows for separation of storage function and improves storage management.
Note: All data operations on the storage script involve reading/writing the entire data structure stored within that script. This is reasonable and acceptable for a range of uses, but is an important consideration for use. You should evaluate the data size and usage patterns and choose the most appropriate approach for your use case.
In the following sections we will review the functions and options provided by the sharptree.storage
script, covering details for invoking these functions via HTTP and from another script.
The sharptree.storage
script stores the information in one or more storage scripts as a JSON object named config
. The store
parameter identifies the storage script to be used as the data store. By specifying different store names, it is possible to segregate configuration or state information into separate storage scripts.
As mentioned in the overview, if a store name is not provided, the default storage script will be used: sharptree.storage.configuration
. If a storage script does not exist, it will be created upon the first request for the store.
By default, the store
name is prefixed with sharptree.storage.
to easily group and identify storage scripts. However if the store
name contains a .
character a storage script will be created using the exact name provided with no prefix.
To specify a non-default store name using an HTTP request you can provide a value for the store
query parameter. For example, to use a script named sharptree.storage.mycustomstore
as the data store, the store
would provided as follows: https://maximo.acme.com/maximo/oslc/scripts/sharptree.storage?store=mycustomstore
.
When invoking the script as a library script, provide a value for the store
script context variable.
HashMap = Java.type("java.util.HashMap");var ctx = new HashMap();ctx.put("store", "mycustomstore");service.invokeScript("sharptree.storage", ctx);
The storage path specifies a unique key that identifies a value within the storage area defined by the store
name.
When using an HTTP request, the path following the sharptree.storage
script name is used to identify the storage path, for example the URL https://maximo.acme.com/maximo/oslc/scripts/sharptree.storage/first-example?store=mycustomstore
specifies a storage path of first-example
within the sharptree.storage.mycustomstore
storage script.
When invoking the script as a library script, provide the path
context variable.
HashMap = Java.type("java.util.HashMap");var ctx = new HashMap();ctx.put("store", "mycustomstore");ctx.put("path", "example");service.invokeScript("sharptree.storage", ctx);
To create a new value in a store using HTTP, perform a POST with the JSON content to store. The query parameter unique
may optionally be included to ensure the path is unique and not overwriting an existing value. If this is not specified, the POST will create the value if it does not exist or update it if it does.
As an example, performing a POST to https://maximo.acme.com/maximo/oslc/scripts/sharptree.storage/first-example?store=mycustomstore
with the following value:
{"value": "A stored value","example": true}
will add the value to the storage script named sharptree.storage.mycustomstore
at the first-example
storage path:
config = {"first-example": {"value": "A stored value","example": true}};
If the creation is successful, a JSON response is returned with a status
of success
.
{"status": "success"}
If an error is encountered, a JSON response is returned with a status
of error
, a message
describing the error condition and a reason
code. For example, if the unique
parameter was specified and the key first-example
already exists, the following error is returned.
{"status": "error","message": "The first-example configuration already exists and must be unique.","reason": "config_exists"}
Performing a second POST to https://maximo.acme.com/maximo/oslc/scripts/sharptree.storage/second-example?store=mycustomstore
with the following value:
{"value": "A second stored value","example": true}
will add the value to the storage script named sharptree.storage.mycustomstore
at the second-example
storage path. The sharptree.storage.mycustomstore
will now contain the following:
config = {"first-example": {"value": "A stored value","example": true},"second-example": {"value": "A second stored value","example": true}};
When invoking the script as a library script, provide the path
, store
, action
and content
as context variables. The action
variable is set to POST
, mirroring the HTTP request and the content is the stringified contents of the JSON object. The unique parameter is specified with the context variable mustBeUnique
set to true
.
The result can be read from the result
context variable.
HashMap = Java.type("java.util.HashMap");var content = { "value": "A stored value", "example": true };var ctx = new HashMap();ctx.put("store", "mycustomstore");ctx.put("path", "first-example");ctx.put("action", "POST");ctx.put("content", JSON.stringify(content));ctx.put("mustBeUnique", true);service.invokeScript("sharptree.storage", ctx);var result = ctx.get("result");if (result.status == "success") {// do something on success} else if (result.status == "error") {// do something on error}
To read a value from a store use an HTTP GET request with the storage path and optionally, the store query parameter. For example, to read the first value that was created in the previous section, we would perform an HTTP GET for the following URL https://maximo.acme.com/maximo/oslc/scripts/sharptree.storage/first-example?store=mycustomstore
, which will return the following value.
{"value": "A stored value","example": true}
If the requested storage path does not exist, a JSON response is returned with a status
of error
, a message
describing the error and a reason
code of missing_config
. In the example below a storage path of bad-key
was requested, which does not exist.
{"status": "error","message": "The key name bad-key does not exists in the configuration.","reason": "missing_config"}
When invoking the script as a library script, provide the path
, store
, and action
context variables, where the action
variable has a value of GET
to mirror the HTTP request. The result can then be read from the result
context variable.
HashMap = Java.type("java.util.HashMap");var ctx = new HashMap();ctx.put("store", "mycustomstore");ctx.put("path", "first-example");ctx.put("action", "GET");service.invokeScript("sharptree.storage", ctx);var result = ctx.get("result");
Performing an update is very similar to create, but uses the PUT
HTTP method.
As an example, performing a PUT to https://maximo.acme.com/maximo/oslc/scripts/sharptree.storage/first-example?store=mycustomstore
with the following value:
{"value": "Updated stored value","example": true}
will update the value in the storage script named sharptree.storage.mycustomstore
at the storage path of first-example
with the provided value, resulting in the following:
config = {"first-example": {"value": "Updated stored value","example": true},"second-example": {"value": "A second stored value","example": true}};
As before, if the update succeeds, a JSON response is returned with a status
of success
.
If an error is encountered, a JSON response is returned with a status
of error
, a message
describing the error condition and a consistent reason
property. For example, if an attempt to update non-existent path named bad-key
is made the following error is returned.
{"status": "error","message": "The configuration path bad-key does not exists in the configuration.","reason": "undefined_config"}
If the provided storage path does not exist an error will be returned.
When invoking the script as a library script, provide the path
, store
, action
and content
as context variables. In this example the action
variable is set to PUT
, mirroring the HTTP request and the content is the stringified contents of the JSON object. The result can then be read from the result
context variable.
HashMap = Java.type("java.util.HashMap");var content = { "value": "Updated stored value", "example": true };var ctx = new HashMap();ctx.put("store", "mycustomstore");ctx.put("path", "first-example");ctx.put("action", "PUT");ctx.put("content", JSON.stringify(content));service.invokeScript("sharptree.storage", ctx);var result = ctx.get("result");if (result.status == "success") {// do something on success} else if (result.status == "error") {// do something on error}
To delete a stored value perform a POST
HTTP request the with storage path, store and a JSON body with an _action
property set to delete
.
Note: The
DELETE
HTTP method was not used because the Maximo OSLC API explicitly rejectsDELETE
methods and the use of the_action
property aligns with the standard Maximo REST Object Structure operations.
For example, doing a DELETE to https://maximo.acme.com/maximo/oslc/scripts/sharptree.storage/first-example?store=mycustomstore
will delete the contents of the first-example
storage path in the storage script named sharptree.storage.mycustomstore
.
{"_action": "delete"}
As before, if the deletion succeeds, a JSON response is returned with a status
of success
.
If an error is encountered, a JSON response is returned with a status
of error
, a message
describing the error condition and a consistent reason
property. If an attempt to update non-existent storage path named bad-key
is made the following error is returned.
{"status": "error","message": "The configuration path bad-key does not exists in the configuration.","reason": "missing_config"}
When invoking the script as a library script, provide the path
, store
and action
as context variables. The action
variable is set to DELETE
. The result can then be read from the result
context variable.
HashMap = Java.type("java.util.HashMap");var ctx = new HashMap();ctx.put("store", "mycustomstore");ctx.put("path", "first-example");ctx.put("action", "DELETE");service.invokeScript("sharptree.storage", ctx);var result = ctx.get("result");if (result.status == "success") {// do something with the data on success} else if (result.status == "error") {// do something on error}
The complete script can be downloaded from our GitHub repository https://github.com/sharptree/autoscript-library/tree/main/storage.
The Sharptree Visual Studio Code extension, https://marketplace.visualstudio.com/items?itemName=sharptree.maximo-script-deploy , provides a real world example of the sharptree.storage
script in action.
In this case, it is used to store the last deployment information for each script in a storage scripted named SHARPTREE.AUTOSCRIPT.DEPLOY.HISTORY
. The snippet below shows an example of the storage script where the storage path is the name of the automation script that was deployed, and the value contains information about the most recent deployment of the script: Who deployed the script and when, along with a hash that can be used to compare the current contents to validate if it has been modified since it was deployed.
config = {"TEST1": {"deployed": 1658617124543,"deployedBy": "JASON","deployedAsDate": "2022-07-23T22:58:44.543Z","hash": "aec30febbefbf3f0b3f998ac658a14767124e3eb"},"TRANSTEST": {"deployed": 1658617145687,"deployedBy": "WILSON","deployedAsDate": "2022-07-23T22:59:05.688Z","hash": "f43d94afc08228241d70ece119da3cde53ffca64"},"WORKORDER.WEATHER.REPORT": {"deployed": 1660251815304,"deployedBy": "WILSON","deployedAsDate": "2022-08-11T21:03:35.304Z","hash": "c65a24540990e0698c89ff95126a9bd243b546b6"}};
In this case we wanted to capture and store this deployment information, but we did not want to add a table to the database, both for downtime considerations and to maintain as small of a footprint as possible. The approach of using the storage script fits perfectly for this, as it allows us to store relatively complex, but limited data in a very simple manner.
In this post we reviewed the sharptree.storage
library script for creating, reading, updating and deleting structured data that is stored in a separate automation script. This provides a convenient means for managing data that does not easily fit within a simple Maximo property structure without needing downtime to perform a database configuration.
If you have any questions or comments please reach out to us at [email protected]