MBOs in Automation Scripts: Working with MboSet Collections

Let’s start with defining some terms. The three biggest terms to define are MboSet, Mbo, and MboValueData. First, the acronym MBO stands for Maximo Business Object. This represents a generic record of data within Maximo. An MBO could be a Work Order, an Asset, an Item, or any of the other hundreds of record types in Maximo.

If we think of a spreadsheet of data, an MboSet would be a tab with columns and many rows of data. The data can be sorted or filtered in many ways. Again, this data could represent any record type in Maximo. An Mbo would be a single row of data represented within an MboSet. Finally, an MboValueData record would be a single cell in the spreadsheet that represents a column of data for a single row.

These terms are used in generic fashion because they have behaviors that can apply to any set of records in Maximo. For example, a set of Work Orders, Assets, Items, etc. can all be filtered, sorted, saved, and looped over in the same fashion. In fact, all record types follow a wide set of general behavior patterns. If you understand these patterns, you’re going to write much better automation scripts in Maximo.

This article will focus on the MboSet collection.

There are three ways to get a handle on an MboSet:

  1. Use the getThisMboSet() method on an Mbo. Most automation scripts will create an implicit variable at runtime of the script called mbo. This represents the record on which the script is operating. To get a handle back to the list that the current record is part of you can use some code that looks like this:
    mboSet = mbo.getThisMboSet()

    This is analogous to going back to the List tab in Maximo.

    mbo

  2. Use the getMboSet() method on an Mbo. You can either get an MboSet of data related to the current record, as in a one-to-many relationship, or you can get an MboSet of unrelated data to the current record.
    mboSet = mbo.getMboSet("ASSET")

    or

    mboSet = mbo.getMboSet("$WOAPPR", "WORKORDER", "status = 'APPR' and siteid = 'BEDFORD'")

    mbo

  3. Use the MXServer.getMXServer.getMboSet() method. If you have an existing Mbo in your code, it’s recommended that you use one of the first two options above. However, there are some automation script launch points that won’t contain the implicit mbo variable (such as Cron Task scripts, some of the Integration scripts, etc.). If that’s the case, then this is your option. You’ll need a userInfo variable defined to make this connection as a given user.
    mboSet = MXServer.getMXServer().getMboSet("WORKORDER", userInfo)

Most objects in the Business Components are collections of objects or the actual objects themselves. When the getMboSet() method is called, a set of objects is returned. When the set is first initialized it has zero members, but it can potentially be filled with all objects that have been saved in the database, depending on which methods are called to restrict the set.

For example, the code:

woSet = mbo.getMboSet("$WOAPPR", "WORKORDER", "status = 'APPR' and siteid = 'BEDFORD'")

returns a collection of WO objects (which represent approved work orders for the BEDFORD site). So far, the set has zero members, but after the following code executes:

wo = woSet.moveFirst()

the set contains all approved work orders in the BEDFORD site (or, more precisely, WO objects representing all approved work orders in the BEDFORD site). The methods moveNext() and moveLast() produce similar results (see below).

Note that the method also returns a remote reference to the object that has become the current member of the collection, in this case the first one. This holds true for the other methods that traverse collections as well (moveNext(), movePrev(), and moveLast()).

A set has a current member pointer that is maintained on the server. The current index of that pointer may be retrieved with the following:

woPos = woSet.getCurrentPosition()

The method returns -1 if there is no current member. Note that the first object in the collection is stored in the zeroth position.

The action of moveFirst() sets the current member pointer to the first position. The moveNext() method produces the identical result: the pointer is advanced one position. In the case of moveLast(), the pointer is set to the last position and all records in the collection are retrieved. This can be very expensive in performance terms and is generally not recommended.

If the current object itself is required, call the following:

wo = woSet.getMbo()

The method returns null if there is no current member. The getMbo() method can also return the object at an arbitrary position in the set; in this case the desired position is passed as an integer argument. The following code returns the first object in the collection.

wo = woSet.getMbo(0)

The current member pointer is important, as there are many methods in the MboSet class which operate on the current object in the set. Examples of these are setValue() and getString(). To change the current member pointer, use methods such as moveNext(), movePrev(), and add().

The contents of the collection can be restricted by using the setWhere() method. For example:

woSet.setWhere("wonum like '1%'")

This restricts the set to having only members whose work order numbers begin with the character “1”. This method must be called when the set is newly initialized, i.e. before any members have been placed in the set. If the set already has members, call the method:

woSet.reset()

This clears the current contents of the collection and generates a query to the database. Any modifications or additions made to the set are lost if they are not saved before reset() is called. The setWhere() and reset() methods can be called in either order.

When the setWhere() method is used, it must be passed a valid SQL where clause for the database platform the server is connected to.

Here’s an example of looping over an MboSet:

woSet = mbo.getThisMboSet()
wo = woSet.moveFirst()
while wo:
#Do some things with the work order
wo = woSet.moveNext()

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

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

MBOs in Maximo Automation Scripts: Intro – Using Java Methods

Maximo has consistently remained at the top of the list of Enterprise Asset Management systems throughout the years. While there are many reasons for this, one of the most important reasons has been its ability to easily adapt to different business requirements through configuration of the system.

In earlier versions, developers were able to extend core Maximo Java classes to invoke specific business logic. This offered an unprecedented amount of control for organizations to be able to tweak the solution to fit their needs. While this capability still exists, many organizations have subsequently shied away from these types of changes as they have upgraded their systems over the years. The skills necessary to maintain these customizations were difficult for a lot of organizations to supply, and IBM, understandably so, did not offer support for those heavily customized systems.

Automation Scripting was a new feature that was introduced in Maximo v7.5 based on the feedback from the user community. Customers yearned to have a simpler, yet effective way of customizing the product without having to go through the pain of system downtime and adopting the steep learning curve that came with Java coding and extending the existing Maximo classes.

Automation Scripting provides a lot of benefits to make custom business logic easy within Maximo. However, having knowledge of the Java methods available on Maximo Business Objects (MBOs) can greatly enhance your ability to perform meaningful or deep business logic changes within the system.

Subsequent blog article entries will cover in detail how to:

  1. Working with collection objects (MBO Sets)
  2. Reading single attributes of a collection
  3. Reading multiple attributes of a collection
  4. Modifying an object in a collection
  5. Adding new objects to the collection
  6. Exception handling
  7. Attributes with value lists
  8. Changing status
  9. Object relationships
  10. Query by example
  11. Long descriptions

… and more.

Hopefully these articles will unlock the ability to make the most out of the Automation Scripting capabilities of Maximo, and allow users to develop some useful business logic enhancements to their Maximo systems.  Look for our MBOs in Automation Scripts Series to run on the third Thursday of the month until completion.  And also check out some of our other articles here.

Automation Scripts: Compatibility with Maximo 7.6.1

I recently setup a Maximo 7.6.1 environment. After browsing the new user interface and investigating some of the new features I imported an old JavaScript-based automation script that I like to use. Upon executing this automation script I was met with an error on Line 1. This particular script is called remotely using OSLC.

Here is the message:

BMXAA7837E – An error occurred that prevented the <script> script for the null launch point from running.\n\tReferenceError: \"importPackage\" is not defined in <eval> at line number 1.

Here is the start of my script:

importPackage(Packages.psdi.server);
...

If you are like me you have many scripts that import Java packages so this was certainly alarming.

Why is this happening?

Maximo 7.6.1 uses a different scripting engine with the move to JDK 1.8. You may also encounter this with 7.6.0.6 (and newer) if using JDK 1.8. Given that, I wasn’t completely surprised my script didn’t work but I was surprised that there was not more written about encountering this message and what to do if your scripts no longer work after upgrading.

How do I fix it?

It turns out the answer was in an IBM document all along, but it took a while for me to use the correct phrase in my search engine in order to locate it. At the very end of the document, Maximo 76 Scripting Features, there is mention that the Rhino JavaScript engine was replaced with Nashorn (V8). I was aware that this was happening but I was not aware of what this change meant. It turns out that Nashorn does not permit the import of whole Java packages which sheds light on why I was getting that error above.

Thankfully there is a workaround for your old scripts! Add the following line to the beginning of your script:

load("nashorn:mozilla_compat.js");

This article references how to properly construct your script to take advantage of the new script engine. At least with the workaround above you can get your script working and improve upon it later.

Hopefully this will save you time when using your old JavaScript based automation script in a Maximo using JDK 1.8.

Launch Automation Script on New Record Creation in Maximo

IBM introduced Automation Scripts in Maximo 7.5 as a way to inject scripting code into key system events in order to alleviate the need for Java customization to the product. Launch Points for object-based events, attribute-based events, and action-based events were created to trigger these scripts to run. This gave developers the ability to trigger scripting code within Maximo to fire in the place of the most common methods that custom Java code had been used for previously.

As the usage of Automation Scripts has grown over the years, IBM has expanded its Launch Points to include additional events such as various points in the Integration cycle to again alleviate the need to write custom Java code.

One commonly used method that has been seemingly overlooked is the add() method in the base Mbo class. This method fires immediately upon creation of a new record in Maximo. There is an option in object-based Launch Points to fire only on the add event, however, that triggers only upon the save event of newly created records – not immediately after a new record is created.

In one of the later releases of Maximo 7.6, IBM added an undocumented hook into their code that allows developers to fire an Automation Script upon creation of a new record. This article will walk you through how to do this.

The Mbo.add() method is typically used for:

  1. Conditionally defaulting values on a child record based on information from the parent record
  2. Defaulting values on a custom child record from a parent record, such as its primary key values

Creating a Script to Fire on Add

To have your Automation Script run immediately upon creation of a new record, you can utilize a little known trick. The script itself will not contain any Launch Points, however, there is code within Maximo that will look for an active script with the name OBJECTNAME.NEW and run it, if it finds a match. When using this feature, replace OBJECTNAME with the name of the record type, such as ASSET or WORKORDER.

Let’s look at an example. Say that your users have a requirement to default the Line Type of a PO Line to different values based upon the PO Type that has been selected. Specifically, if the PO Type is SERV, then the Line Type of a PO Line should default to SERVICE. Otherwise, it should default to ITEM. Here are the steps to achieve this:

  1. Log into Maximo as an administrative user
  2. Navigate to the System Configuration > Platform Configuration > Automation Scripts application
  3. From the More Actions or Select Action menu, choose Create > Script option
    1. Script: POLINE.NEW (this is the trick! Make sure that the name is formatted properly)
    2. Description: Default the PO Line Type based off the PO Type
    3. Script Language: python
    4. Log Level: ERROR
    5. Active: Yes
    6. Source Code: [see below]
  4. Click the Create button
po = mbo.getOwner()
if po and po.isBasedOn("PO"):
    poType = po.getString("POTYPE")
    if poType == "SERV":
        mbo.setValue("LINETYPE", "SERVICE")

Once you have created the script, you will be able to go to the PO application and test it out. Create a new PO with a type of SERV (Service). Next, navigate to the PO Lines tab and select New Row. You will see the Line Type for that row is Service. Create another PO record with the type STD (Standard), then select New Row on the PO Lines tab for that PO and you will see the Line Type is Item.

Another Example

Let’s say your users would like to start capturing change history against the STATUS field on Assets, as other similar objects do. After adding a new database object called ASSETSTATUSHIST, you can create a new Automation Script to populate the fields in that table from the parent Asset record.

  1. Log into Maximo as an administrative user
  2. Navigate to the System Configuration > Platform Configuration > Automation Scripts application
  3. From the More Actions or Select Action menu, choose Create > Script option
    1. Script: ASSETSTATUSHIST.NEW
    2. Description: Populate the fields on a new Asset Status History record from the Asset
    3. Script Language: python
    4. Log Level: ERROR
    5. Active: Yes
    6. Source Code: [see below]
  4. Click the Create button
asset = mbo.getOwner()
if asset and asset.isBasedOn("ASSET"):
    mbo.setValue("ASSETNUM", asset.getString("ASSETNUM"), mbo.NOACCESSCHECK)
    mbo.setValue("STATUS", asset.getString("STATUS"), mbo.NOACCESSCHECK)
    mbo.setValue("CHANGEDATE", asset.getDate("STATUSDATE"), mbo.NOACCESSCHECK)
    mbo.setValue("CHANGEBY", asset.getString("CHANGEBY"), mbo.NOACCESSCHECK)

When you add a new row to the ASSETSTATUSHIST table, it will automatically populate with the above values based off of the parent Asset record.

That’s it for now! Please feel free to leave any comments or questions below. For visual instruction of the previous steps, check out our video tutorial.

Run an Automation Script from a Cron Task in Maximo

Cron tasks are jobs that run automatically on a fixed schedule within Maximo. They include important jobs such as the PM Work Order Generation Cron Task, the Inventory Replenishment Cron Task, and others.

There may be times when you come across some requirements where creating your own Cron Task provides the best solution. In previous releases of Maximo, it was required to write a custom Java class to perform the action of the Cron Task when the schedule came due. Therefore, many times we turned to Escalations to provide solutions where scheduled tasks were required.

In Maximo 7.6, you can now run an Automation Script directly from a Cron Task. This article will walk through how to configure this.

Step 1: Create a new Automation Script

  1. Log into Maximo as an administrative user.
  2. Navigate to the System Configuration > Platform Configuration > Automation Scripts application.
  3. From the Select Action menu, choose the Create > Script option.
  4. Populate the appropriate fields on your new script:
    1. Script: CRONSCRIPT
    2. Description: Automation Script called from a Cron Task
    3. Script Language: python
    4. Log Level: ERROR
    5. Source: print ‘Automation Script called from a Cron Task’
    6. Click the Create button to create the new script
  5. Press the Close button on the ensuing dialog
  6. Locate the new CRONSCRIPT script in the list, and open the script
  7. Update the Script Source as follows, and press the Save button when finished:
#######################
# Script to generate an SR for a monthly equipment audit
#######################
from psdi.server import MXServer

# Get the variables from the cron task arguments
srDesc = 'Monthly Equipment Audit'
srOwner = 'WILSON'
if arg:
    srDesc,srOwner = arg.split(',')

# Create the SR
srSet = None
try:
    srSet = MXServer.getMXServer().getMboSet('SR', runAsUserInfo)
    srMbo = srSet.add()
    srMbo.setValue('DESCRIPTION', srDesc)
    srMbo.setValue('OWNER', srOwner)
    srSet.save()
finally:
    if srSet:
        srSet.close()

#######################
# End of Script
#######################

 

A few things to note with this source code:

  • This script will create a new Service Request and populate the Description and Owner fields
  • Notice that there are a few implicit variables that Maximo creates for use in the script:
    • arg: This is a delimited list of arguments that you wish to pass into the script. We will show below how to populate the arguments when we create our Cron Task.
    • runAsUserInfo: This variable gives the script access to the user profile that the cron task is running as. This will allow you to fetch Mbo Sets or perform other tasks with a set of credentials.
    • instanceName: While this variable is not utilized in the above script, it represents the Cron Task Instance Name that the script is being called from. This could be useful in the cases where you have multiple cron task instances and need to distinguish which one is actively running.
  • You can utilize the MXServer.getMXServer().getMboSet() method to fetch MBO sets of any record set that the runAsUserInfo profile has access to. Please note the strategy for closing MBO sets when you are finished with them. This alerts the JVM that the object is ready to be garbage collected. It’s important to follow this strategy, or a similar strategy, to avoid potential memory leaks with the JVM.

Now that we have a script, we can create a Cron Task to call it.

Step 2: Create a new Cron Task

  1. Log into Maximo as an administrative user.
  2. Navigate to the System Configuration > Platform Configuration > Cron Task Setup application.
  3. Click the New Cron Task Definition button on the toolbar or under the Common Actions menu.
    1. Cron Task: MYCRON (give your cron task an appropriate identifier)
    2. Description: My Cron Task (give your cron task an appropriate description)
    3. Class: com.ibm.tivoli.maximo.script.ScriptCrontask (this is the key to the setup, the value is case sensitive and must be exact)
    4. Click the Save button
  4. Click the New Row button under the Cron Task Instances table
    1. Cron Task Instance Name: MYCRON01 (give your cron task instance an appropriate identifier)
    2. Description: My Cron Task Instance 01 (give your cron task instance an appropriate description)
    3. Schedule: 1M,0,0,0,1,*,*,*,*,* (this will schedule the task to run on the first of every month; use the schedule builder to select the schedule that you want your cron task to run on)
    4. Run as User: MAXADMIN (select the user that you want the cron task to run as)
    5. Active: Yes
    6. Click the Save button
  5. There should be two new parameters in the Cron Task Parameters table at the bottom of the page.
    1. SCRIPTARG: This parameter is used to pass in variables to the script. You can separate variables by a comma or other delimiter in order to pass in more than one variable to the script.
    2. SCRIPTNAME: This parameter tells Maximo which Automation Script to trigger when the cron task runs.
    3. Fill out the above parameters and click the Save button. The SCRIPTNAME parameter is required, but the SCRIPTARG parameter is optional.

At this point, the Cron Task is configured and ready to go. When the cron task runs it will call the script with the arguments specified in the parameters table.

If you have any trouble, or have questions on how this works, please leave feedback in the comments below.