Implicit launchpoint Variables

Implicit launchpoint variables are predefined if a script runs in the context of ICD (not if you are using the RMI Interface).

Overview of Implicit Variables

The following list describes the most commonly used variables:

Input Variables (incoming to the script):

mbo – The current Mbo in the context of the script execution. This is an object.
mboname – The name of the current Mbo in context of the script as a string.
app – Name of the ICD application which initiated the script execution
user – Name of the user whose action initiated the script execution.
interactive – Is true if script is executed interactive via UI and false if executed in background (e.g. scheduler, integration framework).
scriptName – name of the script
launchPoint – name of the launch point
action – Only for action launchpoints the name of the action that executed the script.
onAdd – Boolean variable. Is True if the Mbo in the script is added and a new Mbo (not yet saved). Very useful for conditional launchpoints, but may also be useful for Object and Attribute launchpoints.
onUpdate – Boolean variable indicating that related Mbo is being updated.
onDelete – Boolean variable indicating that related Mbo is marked for deletion.
wfinstance – Object of type psdi.workflow.WFInstance that indicates the current workflow from which the action was started. This is only valid for action launchpoints if the action is launched from a workflow.

Output Variables (Script provides them back to ICD)

errorkey – For throwing MXExceptions from the script
errorgroup – For throwing MXExceptions from the script
params – For throwing MXExceptions from the script. This is a Array of Strings
evalresult – evaluation result of the Condition Launch point

Usage patterns

When using implicit variables in your script you should first care about the fact that the variables are nowhere defined and therefor eclipse will throw you an error on the first usage. Please have a look to the “Undefined Variables” Section in this articel to solve the issue.

Perform different actions based on the Launchpoint executing the script

A Jython script could have more than one launchpoint to start its execution. Sometimes the script needs to perform different kind of actions depending on the launchpoint. Another use case for this pattern is to assign different values to variables based on the launchpoint to omit the usage of launchpoint variables. A sample for this is shown in the article “Tips for using Eclipse with Jython” in the section “Prevent Using Script In-/Out Variables”

Execute script actions only if running from GUI

It is sometimes a requirement that a script behaves differently if it is called from a User GUI Interaction or from a system interaction like a crontask, escalation or an Integration Framework call. The following pattern can solve this issue:

interactive = interactive # @UndefinedVariable

if interactive == True:
# Things to do if script is running in user Context
else:
# Things to do if script is called by Crontask, MIF, ...
.

Execute different script actions depending on the ICD Application

Sometimes it is required that a script executes different paths depending on the Maximo application it is executed from. The code pattern is quit simple in that case:

app = app  # @UndefinedVariable

if app == "ASSET":
    # Do actions if called from asset Application
if app == "ITEM":
    # Do actions if called from item Application

Throwing an exception from a script

The usage of the errorkey/errorgroup/params variables is shown in this article.

Logging in Maximo Jython scripts

When you develop scripts with more than a couple of lines of code it is always useful to write some information to a log. Maximo has a very powerful logging engine based on the Apache log4j Java based logging utility.

In the first part of this Blog I will show you how the logging engine will be configured. In the second part we will discover the technics to utilize the logging system from our Jython scripts.

Preparation of Maximo Logging

The Logging configuration in Maximo can be found in the following menu:

Goto > System Configuration > Plattform Configuration > Logging

The first think we have to do is to create a new Appender. This is basically the definition of the logfile where the log is written to. You can either define a common customscript logfile or define different logs for larger scripts. From the Logging application select the Action „Manage Appenders“ and the following screen occurs:

logging1

By selecting „New Row“ you can add an additional Appender. Fill in the values as shown below and press OK at the end.

logging2

You are now back at the entry screen for logging. Go to the Filter bar an search for the „autoscript“ Root Logger entry.

logging3

As you can see there are no specific Loggers defined under this entry. You can now define your own Customscript Logger by pressing the „New Row“ button in the Loggers section. Fill in the following values:

logging4

Important is to select the „Appenders“ entry „customscript“. This is the additional appender for this Logger which will write to the customscript.log file.

To apply your new settings there is no save button. You have to select the Action „Apply Settings“ to enable the new Logger you just have defined.

Using the customscript Logger

To use the scripting engine you first have to initialize a new Logger. This can be done by the following statement:

myLogger = MXLoggerFactory.getLogger(„maximo.script.customscript")

After that the logger can be easily used:

myLogger.info(“<message>”) writes one line to the log with the selected message level. Only levels equals or higher then selected in the customscript logger are really written to the logfile.

A complete example looks like this:

from psdi.util.logging import MXLoggerFactory
myLogger = MXLoggerFactory.getLogger("maximo.script.customscript")
myLogger.debug(“This is a debug entry")
myLogger.info(“This is an info entry")
myLogger.warn(“This is a warning entry")
myLogger.error(“This is an error entry")
myLogger.fatal(“This is a fatal entry")

Tips for using Eclipse with Jython

After you hopefully read the article about setting up Eclipse for Maximo Jython development, there are some Tips & Tricks to get the best results from using Eclipse in combination with Jython. A short summary can be found here:

Undefined Variables

You often use so called implicit variables when you program in context of a launchpoint. The best known variable is mbo which is used quit often. If you use this variable in context of Eclipse it will throw an error in the GUI, because it has never been declared in context of the script.

undefinedvariable1

My best practice is to assign these variables at the beginning of the script to another variable even using a more meaningful variable name (or even the same name) and using the @UndefinedVariable tag. The above sample would now looks like this and will not throw any error:

workorderMbo = mbo  # @UndefinedVariable
workorderMbo.getString("wonum") 

Prevent Using Script In-/Out Variables

Maximo provides you the opportunity to define In-/Out Variables in context of a launchpoint. Variables defined in that way can be accessed as regular variables in Jython but will seamlessly read/change the content of an MBO attribute. This looks very handy at the beginning, but will make reading of scripts much harder, because you can never be sure which attribute a variable is really bound to. In addition you get much more issues with Undefined Variables in eclipse. So in fact of clarity I am a big fan of not using In-/Out Variables. I will show you the programming alternative to this:

In-Variables

assetNum = mbo.getString(“ASSETNUM”)

Out-Variables

mbo.setValue(“ASSETNUM”,assetNum)

Now you could say that the Maximo mechanism will provide you the benefit to assign different values to a variable based on its launchpoint. The situation can also be handled quit simple using some script code:

launchPoint = launchPoint  # @UndefinedVariable
if launchPoint == ‘MYLAUNCHPOINT1’:
    assetNum = mbo.getString(“ALTASSETNUM”)

So I hope you will not see any uncovered situations but your script has now all clarity at one place.

Let Eclipse organize your Imports

Have you ever seen a Jython script which starts like this:

from java.io import *
from java.rmi import *
from psdi.mbo import *

This is really bad style because you will import the complete classes from which you only need a small piece. Much better:

from java.io import files
from java.rmi import RemoteException
from psdi.mbo import MboConstants

But how can you easily maintain the shown list? Quit easy: Don’t care about – Eclipse will help you. Never define an Import manually!
Just write your code using an method from a class library. When you use an method not yet imported Eclipse will help you with an error. Just place your mouse over the error and you will get a hint like this:

undefinedvariable2
Now press the CTRL-1 one key and Eclipse will show you how it might solve the issue:
undefinedvariable3

By selecting the first option “Import MXServer (psdi.server)” Eclipse will create the import statement for you:

from psdi.server import MXServer

On the other hand side, when you no longer need an import Eclipse will show a warning on that import:

undefinedvariable4

You can just delete the line. Even when you delete a line to much you now know an easy way to restore the imports.

Use a Revision Control System

I think it is agreed best practice to have a Revision Control System in place when you develop more than some small scripts. But sometimes you do not have a choose and there is no tool available in your environment. For that case there is a cool, but not well known function within Eclipse. The Team function provides a local history of your edits within the last 7 days. You do not have to activate it, it’s just there!

To get an older file version right click on the File and select Team > Show Local History…

undefinedvariable5

You can now right click an entry and either open it directly or compare an old version with the current version. That is a very nice feature.

How about the 7 days? You may want a longer retention period for your files.

Select: Window > Preferences in the menu.

In the tabs on the left select: General > Workspace > Local History

In this configuration page you can either completely remove time limits for this function or extend the number of days and size for the history.

undefinedvariable6

Display Maximo Messages in Jython

It is often useful to provide a user some visual feedback if a script runs ok or ends with an error message. Maximo provides a complex message system which can be found in its Database configuration. Using this message system you can create message boxes like the following one.

messagebox1

In this post I will show you step by step the usage of the message system.

Scenario

The scenario we want to implement is to check the Priority field within a Workorder. If the Value is larger than 50 we would like to inform the user by a message and not allow to save the record.

The implementation needs the following elements:

  1. A message defined in Maximo
  2. A script with an Attribute Launchpoint

Preparation of Messages in Database Configuration

Start the Database Configuration by:

Goto –> System Configuration –> Platform Configuration –> Database Configuration

In the “Select Action” Menu select “Messages”. You will see an editor Window where you can edit all messages of the Maximo application.

messagebox2

To add new messages press the “New Row” button. A big input screen appears:

messagebox3

A couple of important fields in this screen need some clarification:

  • Message Group: Maximo has a couple of predefined Messages Groups for the different applications or other grouping characteristics. You can either add your message to an existing group or define your own group. An own group can specially help you if you have a couple of related messages for the same context.
  • Message Key: The unique Message Key is an important identifier to address a specific message from your script later on. Therefor it should be a short meaningful specification of what the message means. I prefer upper camel case conversion like “InvalidPrio”. The Message Key together with the Message Group uniquely identifies the message.
  • Display Method: Can be ‘MSGBOX’ or ‘STATUS’. The ‘MSGBOX’ display a standard popup window displaying the message and a set of customizable buttons. The ‘STATUS’ displays the text above the tool bar and does not require user intervention
  • Message ID Prefix: Should be “BMXZZ” which is the pre-defined message prefix  for all custom messages.
  • Message ID Suffix: Specification of the Severity:
    • I = Informational
    • W = Warning
    • E = Error
  • Display ID?: If checked the ID BMXZZ… will be shown in the Messagebox.
  • Value: The Message text. The message Text can include placeholders {0}, {1}, {2} and so on for replacement parameters. I will show you later how to script them.

After clicking OK the new message is immediately available. No Database configuration is required at this point.

Creating the Attribute Launchpoint and the Script

GoTo –> System Configuration –> Platform Configuration –> Automation Scripts

In the “Select Action” Menu select “Create –> Script with Attribute Launchpoint”.

Fill in the following dialog and press Next:

messagebox4

In the following dialog enter the script name and press next:

messagebox5

In the final configuration screen enter the following sample code:

woPriority = mbo.getInt("WOPRIORITY")
if woPriority > 50:
    # Raise an error using a message
    errorgroup = "workorder"
    errorkey = "invalidPrio"

Please notice, that I never use In/Out variables for my scripts, because they make scripts less readable and will cause more issues with undefined variables in Eclipse. As a result I need to get the woPriorty value in line 1.

The important code for the message handling is in lines 4 and 5. We only set the explicit variables errorgroup and errorkey to existing values in the Message Database and that’s all. But how do we show the message to the screen? There is no code for this? The answer is: Maximo will do this for us. We only have to set the two variables at the end of our script. As you see this is also one big limitation, since we can use messages usually only at the end of our script.

If you now save the script and test the Workorder application you should get a message if you enter a Workorder Priority higher than 50.

messagebox6

Sending a message with parameters

What is still missing how we can replace parameters in messages. Let’s assume we have the following message:

Workorder {0} could not be processed because invalid Priority {1} has been specified.

To address such an message from our script we need the following code:

woPriority = mbo.getInt("WOPRIORITY")
woNumber = mbo.getString("WONUM")
if woPriority > 50:
    # Raise an error using a message
    errorgroup = "workorder"
    errorkey = "invalidPrio2"
    params=[woNumber, str(woPriority)]

The trick is the params variable which is a list of replace values for {0} and {1}. The final result looks as follows:
messagebox7

 

Attention:
Message boxes are displayed by throwing exceptions from Jython. If your script updates fields in a Mbo currently displayed the exception can have the effect that the fields in the applications are not updated on the screen. Please consider the Messagebox as the source of the issue if you see this behaviour!

Using external Libraries in Jython

One big limitation of the Jython environment of ICD and Maximo is that the base installation only provides the jython.jar file in version 2.5.2 without any libraries installed. Exactly these libraries can help enormous to write powerful scripts. Some useful examples are httplib and urllib for web access or smtplib for mail functions to just mention some of them. A full reference of included libraries in standard Jython can be found here.

What many users will now know is that the jython.jar file installed in Maximo/ICD does, compared to the original version of this file, include all standard libraries from the Lib directory. That’s quit cool, because we can easily use these libraries in our own scripts without copying any files to the servers. The only thing to know is the exact path of the jython.jar file on the server. This is basically in:

%WebSphere_Home%\AppServer\profiles\ctgAppSrv01\installedApps\ctgCell01\MAXIMO.ear\lib\jython.jar

So on my Windows demo server for example it can be find under:

C:\Program Files\IBM\WebSphere\AppServer\profiles\ctgAppSrv01\installedApps\ctgCell01\MAXIMO.ear\lib\jython.jar

The clue to use these libraries is to add the new library path in your script. To do so you need the following code:

# Extend the sys Path
import sys
foundJython = False
jythonLibPath = "C:\\Program Files\\IBM\\WebSphere\\AppServer\\profiles\\ctgAppSrv01\\installedApps\\ctgCell01\\MAXIMO.ear\\lib\\jython.jar\\Lib"
for path in sys.path:
    if (path.find(jythonLibPath) != -1) :
        foundJython = True
if (foundJython == False):
    sys.path.append(jythonLibPath)
 
import re
mystring = "Hello World!"
test = re.match(r'(.*)World!', mystring)

hello = test.group(1)

print hello

Lines 1-9 are used to extend the sys-path. In the following lines I demonstrate the usage of the external library module re.

If you would like to use additional libraries, which are not included in the standard Jython package you need to copy the library source files to a new directory on the Maximo server and then include this directory in a similar manner like show above.

One improvement of the scenario show above is to use a property for the Jython path (for details see here). Change line 4 to the following code after you have created a Maximo property custom.jython.lib:

from psdi.server import MXServer
configData = MXServer.getMXServer().getConfig();
jythonLibPath = configData.getProperty("custom.jython.lib");

Adding Mbo records to a MboSet

In this blog I will show you in detail how to add new Mbo records to a MboSet.

The easy pattern

To do so we will directly start with a simple code example:

woset = session.getMboSet('WORKORDER')
wo = woset.add()
if wo is not None:
    wo.setValue("DESCRIPTION","New Testworkorder")
    wo.setValue("WOPRIORITY", 10)
 
    # Only save if necessary (e.g. Script executed via RMI)
    woset.save()

This is the simplest form of adding a record. At the beginning we need to have a MboSet object stored to the woset variable.
By using the add method woset.add() a new record will be added to an existing MboSet. The record is added at the beginning of the MboSet. If you would like to add the new record to the end of the existing set you can use the woset.addAtEnd() method.

Very important is line 3 to check if we really have created a new object instance. Without that check our wo.setValue(..) method calls would result in Null Pointer exceptions. The wo.setValue method is quite easy to use to set the individual fields in the MboSet. The good thing at this method is that it does not differentiate between datatypes.

The woset.save() method call is a bit special and need some explanation. Depending on the way you run your script you should or should not save the MboSet at the end:

  • In general no saving on object & attribute launchpoints
  • In general no saving in workflow actions
  • In general save in RMI Scripting, in direct invocation or when you act on a dialog button.

But as you can imagine there is no rule without exceptions and you eventually have to trial and error to find the correct usage.

Add Mbo records to multiple MboSet’s using transactions

In the previous example we just added a single record to a single Mbo. What if we wan’t to a add an additional Worklog entry to the new Workorder. What about transaction processing. We only want to create the Workorder together with the worklog entry or fail both. The following script shows such an example:

woset = session.getMboSet('WORKORDER')
wo = woset.add()
if wo is not None:
    wo.setValue("DESCRIPTION","New Testworkorder 3")
    wo.setValue("WOPRIORITY", 10)
 
    worklogset = wo.getMboSet("WORKLOGAPPT")
    worklog = worklogset.add()
    if worklog is not None:
        worklog.setValue("DESCRIPTION", "WLOG Descr.")
        woset.save()
        print "Workorder " + wo.getString("WONUM")  + " created !"

This example is quite similar to the first one. The difference is that we navigate from our newly created workorder Mbo to a Worklog MboSet via a predefined relationship. Using a relationship at this point automatically holds together the two MboSets via a transaction. There is nothing else via have to do to create this kind of dependency! Cool, isn’t it?

The next line to mention is the woset.save() statement. You could ask: “Why do we do not save the Workorder object?” My answer is: “Save the object you want! Since both MboSet objects are linked by a relationship it doesn’t matter which object you save. The result is the same!”

Jython Introduction

Jython is an implementation of the Python programming language designed to run on the Java Virtual Machine (JVM) and can be used for Maximo / ICD scripting development. The syntax of Jython is based on Python but Java libraries can be included and used in Jython. This is a major benefit when we use Jython for Maximo / ICD Development because on the one hand side you will find a lot of documentation especially for the Python language on the other hand side you can include all Java libraries used in Maximo / ICD. One of the most commonly used libraries is the businessobjects.jar file, which contains most of the business logic included in the product.

In Maximo and ICD up to release 7.6, there is a relatively old version 2.5.2 of Jython in use. If you develop your scripts you should keep this in mind as there is already Jython 2.7 available today which has some new features included.

In this blog, I will not provide a dedicated introduction into the Jython language since there is some very good stuff available on the internet. I will give you some links at this point:

  1. Official Jython Homepage – with references and tutorials
  2. The Definitive Guide to Jython – Very good book!
  3. Intro to Jython, Part 1: Java programming made easier – IBM Developer Works article
  4. The Jython Wiki

Navigate to different MboSet using Relationships

In this blog I will show you a way to navigate from a given Mbo to a different, related MboSet. The easiest way to perform such a navigation is to use a predefined relationship as it is defined in the database configuration of a table. For our example lets assume we are starting from the WORKORDER MboSet. We would like to print out all workorder activities for the first workorder we read.

The following script can be technical combined with the RMI introduction to get a quick result since it requires a valid session object.

woset = session.getMboSet('WORKORDER')
wo = woset.moveFirst();

if wo is not None:
    print "Workorder ",wo.getString("WONUM")    

    workActSet = wo.getMboSet("WOACTIVITY")
    workAct = workActSet.moveFirst()

    while (workAct != None):
        print "Workorder activity: ", workAct.getString("DESCRIPTION")
        workAct = workActSet.moveNext()

Some of the important commands in this example are:

  • if wo is not None: Very important! Check if we have found a workorder Mbo. If you do not do this check the next access like wo.getString(..) could lead to a null pointer exception.
  • worklogset = wo.getMboSet(“WORKLOGAPPT“) gets a reference to a different MboSet based on the current MBO in wo and the existing relationship “WORKLOGAPPT“
  • while (workAct != None) the loop construct to loop on all workorder actions. Have a look here to loop on MboSet’s.

Output fields from a Mbo with different data types

TPAE supports nearly twenty different data types for it’s fields which can be used during database configuration. Commonly used types are ALN, DATE, INT, UPPER, YORN just to mention some of them. A full list of available datatypes can be found on the support webpage.

When we read records using a script we have to be very carefully to always use the correct command which is related to the data type of the field. Some important commands are:

  • wo.getString(“<FIELD>”) – returns a String value
  • wo.getInt(“<FIELD>”) – returns a Integer value
  • wo.getLong(“<FIELD>”) – returns a BigInteger value
  • wo.getDouble(“<FIELD>” – returns a Double/Decimal value (better than getFloat()).
  • wo.getDate(“<FIELD>”) – returns a Date value
  • wo.getBoolean(“<FIELD>”) – returns Boolean Value from a YORN Field (True / False)
Attention: It is a common mistake to use the getString method to read numeric values which will work without errors. The issue you will fall into is that numbers are represented  with thousands separators so a number 1000 is represented as a string “1.000”.

In context of the workorder MboSet a code sample could be the following one, which reads different fields with different data types:

 woset = session.getMboSet('WORKORDER')
    wo = woset.moveFirst()

    while (wo != None):
        # Read out values
        print "Workorder ",wo.getString("WONUM")
        print "Priority ",wo.getInt("WOPRIORITY")
        print "StartDate ", wo.getDate("TARGSTARTDATE")
        print "WorkorderID ", wo.getLong("WORKORDERID")
        # get next Workorder
        wo = woset.moveNext()
1 2 3