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

Read Maximo system properties in a Jython script

It is a good practise to store all properties like usernames, passwords, Url’s, etc in the Maximo System Properties. User defined properties can be defined in addition to all the tons of existing system properties. You will find the properties application under

System Configuration –> Plattform Configuration –> System Properties

To define a new property just click “New Row” in the Global Properties section:

properties1

The usage of these properties in a Jython script is quit easy. To read and print the new “custom.username” property the following script can be used:

from psdi.server import MXServer
configData = MXServer.getMXServer().getConfig();
maxProperty = configData.getProperty("custom.username");
print maxProperty

properties2

When tested in the Maximo Script editor the script should print out the username “bigadmin”. If you get a no output at StdOut or “None” you should verify if you made a live refresh of the property data.

Selecting specific Mbo’s using Relationships and Where Clauses

A very common scenario is that you would like to get an instance of a MboSet with not all records included, but only a subset. Spoken in SQL you would like to apply a where clause to limit the number of records you get as a result in a MboSet. In this article I will show you different ways on how you can achieve this.

  1. Usage of relationships

A very common pattern is to just use relationships when you initialize your MboSet. I have shown such a pattern in this article. You can easily define the where clause directly in the relationship and you will only get the records based on the where clause.

  1. Adding a Where-Clause to an existing MboSet

It is quit handy to add a Where-Clause to an existing MboSet. The following code example will show this scenario:

woset = session.getMboSet('WORKORDER')
woset.setWhere("WONUM = '2009'")
woset.reset()
wo = woset.moveFirst()
if wo is not None:
    print "Workorder ",wo.getString("WONUM")

At the beginning you initialize a MboSet returning all Workorder’s in the system. In line 2 you append a Where-Clause to the result set. When you have used a relationship with an existing where clauses in it the new where clause will be appended. The woset.reset() method call in line 3 is required to “execute” the where clause and update the result set in woset. The rest of the script just shows the first record.

  1. Building a temporary relationship

This is one of my favorite patterns which can help you make life much easier. When you develop your script you will not always have the perfect relationship for navigation defined in the system. So you can either define a new relationship in database configuration or you can do this step directly from your Jython script. You define a new relationship just to be used temporarily in your script with no impact to the rest of the system.

The syntax based on the JavaDoc is:

public MboSetRemote getMboSet(java.lang.String name,
                              java.lang.String objectName,
                              java.lang.String relationship)
                    throws MXException, java.rmi.RemoteException

A real example statement to get all worklog entries for a specific workorder could look like this:

worklogset = wo.getMboSet("$TEMPREL1", "WORKLOG", "RECORDKEY='2009'")

$TEMPREL1 – An unique identifier for the relationship. It needs to be unique and for this reason it is good practice to start the name with a dollar sign.

WORKLOG – The database object which should be queried using any provided where clause.

RECORDKEY=’2009’ – The where clause to be applied to reset the MboSet of interest.