Updating End Points and Reloading Cache in Automation Script

In Maximo you can use End Points to point to External APIs to pull in data. You can invoke those End Points in a variety of ways, including inside an Automation Script. Take the following code as an example:

handler = Router.getHandler('ENDPOINTNAME');
responseBytes = handler.invoke(null, null);

You can then parse the response that comes back and use that data in any way that meets your needs. Recently we were working with an external API that required an initial login to capture an API Key to be used with future calls. We called the Login API and then grabbed an API Key from the response object. We then needed to update a different End Point to utilize this API Key as an HTTP Header in future calls.

This appeared to work in Maximo as we would see the data show up on the updated End Point. However, each time we invoked the End Point it would throw an error that it was not able to connect. We would then restart Maximo and everything would start working. What we found is that End Point data in Maximo is cached. After updating an End Point, we needed to reload the End Point cache to make a successful connection. To do that we added a line in the automation script after we updated and saved the End Point with the new API Key. The line that is needed is:

MXServer.getMXServer().reloadMaximoCache(EndPointCache.getInstance().getName(), true);

After running this code we can now successfully call our updated End Point. Be aware that there are several similar objects that are cached when Maximo starts up, such as Relationships, Integration information, some Domain information, etc. If updates are made to those records, you may need to refresh the Maximo cache for those records as well.

Hope this helps!

Maximo JSON API: An IoT Example

I’ve had a lot of conversations recently with folks attempting to implement more Condition Monitoring within their organization. The benefits of shifting from time-based maintenance to condition- or use-based maintenance are well documented and very real. However, making that shift involves a fair amount of planning, analysis, and technology. This article will show an example of how to bridge a small portion of the technology gap; specifically we will focus on creating meter readings in Maximo via the JSON API available in Maximo 7.6.0.2 and higher.

Meter readings are the heart of how Condition Monitoring is implemented in Maximo. They represent a piece of data at a point in time associated with an asset. This data could be related to the asset’s condition such as temperature, pressure, voltage, etc. This data could also be related to the asset’s usage such as an odometer reading, the number of cycles of the asset, etc. Lastly, the data could be based on a simple value list such as Pass/Fail, Open/Closed, Blowing / Not Blowing, etc.

To create a new meter reading via Maximo’s JSON API, let’s start with an example:

POST http://mxapprove.a3jgroup.com/maximo/oslc/os/mxasset/_QkVERk9SRC8xMTQ1MA==?lean=1
maxauth: c21pdGg6c21pdGgx
Content-Type: application/json
properties: *
x-method-override: PATCH
patchtype: MERGE
{
    "assetmeter": [
        {
            "metername": "O-PRESSUR",
            "linearassetmeterid": 0,
            "newreading": "4900"
        }
    ]
}

Let’s look at this part-by-part. First, we start with the HTTP POST itself.

POST /maximo/oslc/os/mxasset/_QkVERk9SRC8xMTQ1MA==?lean=1

This message needs to be an HTTP POST (not HTTP GET). The http://mxapprove.a3jgroup.com/maximo portion of the URL is your Maximo environment’s URL. Substitute that for your own environment’s URL. The /oslc part of the path represents the usage Maximo JSON API. The /os part of the path tells the API that the next part of the path (in our example is /mxasset) will be an Object Structure in Maximo. Finally, the /_QkVERk9SRC8xMTQ1MA== represents a unique identifier for an Asset record that can be referenced in the MXASSET Object Structure. This identifier can be found be querying for the asset using an HTTP GET to the same MXASSET Object Structure, or it can be derived by base64 encoding the asset’s SITEID + “/” + ASSETNUM and prefixing that string with an underscore (“_”) character. The ?lean=1 part of the path allows us to not have to specify namespace prefixes in the body of the request.

Next are the various headers.

maxauth: c21pdGg6c21pdGgx
Content-Type: application/json
properties: *
x-method-override: PATCH
patchtype: MERGE

The maxauth header represents the credentials that are being used to broker the transaction. The value is a base64 encoded string of the format USERNAME + “:” PASSWORD. In an LDAP environment, switch the maxauth header to Authorization and prefix the string “Basic ” to the base64 encoded value. The Content-Type header tells the HTTP request that the body of the message will be in JSON format. The properties header tells the request which fields from the MXASSET Object Structure should be sent back in the HTTP response, with the * character representing all fields in the object structure. The x-method-override header with a value of PATCH tells Maximo that this will be an update to the asset record, and the patchtype header with a value of MERGE tells Maximo to add a new record while keeping the other ASSETMETER records. Without that header, the integration will replace all of the ASSETMETER records with the list in the message.

Next is the body of the message.

{
    "assetmeter": [
        {
            "metername": "O-PRESSUR",
            "linearassetmeterid": 0,
            "newreading": "4900"
        }
    ]
}

Note that we don’t need any identifying information about the asset in the message body, such as the ASSETNUM or SITEID. This is because we are required to specify the unique identifier in the URL string. We specify the assetmeter key as an array of meters associated with the asset. For this example, we are only updating a single meter against the asset (Outlet Pressure). In our object, we specify the Meter Name and New Reading value. The linearassetmeterid key with a value of 0 is necessary due to that field being part of the unique identifier on the ASSETMETER table. Values such as the New Reading Date and Inspector will default based on the current date and the logged in user credentials, but can also be specified explicitly in the message.

Please feel free to leave questions or comments below. Good luck in your IoT journey!

MBOs in Automation Scripts: Adding New Objects to the Collection

In our previous posts in this series, we talked about how an MboSet is a collection of Mbo objects. This is analogous to a spreadsheet of data representing an MboSet, and a single row within that spreadsheet representing an Mbo. This article will discuss how to update single attributes on an Mbo, or in our spreadsheet analogy, update data within a single cell within a row.

If there is a need to add a new object to a collection, the add() method may be on an MboSet, as in the following example:

newWo = woSet.add()

This returns a reference to the newly added object and sets the current index in the collection to point to it. The location of the new object within the collection is not guaranteed, so no assumptions should be made as to where it has been placed relative to the previous current index. You can use the getString() method to fetch default or autokey values for particular attributes. One of the best examples of an autokey value is the “wonum” attribute, which must be unique as the principal identifier of a work order. These values are set automatically by Maximo as part of the add() method.

Once a new object has been created, the attributes of the object can be altered by using the setValue() method. This method call can be performed directly on the newly added object:

newWo.setValue("assetnum", "11430")

As mentioned previously, changing an attribute value may result in other attribute values being updated.

Once all values have been set, the save() method should then be called:

woSet.save()

The save() method may throw an exception, as was discussed in the section on modifying objects. The most common exception thrown by the saving of a newly added object results from a required attribute not being set. The exception returned in this case is of type MXApplicationException with an error group and key of “system” and “null”, respectively.

Our next article will focus on exception handling with Mbos and MboSets.

MBOs in Automation Scripts: Update Attributes of an Mbo

In our previous posts in this series, we talked about how an MboSet is a collection of Mbo objects. This is analogous to a spreadsheet of data representing an MboSet, and a single row within that spreadsheet representing an Mbo. This article will discuss how to update single attributes on an Mbo, or in our spreadsheet analogy, update data within a single cell within a row.

To modify an object in the collection, the object must first be found. Most of the time, the launch points in automation scripts will supply an implicit variable called mbo that refers to the current record. Alternatively, you can get a handle to an Mbo from an MboSet as described in previous articles of this series – remember the setWhere() and moveFirst() methods. Once you have an Mbo, changes are made using the setValue() method. As is the case with other methods, setValue() is a member of both the Mbo and MboSet classes.

Our first example changes the description of the current Mbo (a work order) by calling the mbo variable’s version of the setValue() method.

mbo.setValue("DESCRIPTION", "Changed the description")

In the setValue() method, passing the second argument as a string works for all attributes, regardless of their underlying data types. For example, if an attribute is a numeric type, passing the string “1” is allowed. There are also setValue() method calls that take the long, int, Date, boolean, float, or double data types as the second argument.

This call will throw an exception if the underlying data type does not match the type of the value passed in the call. For example the following code does not work.

mbo.setValue("description", 1.0)

A call to set the value of a particular attribute may result in the values of other attributes changing. For example, setting the “assetnum” attribute of a work order may result in the “location” attribute being updated as well. To check the new value, getString() could be used on the “location” attribute.

Once all values have been modified, the save() method can then be called.

mbo.getThisMboSet().save()

This saves and commits all changes to the database. In most cases, an explicit call to save() is not necessary. In cases where the automation script fires before the save event, the save event will trigger automatically after the execution of the script.

If there is an error during the save, an exception is thrown and the changes are not saved. In that case, the collection is left in exactly the same state as prior to the save() call. This allows appropriate action to be taken and the save() call to be resubmitted.

There is a third argument that can be passed to the setValue() method, which is the Access Modifier. This should only be used when you are certain of the implications of using it. In essence, standard application logic can be bypassed by using access modifiers, which is why they should be used only when necessary. Standard access modifiers are:

  1. NOACCESSCHECK: Suppresses the “Field is Read Only” message and performs the update regardless of whether the field is editable or not.
  2. NOVALIDATION: Update the field without validating the data being entered.
  3. NOACTION: Do not perform related system actions when updating. For example, do not update the location on a work order when updating the asset.

These access modifiers can be used on an update by themselves or combined together. For example, to update a field even though Maximo has the field marked as read only:

mbo.setValue("DESCRIPTION", "Update a read only field", mbo.NOACCESSCHECK)

To update a field and not perform the validation and action events:

mbo.setValue("DESCRIPTION", "Update a field without validation or action", mbo.NOVALIDATION|mbo.NOACTION)

Putting all three together:

mbo.setValue("DESCRIPTION", "Please be careful doing this", mbo.NOACCESSCHECK|mbo.NOVALIDATION|mbo.NOACTION)

A list of attributes whose values may be modified and the objects to which they belong can be found in the MAXATTRIBUTE table in the MAXIMO database. The OBJECTNAME column of this table corresponds to the object name. The ATTRIBUTENAME column corresponds to the attribute name.

Our next article will focus on how to add new Mbo records into an MboSet.

Auto-Generating DBC Files

In the ongoing effort to use cool and interesting tools for application and database updating it has been very nice to see the geninsertdbc.bat tool put into practical use. Harnessing these tools to create DBC files can be very advantageous if you are still getting all the available commands and syntax down to master them. So, let’s look at a scenario where I needed to update a Maximo automation script.

The required solution is to update a pre-existing automation script with new code for the source field in the autoscript table as well as update the scriptlanguage field. The caveat is that the script language (python) requires proper formatting to execute correctly. In the first attempt I tried to update the automation script via a freeform insert in the DBC’s statements section using SQL line breaks and indentation syntax.

 

While this did achieve the outward appearance that I wanted Maximo was not “sold” on the line spacing and indentation of the ( CHAR(13) or CHAR(9) ) SQL syntax and the automation script would not run after the server(s) were restarted.

Now, to get to the fun part. An alternate method was suggested on how to get this information into Maximo via the geninsertdbc.bat tool. IBM has tools available that can auto generate DBC files based on parameters that you give the output tool at the command prompt. I reset the automation script’s source code with the python source code that was previously working, and I opened an administrative command prompt then navigated to the ..\maximo\tools\maximo\internal directory under the Maximo installation folders.

I ran the following command: C:\IBM\SMP\maximo\tools\maximo\internal>geninsertdbc.bat -tautoscript -w"autoscript = 'ITEMDELETION'" -ftest2 -ca3jgroup

To break this down lets look at the parameters given for the command.

-t: will pull from the table

-w: will specify a where clause

-f: will name the output file

-c: will point the file to a directory of your choosing

 

After the geninsertdbc.bat tool is run there will be an output file created which will look similar to this.

In this file we have the column information extracted from the database and formatted in the XML layout of a DBC file ready to be either edited or re-added to Maximo via the runscriptfile.bat command. What I was able to use from this file was the character string that the geninsertdbc.bat placed around the source string (
) which is the character string that allows for spacing and line breaks while inserting into databases via the DBC file method.

I then took this info and created a DBC file that would update as opposed to insert into the database. The source COLUMNVALUE took some effort to get the correct spacing and breaks in place, but when you run the script and restart the server(s) your automation script will run without any further efforts.

This really is just a scratch of the surface of what you can do with auto generating DBC files. Further uses can only truly be explored/acknowledged by trying this method on other tables to see the results that may come from it and applying the built-in functionality detailed in files like the script.dtd file for manipulating the data to be added/changed.

An outstanding source for learning about DBC files is located on this IBM Developer Works page. I encourage anyone interested in this to go through this content and then try to apply it to your day to day tasks.

MBOs in Automation Scripts: Reading Multiple Attributes of an MboSet

In our previous posts in this series, we talked about how an MboSet is a collection of Mbo objects. This is analogous to a spreadsheet of data representing an MboSet, and a single row within that spreadsheet representing an Mbo. This article will discuss how to read multiple attributes from an Mbo, or in our spreadsheet analogy, look at data within multiple cells within a single row.

Because most of the objects in Maximo possess many attributes, a long series of getString() method calls might be required to retrieve all the necessary information in a given situation. To make such retrieval more convenient, a method called getMboValueData() has been provided. It allows the programmer to fetch many data items in a single operation by specifying a list of attributes and, if desired, a set of rows in the collection. The result of such a call is a one- or two-dimensional array of MboValueData objects. Another advantage of this approach is that it speeds up processing over a network. When a number of data items are retrieved in a single call over a network rather than many, the efficiency advantages are self-evident.

The getMboValueData() method is available in several versions; as with many other methods it is a member of both the MboSet and the Mbo classes. The general syntax of the most-used version is as follows:

valueDataList = mbo.getThisMboSet().getMboValueData(attributes[])

or

valueDataList = mbo.getMboValueData(attributes[])

The attributes variable above is an array of strings containing a list of attribute names (e.g. “wonum”, “description”, “assetnum”, or “asset.description”). The “asset.description” attribute is an example of “dot notation” using Maximo relationships (more on relationships in a subsequent article). Dot notation is a shortcut that allows you to retrieve data from a related object without accessing the related object itself. For example, if a Work Order object has a value entered in its “assetnum” attribute, passing “asset.description” to either getString() or getMboValueData() will fetch the specified Asset’s description. Thus, by combining dot notation with getMboValueData(), data can be simultaneously retrieved in one method call even from objects that are in different Maximo Business Objects. This is an important part of the convenience that using getMboValueData() confers.

The return from the getMboValueData() method is an array of MboValueData objects. Each one contains the value of the attribute specified in the corresponding position in the attributes parameter. There are several methods which allow retrieval of this data value; the most often used, getData(), returns the data as a string. Not only can the data value be recovered; there are also methods which return information about the attribute itself, such as its maximum length, whether it is read-only, etc. This additional information is very useful in many situations.

A code sample follows:

attributeList = [ "wonum", "description", "assetnum", "asset.description" ]
// Assume the mbo is a Work Order and that the Work Order object has an Asset associated with it.
valueData = mbo.getMboValueData(attributeList);
print "The description of Work Order " + valueData[0].getData() + " is " + valueData[1].getData()
print "The Asset associated with Work Order " + valueData[0].getData() + " has identifier " + valueData[2].getData() + " and description " + valueData[3].getData()

If any of the attributes specified in the array of attribute names (attributeList in the above example) does not exist, a null value is placed in the corresponding position of the returned array. Similarly, if any of the dot notation entries in the list refer to nonexistent related objects, nulls are returned in those positions as well. In the above example, if the mbo variable has no value specified in its “assetnum” attribute, then null would be returned in the position where “asset.description” is entered in attributeList.

There are two other versions of the getMboValueData() method. One is a special case of the version described above. The syntax is:

valueData = mbo.getThisMboSet().getMboValueData(attribute)

or

valueData = mbo.getMboValueData(attribute)

The argument is a string which is a single attribute name and the return is a single MboValueData object. As above, this method is a member of both the MboSet and Mbo classes. Even though it returns less data than the more general versions, it may still be preferable in certain situations. For example, if the desired result is information about a single attribute (e.g. read-only, length, etc.) of some object, then it is sufficient to apply this method to that single attribute rather than many.

The remaining version is more general in nature. This is a member of only the MboSet class and allows the retrieval of data not only for multiple attributes but for multiple objects as well. It is used when many objects are to be processed at once, such as during the generation of a report or the filling of a table. The syntax is:

valueDataList = mbo.getThisMboSet().getMboValueData(firstRow, rowCount, attributes[])

The firstRow and rowCount variables are integer arguments; they specify the first row and the number of rows in the row set to be retrieved, respectively. The most commonly used values are 0 and mbo.getThisMboSet().count(), which fetch data for the entire collection.

The attributes variable is an array of strings containing a list of attribute names, as above. The return is a two-dimensional array of MboValueData objects. A code sample follows.

attributeList[] = [ "wonum", "description", "statusdate" ]
// Assume the woSet collection is non-empty
woCount = mbo.getThisMboSet().count()
valueData = mbo.getThisMboSet().getMboValueData(0, woCount, attributeList)
for i in range(len(valueData)):
    print "The description of Work Order " + valueData[i][0].getData() + " is " + valueData[i][1].getData()
    print "The Status Date of Work Order " + valueData[i][0].getData() + " is " + valueData[i][2].getData()
}

That’s it for now. Our next article will focus on how to modify attribute values of an Mbo in an MboSet.

MBOs in Automation Scripts: Reading Single Attributes of an MboSet

In our last post in this series, we talked about how an MboSet is a collection of Mbo objects. This is analogous to a spreadsheet of data representing an MboSet, and a single row within that spreadsheet representing an Mbo. This article will discuss how to read single attributes from an Mbo, or in our spreadsheet analogy, look at data within a single cell within a row.

Once an MboSet has been fetched, data associated with the individual objects it contains can be extracted. Attributes that are associated with character data types, such as ALN, UPPER, and LOWER, can be retrieved by using the getString() method.

The getString() method is a member of both the Mbo and MboSet classes. The Mbo version fetches data from the Mbo object itself; the MboSet version fetches data from the current member of the MboSet.

For example, here moveFirst() is called to set the current member pointer to the first object in the woSet collection. Since woSet was newly restricted from a new setWhere() call, moveFirst() also triggers the retrieval of the record from the server. The getString() method of the woSet object is called and the value of the description attribute of the current member is retrieved.

woSet = mbo.getThisMboSet()
woSet.setWhere("wonum='1100'")
woSet.moveFirst()
woDesc = woSet.getString("DESCRIPTION")

In the next example the moveFirst() call, in addition to the above functions, also returns the work order object which is the new current member of woSet. The getString() method is called through the object wo and the value of the description attribute is retrieved.

woSet = mbo.getThisMboSet()
woSet.setWhere("wonum='1100'")
wo = woSet.moveFirst()
woDesc = wo.getString("DESCRIPTION")

Next, moveFirst() is called to make the first object the current member. A reference to that member is then retrieved by a getMbo() call. The getString() method is then called as before.

woSet = mbo.getThisMboSet()
woSet.setWhere("wonum='1100'")
woSet.moveFirst()
wo = woSet.getMbo()
woDesc = wo.getString("DESCRIPTION")

Note that the result is the same in each of these three examples. Which combination of calls should be used to reach the result depends entirely on what is most convenient in a given situation.

The getString() method may be used on all attributes regardless of their data types.

There are also getDate(), getInt(), getLong(), getByte(), getBoolean(), getFloat() and getDouble() methods, which return the value of an attribute as something other than a string. An attempt to use these methods on an underlying attribute not of the correct type results in an exception being thrown. For example the following does not work because the attribute is not of type DATE or DATETIME.

woDate = woSet.getDate("DESCRIPTION")

A list of the attributes whose values may be retrieved and the objects to which they belong is listed in the MAXATTRIBUTE table in the Maximo database. The OBJECTNAME column of this table corresponds to the table name, while the ATTRIBUTENAME column corresponds to the column name.

For a complete description of all methods available, refer to the Mbo and MboSet JavaDocs on the IBM Asset Management Developer Center.

Our next article will focus on reading multiple attributes from the current record in an MboSet.

Call Maximo Automation Scripts from JSON API

It was not until I read chapter 14 of IBM’s overview of the Maximo JSON API that it occurred to me that we could use the JSON API to launch an automation script in Maximo and then see the results in the return message. We can create our own APIs using the relative simplicity of an automation script and not have to write a single line of Java code. Truly powerful stuff!

The example provided in the article was also useful to me in that I was going through the process of loading data into Maximo when I came across the article. The script used in the article queries Maximo objects and reports back a record count. In my situation this was a very poignant and timely revelation.

Creating the Script

The first step in this process is to create an automation script that we want Maximo to run. I took the script provided in the example and expanded the list of objects to suit my situation. Besides the additional objects I wanted a total count of records that were loaded which is the purpose of the “Get Total Count” section at the end of the script.

Source Code:

importPackage(Packages.psdi.server);
// Create the response object
var resp = {};
// Get the Site ID from the Query Parameters
var site = request.getQueryParam("site");
// Count of Work Orders
var woset = MXServer.getMXServer().getMboSet("WORKORDER", request.getUserInfo());
woset.setQbe("SITEID","="+site);
var woCount = woset.count();
resp.wo_count = woCount;
woset.close();
// Count of Service Requests
var srset = MXServer.getMXServer().getMboSet("SR", request.getUserInfo());
srset.setQbe("siteid","="+site);
var srCount = srset.count();
resp.sr_count = srCount;
srset.close();
// Count of Items
var itmset = MXServer.getMXServer().getMboSet("ITEM", request.getUserInfo());
var itmCount = itmset.count();
resp.item_count = itmCount;
itmset.close();
// Get Total Count
resp.total = woCount+srCount+itmCount;
var responseBody = JSON.stringify(resp);

Navigate to the Automation Script application and select Create and then choose Script.


Since we are going to be launching this script from an HTTP call we just need to create the script and not provide a separate launch point.

Fill out the script Name, Description, Language and the Source Code from above.

Make sure the script is Active and select Create to save the script.

Executing the Script

We need three pieces of information to complete a successful JSON API call:

  • Site ID
    • In this example we are using the demonstration data, so our site is BEDFORD.
  • Username and Password
    • User Name: maxadmin
    • Password: maxadmin
  • URL
    • http://maximo_host/maximo/oslc/script/a3j_recordcount?_lid=maxadmin&_lpwd=maxadmin&site=BEDFORD
    • Note that there are underscores in front of lid and lpwd which might now be obvious from the link formatting above.

Script Results

{
"wo_count": 1331,
"sr_count": 41,
"item_count": 357,
"total": 1729
}

Import SSL Certificates into WebSphere Trust Store

Ever tried to connect your Maximo system with an external secured URL? By default, WebSphere is designed not to trust secured external URLs. It will only allow the connection if an administrator specifically instructs WebSphere to do so by importing the certificate into its Trust Store.

Here are some examples of where this may be useful:

  • Connection to a GIS REST service for integrating GIS data with Maximo
  • Connection to a secured Office 365 Email server
  • Connection to a financial system, such as SAP, that uses secured APIs to communicate
  • Connection to an SMS service for texting users when certain system events occur

… and there are many more.

Here is the message you may encounter:


BMXAA1477E - The connection failed to the HTTP handler for the endpoint. Review the error and server log files for information to indicate the cause of the issue, for example, incorrect properties in the DefaultHTTPExit.java handler class. 
com.ibm.jsse2.util.j: PKIX path building failed: java.security.cert.CertPathBuilderException: PKIXCertPathBuilderImpl could not build a valid CertPath.; internal cause is: 
java.security.cert.CertPathValidatorException: The certificate issued by CN=GlobalSign, O=GlobalSign, OU=GlobalSign Root CA - R2 is not trusted; internal cause is: 
java.security.cert.CertPathValidatorException: Certificate chaining

Look familiar? Let’s fix it.

  1. Log into WebSphere as an administrative user.
  2. Click on the Security > SSL Certificate and Key Management link in the left navigation pane.
  3. Click on the Related Items > Key stores and certificates link on the right side of the main pane.
  4. Click on the CellDefaultTrustStore item in the table.
  5. Click on the Additional Properties > Signer certificates link on the right side.
  6. Click on the Retrieve from Port button.
  7. Fill out the Host, Port and Alias fields. For example:
    1. Host: www.google.com
    2. Port: 443
    3. Alias: www.google.com

  8. Press the Retrieve signer information button. Ensure that the values seem reasonably correct (i.e. you don’t get an error back.)
  9. Press the OK button.
  10. Click the Save to Master Configuration link.
  11. Press the OK button after the changes have been synchronized with all of the nodes.

At this point you’ll need to restart Maximo, your node agents, and your deployment manager for the changes to take effect. From here forward, Maximo will now trust that URL and allow the connection.