Search This Blog

Wednesday, September 14, 2022

MAXIMO : Performance Best Practices - MboSet

 This article details  java /script based customization best practices when getting MboSet and related Mbo's for business logic.

Broadly there are two way's to fetch MboSet in the code.  Either using relationships be it those defined in database configurations or through dynamic relationships being created within Code , or using MXServer class.

There are times when we need to use one of these approach to fetch required MboSet and its not like never use MXServer to fetch MboSet which is misconception many folks have as part of Maximo coding standards or guidelines.

Here I would be discussing more specifically on MboSet being fetched via MXserver  and required considerations while using this approach , lacking it would have serious concerns around memory cleanup/OOM or stuck thread issues or sometimes even undesirable or inconsistent results.

Below are are rules of guidelines to be considered while getting MboSet from MXServer reference.

1. setwhere() and reset()

Almost all the time you should have dedicated where-clause to be applied when MboSet is fetched via MXserver  as without that it will load all the records from  set into memory ( as per fetch limit)  will be both performance heavy and in-effective memory usage . Hence setwhere() should be used to filter for appropriate criteria to load required data only. Then reset() is actually doing getting data from database as per applied clause in setwhere() into the memory.

MboSetRemote assetSet = MXServer.getMXServer().getMboSet("ASSET", getUserInfo());
assetSet.setWhere("LOCATION='BEDFORD'and status='OPERATING'");
assetSet.reset();

2. In case this set is being fetched to add new records and not for traversing current records

the ideally sethwere should be something like below to get empty set

assetSet.setWhere("1=2");
assetSet.reset();

3. If set is to be used only for traversing the record without any add/update/deletion then it should have DISCARDABLE flag set to true so it wont consume memory and dont get included in cached in memory and will be always readonly.

assetSet.setFlag(DISCARDABLE, true);

4. Additionally if set being fetched is not to be saved  then NOSAVE flag can be set to its not get included in respective transaction.

 assetSet.setFlag(NOSAVE, true); 

5. Incase set is fetched from MXServe it wont get automatically included in MXTransaction and hence save() should be explicitly called for any data add/change operations on set else it will be lost. This is not required when MboSet is fetched via relationships or through coding dynamic relationships.

Always call clear() and close() on the MboSet fetched from MXServer once it is certain that it is not required anymore. This releases the memory and the database resources. If the set is not closed, it will remain in memory till it is collected by the garbage collector which could be a long time

assetSet.clear();
assetSet.close();

Thursday, May 26, 2022

MAXIMO : Application Configuration Best Practices

This article details  application configuration best practices

Enhance existing Application OR  Clone - what to do ?

Reposition or remove fields

If you have small changes to out-of-the-box applications, you cant make the changes directly within the application.

 A best practice is not to remove entire sections or tabs, but rather to hide them with a Signature Option.

When new versions of the out-of-the-box application are released and this application is upgraded, preserving these old sections and tabs allows for the upgrade process to add these new fields.  You can then examine these applications after upgrade to determine if you want to expose any of the new fields in your UI. 


If you hide sections with a Signature Option - it will have a performance impact.  Therefore, if you require major differences,  you may want to  consider cloning the application and modifying the cloned application.  You can also use this approach to hide sections of an application for certain security groups and show sections of the application to others.

Clone an existing application

You may want to clone an existing application if you want a user interface that is very different from the out-of-the-box application.  You might also decide that different users should see very different views of the application.

Any additional changes that future versions of your products make to the out-of-the-box applications must be manually merged into your cloned applications if you want these additional features.  In addition, you will need to manage the security groups and metadata of this new application separately from the original application. 

For instance, if new dialogs and new menu options are added to an application -  you will need to manually add all of this to your cloned application if you want your users access to this new functionality.  

Conditionally modify fields based on the state of the record

Conditional UI is a powerful tool that can change the behavior of your user interfaces based on the state of the current record. You can highlight key data for your users based on a condition. 

For instance, you might choose to change the color of the priority field in the list tab if a record has a priority value of 1.   You may also want to hide additional fields from your end users by default and only show them based on the values of other fields.  For instance, you might only prompt a user for a back-out plan for a change if it is a high risk change request.

Conditional UI is not a replacement for business logic.  Using Conditional UI to make a field required at various aspects of your process is probably a poor decision, since this requirement is enforced only at the user interface level.

 Automation scripting, Java programming, or the workflow engine should be used to enforce business logic in your objects. 

Because the conditional user interface logic is evaluated every time the user interface is rendered, it can be expensive from a performance standpoint. The conditional user interface expressions are also executed each time the value of a field is set by an end user.  This can slow down the time it takes to traverse the page.  For example, if you write a SQL conditional expression that takes a half second to execute, then it will take 10 extra seconds for an end user to fill in a page of 20 fields.

You should carefully consider the condition expressions you have written to make sure they execute against the MBO data available in memory if possible and do not require additional database invocations to related records to determine the result of the condition.

Enhancing Existing Applications

Sometimes out-of-the-box applications do not meet your business needs, and you need to add information to them.

Add existing fields to an application

You may want to show data to your users that is not currently displayed in the application.  To verify the field exists, access Database Configuration application within Maximo.  Locate the object, and confirm that the data you want to display is already available as an attribute of this object. 

If this is the case, adding this attribute to the application user interface is a simple drag and drop operation from Maximo's Application Designer.  

If the data that you wish to show is available on a field on a related object and the parent and child objects are related in a one-to-one relationship, you can follow a similar approach add the related field to the user interface setting an attributename with this syntax:

RELATIONSHIPNAME.ATTRIBUTENAME.
Best Practices:  It is not recommended to show related fields in the list tab of an application because of performance impacts.  The additional database query can cause significant performance degradation.

You can modify the advanced search dialog for your application to allow searching on this related information using the same related field syntax.  When searching on these related fields, you can search on one-to-many relationships. For instance, you can find any work order with a child task that’s assigned to you.

Add existing related information to an application

For instance you might add a table or dialog to the Person application to show the list of work orders this person currently owns.  This information already exists in the database and is accessible through a relationship.  

Add new fields to an application

Many customers find that new types of data must be collected for their business processes. These unique fields will first add the new fields to the underlying MBO using the Database Configuration application.

  • Add a new primitive field (for instance adding a new text attribute, or numerical attribute to the Ticket object). 
  • Add a new field that relates to a different object (for instance a new field on the change work order that points to a person).  

Relate two existing objects in a new relationship

You might want to build a mapping table that relates existing objects in a new way.  For instance you might add a list of persons to a change work order. You can use the Database Configuration application to build a new mapping MBO that captures this one-to-many relationship. 

The new MBO will have one set of fields that points to the primary keys of the parent object and one set of fields that point to the primary keys of the child object.  You will then add a new relationship between the parent MBO and the mapping MBO using the Database Configuration application. You’ll then add this relationship to a new table in the application using Application Designer. 

In this table you will put a lookup to allow the end users to select the child object that they want to relate to the parent object (see information on configuring a lookup above).  You might also choose to write a dialog and Bean code to allow them your end users to select multiple “child” objects to relate to the parent object at one time.

Create an entirely new application to capture business information

This is the most expensive option to develop and maintain. Make sure that another existing MBO doesn’t already represent the concept you’re trying to map inside Maximo.  First you must create the new underlying MBO representation in the Database Configuration application.   

You have to consider what kinds of business logic and validation logic this MBO should apply through Automation Scripting or Java code. Make sure to follow best practices when writing the MBO code. Then you must go to the Application Designer and create a new application and point it to your new MBO.

References: Application Configuration Best Practice

Sunday, May 1, 2022

MAXIMO : REST/OSLC APIKEY based authentication using IdP via SAML


Starting Maximo 7.6.0.9 on-words IBM capability to integrate Maximo with Identity Providers (IdP) like MS Azure AD, Open Connect , AWS IAM etc. , using SAML. Security Assertion Markup Language (SAML) is an open standard that allows identity providers (IdP) to pass authorization credentials to service providers (SP).

As this integration worked properly only with Maximo UI application, Browser less connection for Maximo REST, OSLC APIs was not supported and needing direct local active directory connection configuration for the same. 

Maximo has added this feature of API Key from Maximo 7.6.0.9 which can be used to overcome the unseen limitation. In the earlier versions of Maximo, creation of API for specific user and management of the key was not user friendly and it needed MAXAUTH to be passed to generate the API Key from REST/OSLC API call. With Maximo 7.6.1.2, API Key feature has been improved and now administrators can generate the API keys for users from Maximo UI and revoke them on the go.

Once users have integrated Maximo with SAML authentication, users can go to the Work Center application and can assign the API keys from the Administration Work Center.

We have mentioned the steps in details below on how API Keys can be created and used for REST/OSLC APIs -

1.To create API key, administrators should go to Administration Work Center > Integration.





2. Click on the API Keys and click on the Add API Keys button-




3. Search for user for which API Key needs to be generated and click on the Add button.



4. This will generate API Key for the user and the key will be visible on the work center. In the future if the administrator wants to revoke the access of the user, then the key can be deleted by pressing the Delete button on the same card.



5. Now this API Key can be used with REST/OSLC APIs.

While using the API Key, there is no need to pass MAXAUTH for Non-LDAP (Native Authentication) or User Credentials with BASIC Auth for LDAP enabled Maximo Instance.



6. APIKEY will be passed in Params for API and which will provide the API output after authentication from Maximo.




Keep RESTing using Maximo APIKEY.. :)

MAXIMO: Kubernetes what basics you need to Know as Maximo Consultant ?

 Kubernetes (K8S) is the most adopted container orchestration service available aligned with MAS/OpenShift.   Hence basic understanding of Kubernetes is required to understand OpenShift better which is the default platform for Maximo Application Suite (MAS).

OpenShift commands very much aligned to those of K8S while it additionally  provides  ability for users to interact with K8S using Web Console.










Here in this post I am going to share  a list of some very frequently used kubectl commands which are very useful for all of us as  maximo consultants and this will also help to understand the commands for OpenShift.










Friday, April 29, 2022

MAXIMO :In-session logging to debug issues through specific login user actions in log

 

How to turn on the in-session log file for a particular user  ?

 In-session logging makes debugging easier and prevents performance issues.


With in-session logging, also known as thread-based logging, you can configure logging for an individual user so that other users are not affected by performance issues. When you configure in-session logging, you can list the logs that you want to enable. The scope is set to root loggers, such as maximo.sql and maximo.integrate. The contexts that you can enable include OSLC or REST calls, MIF web services and XML or HTTP calls, UI sessions, cron tasks, and MIF MDBs. The logs are generated in a separate file that is created under the working directory of the application. Logs must be manually purged to free up disk space.

Objective

To be able to track an issue specifically on a user only to avoid performance issues.

Steps

Go to> System Configuration> Platform Configuration> Logging

Select Action > Configure Custom Logging > Thread Logger tab

Configure the context you want to work with. If you are willing to replicate an issue upon a user action, then this can be the UI Context and Set the user you want to trace the logging on.






In the lower section, set the logger and log levels you desire, ie: log4j.logger.maximo.sql

Click OK

Once everything is configured, you will see the logging information under Select Action > In Session Logs.









Reference Maximo Debugging in-session logging



Thursday, April 28, 2022

MAXIMO: Automation Script Key Features Guide

                   Maximo 76 Scripting Features  (By   Anamitra Bhattacharyya on IBM community)

                  

The Global “service” Variable

The service object is a global object available in all the script points in one form or the other. For

most of the script points it's called “service”. As you got through this doc, you will see that the

MIF Object Structure scripts are not directly referring to this variable as “service”. Instead they

refer to it as “ctx” (which is nothing but an extended version of the “service” object).

The “service” object helps us make some of the common tasks simple from an automation

script. For example the tasks like throwing an error or adding an warning or invoking workflows,

invoke channels, logging etc become much easier leveraging the “service” var. And it's not just

being easy to use, it's also being the better way to use. For example if you want to throw real

time errors, rather than setting the errorkey and errorgrp variables, you should just use the

service.error(grp,key) or service.error(grp,key,params) apis to achieve that. It's also leveraged to

invoke library scripts as you will see in the next few sections. We will also have examples on

how to invoke a MIF Invoke Channel to integrate to some rest apis using the

service.invokeChannel(channelname) api.

Library Scripts

Library scripts are good for encapsulating some re-usable logic. In Maximo 76, we can write

library scripts as just simple scripts. We can write these scripts in any language and then call

from another language.Lets make a library script for making HTTP GET calls. We can make that

using a py script like below

from psdi.iface.router import HTTPHandler

from java.util import HashMap

from java.util import String

handler = HTTPHandler()

map = HashMap()

map.put("URL",url)

map.put("HTTPMETHOD","GET")

responseBytes = handler.invoke(map,None)

response = String(responseBytes,"utf-8")

Here the script is expecting a variable called “uri” to be passed into the script from the invoking

script. The response is set to the “response” variable, which can be used by the calling script to

get the response data as a string.

A sample script (js) code that can leverage this library script is shown below

importPackage(java.util)

importPackage(Packages.psdi.server)

var ctx = new HashMap();

ctx.put("url","http://localhost:7001/maximo/oslc/script/countryapi?_lid=wilson&_lpwd=wilson");

service.invokeScript("LIB_HTTPCLIENT",ctx);

var jsonResp = ctx.get("response");

var countries = JSON.parse(jsonResp);

Note how it creates the ctx and then passes the “uri” to it and then uses “service” variables

invokeScript api to invoke the library http script (named LIB_HTTPCLIENT). The script also gets

the response from the “response” variable and then uses the JSON.parse(..) api to parse that

data to a json object for further processing. We are going to talk about this script more in the

lookup script below.

Creating lookups using scripts

Creating lookups can be done using the attribute launch point “Retrieve List” option. As with any

attribute launchpoint, we are going to hook the script up with the mbo attribute on which we

want the lookup to happen. But just writing the script is not enough for lookups to work. We

need to make an entry in the lookups xml for the lookup dialog. We also need to associate the

lookup name to the mbo attribute in presentation xml. In the example below we would want to

write a lookup for the “address5” attribute in the Address mbo for the multisite application. The

address5 maps to a country name using the country code. Say we want to use some of the

external rest apis available that provides the list of countries. There are quite of few of those out

there. But for this example, just for some fun and learning, let's explore how we can write our

own rest apis using Maximo scripting.

Creating REST apis using Automation Scripts

Below we show a script (create this as a vanilla script in autoscript application - no launchpoints)

that will do just that.

var countries = {"USA" : "United State Of America", "CAN":"Canada", "GBR": "United Kingdom"};

var cc = request.getQueryParam("cc");

var responseBody;

if(cc)

{

if(countries[cc])

{

responseBody = JSON.stringify(countries[cc]);

}

else

{

responseBody = "";

}

}

else

{

responseBody = JSON.stringify(countries);

}

We do a lot of json processing here and that is one of the reason why we chose js as the script

language. Note the use of 2 implicit variables - request and responseBody. The request refers

to the REST api request object (of type com.ibm.tivoli.maximo.oslc.provider.OslcRequest). We

leverage that to get http request query parameters for the script to process. The api call will look

like

GET /maximo/oslc/script/<script name>

This will return the json with the country code and the country names. You can use any browser

or a rest client like chrome POSTMAM to do this test. The api will also optionally support getting

the country name based on a country code.

GET /maximo/oslc/script/<script name>?cc=USA

In this example scenario, lets name this script as countryapi. Also make sure that you send in

the authentication information as part of this request - like using the maxauth header or for test

purpose the _lid=<user>&_lpwd=<password> query params (for maximo native authnetication)

or the FORM/Basic auth tokens (for LDAP based auth).

Next we need to create a non-persistent object for storing this country code/country name pairs

as maximo lookups only work off of MboSets. We can name the mbo COUNTRY with 2

attributes - name, description.

Next we write the “retrieve list” script.

importPackage(java.util)

importPackage(Packages.psdi.server)

var ctx = new HashMap();

ctx.put("url","http://localhost:7001/maximo/oslc/script/countryapi?_lid=wilson&_lpwd=wilson");

service.invokeScript("LIB_HTTPCLIENT",ctx);

var jsonResp = ctx.get("response");

var countries = JSON.parse(jsonResp);

var countriesSet = MXServer.getMXServer().getMboSet("COUNTRY", mbo.getUserInfo());

for(var cnt in countries)

{

var cntMbo = countriesSet.add();

cntMbo.setValue("name",cnt);

cntMbo.setValue("description",countries[cnt]);

}

listMboSet = countriesSet;

srcKeys=["NAME"];

targetKeys=["ADDRESS5"];

Note that, we have used the library script LIB_HTTPCLIENT to make the rest api call to get the

list of countries. Note the use of the implicit variable “listMboSet” which holds the mboset to be

used in the lookup. The target mbo attribute where the value is going to set has a different name

(address5) than the src attribute name (“name” in country mbo). So we leverage the implicit vars

srcKeys and targetKeys to let the system know where to set the selected value.

MIF (Invoke Channel) Exits Using Scripts

The use case here is to set the city and state in the Organiztion application->Address tab when

the user enters the zip code.

Assume we have an external rest api that looks like below

GET <zip to city uri>?zips=<zip code>

And the response is a json that returns the city and state for the <zip code>. We want to invoke

that api when the user enters the zip code and then tabs out (of attribute Address4). To do this,

we would need to create an Object Structure for the Address mbo - say named MXADDRESS.

We are then going to set-up an invoke channel with an HTTP endpoint that has the url set to the

<zip to city uri>. We are going to set the zips query parameter dynamically in the exit scripts.

Make sure that you set the “process response” check box and set the request and response

Object Structure to MXADDRESS.

We will then create the external exit scripts for both the request and response handling. We will

use the Create Scripts for Integration menu option from the Autoscript application to create the

request and response exits for Invoke Channel. For both cases, we are going to choose the

“External Exit” option. The request (INVOKE.ZIPCHANNEL.EXTEXIT.OUT) exit (in py) will look

like below:

from java.util import HashMap

from psdi.iface.router import HTTPHandler

from psdi.iface.mic import IntegrationContext

from psdi.iface.mic import MetaDataProperties

epProps = HashMap()

urlProps = HashMap()

zip = irData.getCurrentData("ADDRESS4");

urlProps.put("zips",zip)

epProps.put(HTTPHandler.HTTPGET_URLPROPS,urlProps)

IntegrationContext.getCurrentContext().setProperty(MetaDataProperties.ENDPOINTPROPS, epProps)

The response (INVOKE.ZIPCHANNEL.EXTEXIT.IN) exit (in js) will look like below

importPackage(Packages.psdi.iface.mic);

var resp = JSON.parse(erData.getDataAsString());

var irData = new StructureData(messageType, osName, userInfo.getLangCode(), 1, false, true);

irData.setCurrentData("ADDRESS3", resp.zips[0].state);

irData.setCurrentData("ADDRESS2", resp.zips[0].city);

Note the response json is assumed to be like this (based on a sample popular rest service out

there):

{

“zips”:[

{

“state”:”Blah”,

“city”:”Blah1”

….

}

]

}

This script could have been optimized a little bit more for the use case given. For example we

could have directly set the json data to the Mbo - something that we can do only in case of

Invoke Channels.

importPackage(Packages.psdi.iface.mic);

var resp = JSON.parse(erData.getDataAsString());

var mbo = IntegrationContext.getCurrentContext().getProperty("targetobject");

mbo.setValue("address3",resp.zips[0].state);

mbo.setValue("address2",resp.zips[0].city);

service.raiseSkipTransaction();

Note here how we got the “mbo”. We got it from the IntegrationContext “targetobject” property,

which stores the reference of the Mbo to which we are going to set the json response to. Setting

the response can be done by MIF using the irData - as we have shown in our previous

example. In this example we show how you can do it without needing to create the irData. We

use the resp json object to get the state and city and set it directly to the “mbo”. Then we can

just skip the rest of MIF processing by asking the service object to skip the transaction. We can

use this in other forms of inbound exits - like the user exit and the external exit for Enterprise

services. Make sure that you set the data yourself to the mbos that you want and the skip the

rest of MIF processing. Also please note that this IntegrationContext “targetobject” property is

only available for exits in InvokeChannel.

Next we will write an Attribute LaunchPoint script for the Address4 attribute (zip code attribute in

Address object) to invoke this channel when the zip code value is set.

service.invokeChannel("zipchannel")

As you can see, we use the service variable to do this call. This will call the channel, which in

turn goes through the request and response exits.

Maximo Warnings and Errors

Maximo errors in 75 version used to get done by setting these variables - errorgroup, errorkey

and params. Setting those did not stop the execution, as the execution will continue till the end

of that script and then it will check the flags and throw the error. Often this may not be the

expected behavior. To fix this issue, in 76 we added utility methods to the global “service”

variable to throw errors and warnings. The example below should replace what you have been

doing with errorkey and errorgroups in 75.

service.error(“po”,”novendor”)

Showing a warning message can be done fairly easily with automation scripts. Say we want to

show a warning to the users when they save a PO with no lines.

To do this, first we need to create a warning message from the messages dialog in the db config

application. Say we name it "nolines" under the message group "po".

We create an Object Launch Point - Save event on Add and Update. The code below (py)

validates the POLINE count and uses the "service" global variable to show the warning.

if mbo.getMboSet("POLINE").count()==0 and interactive:

service.setWarning("po","nolines", None)

Interfacing with YN or YNC Dialogs

YNC (aka Yes, No, Cancel) interactions can now be designed using automation scripts. We

need to do some prep work before writing the script. First we need to define a message that

support the YNC interaction. This is similar to what we have done in defining the warning

message group and key before. Make sure you have the message as informational (I) and

support the yes and no buttons.

The use case below launches a yes/no dialog when the priority is set to 1. It asks the user if the

he/she wants to set a default vendor for this. If the user select “yes” the script will set the default

vendor A0001 and will mark the vendor field as required. If the user selects “no” vendor is

marked as not required.

The script shown below associates the var v with vendor and is added as an Attribute Launch

point script with “action” event. “assetpr” has been defined in the “asset” group as an

informational message with buttons Y and N enabled.

def yes():

global v,v_required

v = "A0001"

v_required = True

def no():

global v,v_required

v_required = False;

def dflt():

service.log("dflt")

params = [str(priority)]

service.yncerror("asset", "assetpr",params)

cases = {service.YNC_NULL:dflt,service.YNC_YES:yes,service.YNC_NO:no}

if interactive:

if priority==1:

x = service.yncuserinput()

cases[x]()

MIF Object Structure Scripts:

Its often required to manipulate the XML/JSON data processing in the Integration Object

structure layer. Take the case of outbound messages where the Object structure processing

would serialize the Mbo structure into a XML or json message. Some of the common use cases

are listed below:

● To override certain values in the xml or json based on the current mbo state.

● Another use case maybe to skip some mbos or attributes based on certain conditions.

You may think that you can do it in exits. However exits are further down the line in outbound

processing and may-not be very efficient to let the data get serialized to json/xml and then get

dropped. The below example in py shows the 3 functions one can define to interface with the

Object structure serialization process. The 3 methods are

Function name Purpose

overrideValues(ctx) Used for the purpose of

overriding the mbo attribute

values as they get serialized

to json/xml

skipMbo(ctx) Used for the purpose of

skipping the Mbos before

serialization. These skipped

mbos will not be part of the

xml/json generated.

skipCols(ctx) Used for skipping mbo

attributes before serialization.

Note that all these functions take in a common parameter ie the ctx - which is an object of type

psdi.iface.mos.OSDefnScriptContext. This object contains the state of the serialization and

helps the script code to figure out at what level (in the Object structure hierarchy) we are at the

point of that call back.

The code below can be applied to the MXPO object structure.

def overrideValues(ctx):

if ctx.getMboName()=='PO' and ctx.getMbo().isNull("description")==True:

ctx.overrideCol("DESCRIPTION","PO "+ctx.getMbo().getString("ponum"))

def skipMbo(ctx):

if ctx.getMboName()=='PO':

if ctx.getMbo().getMboSet("poline").count()==0:

ctx.skipTxn()

elif ctx.getMboName()=='POLINE':

if ctx.getMbo().isNull("itemnum"):

ctx.skipMbo()

def skipCols(ctx):

if ctx.getMboName()=='POLINE':

if ctx.getMbo().getBoolean("taxed")!=True:

ctx.skipCol(['tax1','tax2','tax3','tax4','tax5'])

The points to note here are:

● In the override cols note that we are overriding the description only when the object is

PO and the description is null. This will not change the PO mbo description. It will just

have the description in the json/xml.

● In the skipMbo thing we are will skip the poline processing if line is a service line. We are

also going to skip the PO itself if there are no lines for it.

● In the skipCols we check for the mbo in process to be POLINE and if taxed is set to

false, we skip all the tax attributes.

● These ctx vars all extend the “service” global var and hence all apis available there can

be leveraged here - namely to throw error, call workflow, or to log real time etc.

Note that you can write any combination of these 3 functions in your script code. At least one is

required.

To create this script we need to use the action - Create Integration Scripts from the scripting

application. We need to choose the Object structure name (in this case MXPO) and the fact that

we are using the outbound processing. Next all you need to do is create the script code and

save. When you save, the script is auto-magically attached to the Object Structure processing.

In a similar fashion we have a sample for inbound processing. Again as the use case goes - it

makes sense to use exit scripting when you want to transform the message payload. But when

the message payload is in the format that Maximo consumes and all you want to do is control

how the values get set to the Mbo, you should consider using the Object Structure inbound

processing scripts. It has a tonne of call-back methods. I am going to only talk about the one I

think we are going to use most. I will continue to add the documentation for the other callbacks

over the next few months. The example use case deals with setting attribute values in inbound

MIF processing in a certain order. Often we see this requirement in the customer

implementations where certain mbo attributes needs to get set in a certain order with real time

validations. As you already know, MIF inbound would always set attributes in the data dictionary

attribute order and will always set it using a DELAYVALIDATION model where the attribute

validations and actions are batched up till all the attribute for that mbo has been set. So say you

want to set the asset priority and asset type with real time validations and one after the other.

This is required - say because these attributes actions need to set some other attributes which

are key to subsequent validations. We cannot do that by default in MIF. The known way to do

that would be to set these 2 attributes as restricted in the Object Structure. Then we would write

an automation script to set these 2 attributes one after the other with real time validations. The

code below will do just that.

from psdi.mbo import MboConstants

def afterMboData(ctx):

ctx.log("afterMboData")

if ctx.getMbo().isNew():

ctx.getMbo().setValue("priority",ctx.getData().getCurrentDataAsInt("PRIORITY"))

ctx.getMbo().setValue("assettype",ctx.getData().getCurrentData("ASSETTYPE"))

else:

ctx.getMbo().setValue("priority",ctx.getData().getCurrentDataAsInt("PRIORITY"),MboConstants.DELA

YVALIDATION)

ctx.getMbo().setValue("assettype",ctx.getData().getCurrentData("ASSETTYPE"),MboConstants.DELAY

VALIDATION)

This can be attached to the MXASSET object structure in the same way as described before,

the only difference being that this time you would choose the “Inbound Processing” instead of

outbound processing.

Note that we used the call back method afterMboData(ctx).

This is specifically used for setting data into the Mbo after the MIF framework has done setting

for that Mbo. So when we get the ctx.getMbo(), MIF has already set the attribute values from

XML/JSON with DELAYVALIDATION to this mbo. But with DELAYVALIDATION those mbo

attributes validations/actions have not yet been invoked. This call-back happens right before

that.

We have quite a few such callbacks for inbound processing - each serving its own purpose.

Besides these there are script points for rest api actions and rest api queries for Object

structure. We are covering those in details in the REST/JSON api documentation.

Event Filters for Publish Channels

Publish Channels provide the outbound integration backbone of Maximo. They can either be

event enabled such that when some Mbo’s change their state (ie they get added, updated or

deleted) these channels can trap those events and publish a message to the outbound JMS

queues. The messages then get picked up from those queues and get pushed to their final

destination (using the endpoint/handler framework).

Often we have the need to filter the events such that we want the channel to publish the

message only when a certain state condition is met. This used to get achieved by writing event

filter classes in java. With 76 this thing can be scripted. To create a Filter script, use the “Script

For Integration” -> “Publish Channel” -> “Event Filter” option in the scripting application. Below is

an example of such a filter script on the MXWODETAILInterface publish channel.

If service.getMbo().isModified(“status”) and service.getMbo().getString(“status”)==”APPR”:

evalresult = False

evalresult = True

Note that the evalresult being set to False will indicate the event to be published. So effectively

this script will filter all events that did not change the status to APPR for the workorder. You

might wonder that this could have been done using a channel rule or channel exit class. That is

definitely possible for simplistic rules, although this comes a steep price. The rules get

evaluated after the event has been serialized and then passed to the exit layer for evaluation.

By that time these rules get evaluated you already paid the price of serialization. So why do that

costly process when you can skip it before any processing has been done?

Note that the event filters apply to event based publish channels. Publish channels that are

used to export data cannot leverage these filters.

Also note that there is a bug which causes the “mbo” variable to be not accessible directly here.

You will have to use the service.getMbo() to get access to the event Mbo here. We will fix this

bug in the 7609 release of Maximo.

Before Save, After Save and After Commit Event in Object

Launch Point

75 Scripting supported only the “Before Save” events in the Object Launchpoint. Although that is

the most common place where we do customizations, we often see the need to attach some

customizations at the after save and after commit events - mostly when we are doing some

actions that need to be done after the Mbo has been saved or committed. After save is the

phase when the Mbo’s sql statement (insert/update/delete) has been fired, but the commit has

not happened. After commit stage happens when the Mbo in the transaction has been

committed ie the database commit has been successfully executed.

After save is events are good for writing to an external storage. For example - Maximo

Integration Events are processed at the after save event. In this phase if the write fails, we can

always roll back the Mbo transaction. Also at this stage - all Mbo validations have passed and

hence the chances of failure due to Maximo business logic failure is very low.

After commit events are good for actions that require your Maximo data to be commited. For

example you may want to send an SMS only when an Asset has been reported “BROKEN”.

You can access the “mbo” variable at all of the script points here.

Controlling Mbo Duplicate using Scripting

Say we want to hold the reference of Workorder from which we created (copied from) a new

workorder. We would create a custom attribute called “copiedfrom” in the Workorder object. We

would then need to customize the duplicate process of the Workorder to store the “wonum” of

the original workorder in the “copiedfrom” attribute of the newly duplicated mbo. To do this, we

will create a vanilla script with the name <Mbo name>.DUPLICATE. For example in this case we

will name it WORKORDER.DUPLICATE to intercept the duplicate event. The name <mbo

name>.DUPLICATE will intercept the duplicate event for the mbo named <mbo name>. We will

have 2 implicit variables - mbo and dupmbo to leverage in this script. The mbo variable will

point to the original mbo and the dupmbo will point to the newly duplicated mbo. We can now

set the “copiedfrom” attribute using the script code below

dupmbo.setValue(“copiedfrom”,mbo.getString(“wonum”))

This script is called after all duplicate logic is executed in the Mbo framework. Effectively you

can write logic in this script using both the mbo and dupmbo to control what gets duplicated as

well as do some post duplicate actions (like the sample above).

CanDelete and CanAdd in Object Launch Point

We can control whether we can add or delete a Mbo using scripting Object Launchpoint “Allow

Object Deletion” and “Allow Object Creation”.

Can Add

This is an Object Launch Point - "Allow Object Creation" where you can control whether you can

add a new Mbo, given the current

state of the system. This point pairs up with the canAdd callback that we have in the MboSet

framework.

Lets take a use case where we want to validate that a POLINE can be added to a PO only when

the PO has the vendor information set.

The script code to do that is shown below:

if mboset.getOwner() is not None and mboset.getOwner().getName()=="PO" and

mboset.getOwner().isNull("vendor"):

service.error("po","novendor_noline")

Note that here, there is no implicit variable called "mbo" as the launch point is invoked before

the Mbo is created. At that point

all you have is the MboSet (implicit variable "mboset") for the POLINE.

If you are wondering why this cannot be done using the init Object launch point, the answer is

that its little too late. At that point the Mbo has

already been created and added to the set. Rejecting it at the point would have not helped.

Also note the usage of the global "service" variable here to throw the error in real time. This

effectively replaces setting the errorgrp and

errorkey variables to throw error.

Can Delete

Similar to the "Can Add", this Object Launch Point helps validate whether a Mbo can be deleted

or not. Going with the POLINE object, say we want to enforce a validation that the line should

not be deleted, if the PO is of priority 1.

if mbo.getOwner() is not None and mbo.getOwner().getName()=="PO" and

!mbo.getOwner().isNull("priority") and and mbo.getOwner().getInt("priority")==1:

service.error("po","nolinedelete_forpriority1")

Note that in this case, the mbo is already there (which we are trying to validate for deletion) and

hence we can leverage implicit variable mbo to do the validation.

MIF Endpoint Scripts

We can write MIF endpoint handlers using automation script. For example say we want to write

a handler for sending emails. This is something that is not there out of the box in Maximo. The

steps to do for this would be:

● Use the Add/Modify handlers action from the endpoint application to add a new handler.

We are going to name is scripted and we are going to set the class name to be

com.ibm.tivoli.maximo.script.ScriptRouterHandler.

● We are now going to create a new endpoint for that handler Endpoints app.

● We set the handler to be scripted.

● We need to set the “script” property to the name of the script that we are going to write.

For example say we name it “emailme”.

● We now got to create a script named emalme.

The script code can be as simple as

from psdi.server import MXServer

from java.lang import String

MXServer.getMXServer().sendEMail( to,from,subject, String(requestData))

Note in here, we can define the from and to as literal variables in the script and then set the

email addresses there. We can also define another literal variable called subject to define a

static subject for the email like “Email from Maximo”. We can make it more dynamic and fancy

by getting the to email from the data etc.

Scripting Cron Tasks in Maximo

Starting 76 we can write scripts for cron tasks too. It follows the same principle as the endpoint

handler. We have a cron class - com.ibm.tivoli.maximo.script.ScriptCrontask that helps achieve

this. We need to register that class as a new crontask in the crontask application. We then will

create an instance of it and set the “scriptname” property to the name of the script that we want

this task to run. Next we need to set the schedule and last but not the least - need to define the

script. We are then good to go running this newly defined script with the crontasks. It is no

different than any other crontasks and you can activate/deactivate the task or change the

schedule as you would do in any other cron task.

If you are wondering why we need this when we already have an escalation which is a crontask

that works of a given Maximo object. The answer is simple - we often need to schedule jobs that

are not just based off Maximo objects. So in those cases, this crontask based scripts would

come in handy.

Below we develop a cron script to log an error when the count of maxsessions exceeds a

configured limit.

Step 1.

We will first need to register the script crontask definition. For that we will use the Crontask app

and the class name would be com.ibm.tivoli.maximo.script.ScriptCrontask. We will also need to

create a cron task instance.

In that instance we can set the frequency to 30 sec.

We can set the SCRPTNAME param value to SESSIONCOUNT. This implies that we will create

a script names SESSIONCOUNT to do the job.

Step 2.

Next we create the script SESSIONCOUNT. A sample script in py is shown below. Note that the

“runasUserInfo” implicit var will hold the userinfo for the cron job.

from psdi.server import MXServer

cnt = MXServer.getMXServer().getMboSet("maxsession",runAsUserInfo).count();

if cnt>1:

service.logError("cnt exceeds 1::"+str(cnt))

Step 3.

Next we activate the crontask instance. You need to wait a minute or so for the task to start.

You can use another browser instance to login to maximo – just to create another maxsesson

record. That is when you can see that log that says "cnt exceeds 1::”.

Step 4.

Note that we hard coded the count limit. In this step we can softcode this by leveraging the

SCRIPTARG parameter in the Crontask. We can set that to 1. We need to now save and reload

the task.

Step 5.

Next we modify the script to leverage that SCRIPTARG parameter using the implicit var called

“arg” as below.

from psdi.server import MXServer

argval = int(arg)

cnt = MXServer.getMXServer().getMboSet("maxsession",runAsUserInfo).count();

if cnt>argval:

service.logError("cnt exceeds 1::"+str(cnt))

Step 6.

You can now repeat step 3 to see if you get the error log.

Adding validation to the virtual (aka Nonpersistent) mbos (on execute

- 7609 feature).

In this sample we are going use the change status dialog from the Asset Application to validate

if the memo is filled in when the Asset status is set to BROKEN.

We will write a Object launch point script – MEMOREQD, which will validate on OK button press

of the dialog that memo is required for status BROKEN. The event will be “save/add”. This will

map to the “execute” call back for Nonpersistent mbos. The object name would be

ASCHANGESTATUS. We used Python for this sample.

if mboset.getMbo(0).getString("status")=="BROKEN" and

mboset.getMbo(0).isNull("pluscmemo"):

service.error("memo","reqd")

Note the use of the “mboset” variable, which is leveraged to get the current Mbo at 0 index. Now

try the change status dialog to see if it throws the memo#reqd error when you do not specify the

“memo” for BROKEN.

Leveraging the mbo.add() method in Scripts

It’s a common thing to conditionally set some defaults when a new mbo gets added. We have

so far leveraged the Object Launchpoint Init event to do that. We would need to check for onadd

flag there to make sure we are adding a new mbo and then set the attribute values. Another

way to do that would be to create a script named <mbo name>.ADD. This will automatically add

the script to the “add” callback right after the mbo add method execution has happened and

right before the init() method gets called. You can use this script to now set default values

conditionally. A sample script - ASSET.ADD shows how to default installdate to current date

only when the asset type is PUMP.

from psdi.server import MXServer

If mbo.isNull(“assettype”) == False and mbo.getString(“assettype”) == “PUMP”:

mbo.setValue(“installdate”,MXServer.getMXServer().getDate())

MMI Script

Maximo Management Interface provides a set of apis (REST/JSON apis) that helps a system

administrator gets some insights on the state of the maximo runtime. The root url for that api is

GET /maximo/oslc

And then you can dive deeper into each server (cluster members) using the /oslc/members api.

We provide a bunch of apis that can help you introspect the servers. But of course we do not

have all the apis that you will ever need. Fortunately there is a easy way to dynamically add

these apis using automation scripts. Below I will provide a sample use case (a real one) that

needed us to leverage that feature. Say you need to find out the servers locale as you trying to

debug some locale related issue. You can quickly write up a automation script and serve it up

as an MMI api using the MMI app - available from the Logging app action in Maximo - Monitor

Environment Information. One of the cool aspects of MMI apis being - it gets distributed in every

member server and using the MMI api framework - one can invoke these scripted apilets on

each server.

The sample script would look like below

from java.util import Locale

br.setProperty("syslocale",Locale.getDefault().toString())

Lets name it SYSTEMLOCALE. Note the usage of the implict variable “br” that has apis like

setProperty(propname,propvalue) and getProperty(propName). The script figures out the

system default locale and sets it to the br with a property name of “syslocale”. Now we can add

it to the MMI set of apis using the MMI dialog from the logging application.

The MMI framework links this script with URL like below

GET /oslc/members/{member id}/systemlocale

Processing JSON data with scripting:

JSON processing can be done natively using the jdk built-in js engine - whether its Rhino or

Nashorn. A sample code below shows how to process json from say a rest api invocation using

an MIF HTTP endpoint. A sample json that we get by invoking a popular zip code rest api

(zippopotam) is shown below

{

post code: "01460",

country: "United States",

country abbreviation: "US",

places:

[

{

place name: "Littleton",

longitude: "-71.4877",

state: "Massachusetts",

state abbreviation: "MA",

latitude: "42.5401"

}

]

}

The js code below shows how we can parse the json above and get the state and “place name”

(city):

resp = service.invokeEndpoint("ZIPEP",props,"");

var zipData = JSON.parse(resp);

city = zipData.places[0]["place name"]

state = zipData.places[0]["state abbreviation"]

A similar code in python (assuming that we are not using the python json libraries as they dont

come by default in the jython jar that we ship) would look a little bit involved as it uses Maximo

internal libraries to do the same:

from org.python.core.util import StringUtil

from com.ibm.tivoli.maximo.oslc import OslcUtils

resp = service.invokeEndpoint("ZIPEP",props,"")

resp = String(resp)

zipData = OslcUtils.bytesToJSONObject(StringUtil.toBytes(resp))

city = zipData.get("places").get(0).get("place name")

state = zipData.get("places").get(0).get("state abbreviation")

.

Invoking workflow from inbound MIF:

This is a common ask - how do I invoke a workflow after my mbo is inserted or updated using

MIF. The questions comes up often as the current UI framework allows such a feature to

automatically invoke workflow on save of the application mbo. Below is a sample python

example that does invoke a workflow that will create an assignment when a new workorder is

created using MIF inbound.

To do this we will write an Object Structure script that will invoke the workflow. We will use the

new Integration Scripts action from the Scripting application. Select the options – Object

Structure -> MXWODETAIL (Object Structure name) ->Inbound Processing.

from psdi.server import MXServer

def afterProcess(ctx):

mbo = ctx.getPrimaryMbo()

if mbo.isNew():

MXServer.getMXServer().lookup("WORKFLOW").initiateWorkflow("WOSTATUS",mbo)

Note the use of the “ctx” var – which is same as the global implicit var “service” but with added

functionality for Object Structure processing.

A simple way to test this would be to use the REST json api:

POST http://host:port/maximo/oslc/os/mxwodetail?lean=1

{

“wonum”:”MYLABWO11”,

“siteid”:”BEDFORD”,

“description”:”Invoke workflow test”

}

Once we POST this json, it will invoke the script and will create an assignment for the logged in

user. You can verify that from the start center assignments portlet.

If interested, you can now enhance the script to make sure that this workflow initiation is only

done for transactions that have the status as WAPPR (you can use the ctx.getPrimaryMbo() to

check that).

Java 8 and Nashorn engine:

Some of the above example is written using the jdk 7 based rhino js engine. In jdk 1.8, the rhino

engine has been replaced with the Nashorn (V8) engine. For example the importPackage

command will not work there. You would need to use the JavaImporter function to do the same

in Nashorn. You can look into this stackoverflow link for more details on what all changed from

Rhino to Nashorn that may impact your script code in js.

http://stackoverflow.com/questions/22502630/switching-from-rhino-to-nashorn