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