Using Automation Scripts as a Datastore

August 23, 2022

Introduction

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.

Overview

There are two automation scripts that are used as part of this approach:

  • The 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 data itself is stored in another automation script referred to as the storage script.

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.

Script Options and Operations

Store Name

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.

HTTP

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.

Script

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);

Storage Path

The storage path specifies a unique key that identifies a value within the storage area defined by the store name.

HTTP

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.

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);

Creating

HTTP

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
}
};

Script

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
}

Reading

HTTP

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"
}

Script

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");

Updating

HTTP

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.

Script

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
}

Deleting

HTTP

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 rejects DELETE 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"
}

Script

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
}

Complete Script

The complete script can be downloaded from our GitHub repository https://github.com/sharptree/autoscript-library/tree/main/storage.

Example

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.

Conclusion

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]

In the time it took you to read this blog post...

You could have deployed Opqo, our game-changing mobile solution for Maximo.

Opqo is simple to acquire, simple to deploy and simple to use, with clear transparent monthly pricing that is flexible to your usage.