Showing posts with label maximo. Show all posts
Showing posts with label maximo. Show all posts

Tuesday, July 1, 2025

How to use Maximo HTTP Handler Exit Automation Script for Endpoint Customization

IBM Maximo's Integration Framework allows us to customize HTTP Endpoints using Automation Script Call Back Exit methods.

An HTTP Handler Exit is a script-based customization hook that lets us to intercept and modify HTTP requests or responses for Maximo Endpoints.

Below are the supported callback functions and their use cases:

Call Back Function

Use Case

getUrl(req)

Dynamically modify the target URL based on environment

for example, if you want to append a resource id in the existing URL

urlProps(req)

Set URL parameters such as access key from System property or any record ID like WONUM for query

headerProps(req)

Set authentication token as header param from an external URL

processResponse(resp)

Set Maximo error for any response code or response body message

Read an Unique ID from response and update it in MBO

req object is implemented by psdi.iface.router.ScriptHTTPReq

resp object is implemented by psdi.iface.router.ScriptHTTPResp

How to Configure the HTTPEXIT Property:

  • Create an End point for HTTP handler
  • Set HTTPEXIT property to the value script:{script name}, for example, script:HTTPEXIT where HTTPEXIT is an automation script name


  • Ensure the script exists as an Automation Script without a launch point.
  • Enable the flag "Allow Invoking Script Functions?" to enable call back functions to work in Automation Script. It's editable only at the time of creation. 


from psdi.iface.mic import MicUtil
from psdi.util import MXApplicationException
from com.ibm.json.java import JSON
from java.lang import String

def urlProps(req):
    propertyValue = MicUtil.getProperty('mxe.int.customkey')
    req.addUrlProp('Access-Key',propertyValue)
	
def headerProps(req):
    req.addHeader('Content-Type','application/json')
	
def getUrl(req):
    customURL = req.getURL()
    customURL = customURL + "uniqueID"
    req.setUrl(customURL)
	
def processResponse(resp):
    if resp.getResponseCode() > 201:
        stringData = String(resp.getData(), "UTF-8")
        respjson = JSON.parse(stringData)
        params = [respjson["message"]]
        resp.setError(MXApplicationException('iface','customerrmsg',params))
        # iface, customerrmsg maxmessage must be available with {0} as content 

Thursday, December 21, 2023

Maximo Filter data using LOOKUPS whereclause tag

What ? Apply filter/whereclause on lookup values 

Why ? Easier to apply a condition on a list of values without a table domain
Some Maximo fields like WORKORDER.WORKTYPE have a field level class and adding a table domain would add more complexity for applying a list where condition. Instead a custom lookups.xml configuration in application designer is simple approach to achieve it.

How ?  Sample use case: Display items in Work Order plans tab that are in ACTIVE status or PENDOBS (Pending Obsolesce)  status having current balance greater than zero.

Follow the below steps to filter values from a lookup
  • Export the LOOKUPS.xml from Application Designer
  • Open the xml file and add/modify new table section
  • Add whereclause attribute on the table tag
<table id="activeitem" inputmode="readonly" selectmode="single" 
whereclause="status='ACTIVE' or (status='PENDOBS' and exists (select 1 from inventory a, invbalances b 
       where a.itemnum = item.itemnum and a.itemsetid = item.itemsetid and a.itemsetid = b.itemsetid and a.location = b.location 
   and a.itemnum = b.itemnum and a.siteid = b.siteid and b.curbal &gt; 0 and a.status in ( 'ACTIVE', 'PENDOBS')))">

  • We can't use greater than and lesser than symbol directly in the lookup.xml, so replace them with equivalent characters.
         greater than  (>) -->  &gt;      lesser than (<) -->  &lt;
  • Condition in whereclause runs from Item table, so use item.itemnum inside the subquery
  • Import the LOOKUPS.xml

  • Link the custom lookup "activeitem" to the WOTRACK.xml application on the ITEM field under the Planned Materials section 

References :  limiting-lookups-using-whereclause

Tuesday, November 1, 2022

Configuring Websphere 7 for SAML SSO to authenticate Users in Maximo

This post details on how to configure Websphere 7.x version for SAML SSO to authenticate users in Maximo application

What is SAML ? 
  • Security Assertion Markup Language (SAML) is a standard for logging users into applications based on their sessions in another context
  • Most organizations already know the identity of users because they are logged in to their Active Directory domain or intranet, So they use this information to login into Maximo
  • SAML SSO works by transferring the user’s identity from one place (the identity provider) to another (the service provider)
  • When the user accesses the Maximo URL, the application identifies the user's origin, then redirects the user back to the Identity provider for authentication 
  • The user either has an existing active browser session with the identity provider or establishes one by logging into the identity provider 
  • The identity provider (AWS or Azure) builds the authentication response in the form of an XML-document containing the user’s username or email address, signs it using an X.509 certificate, and posts this information to the service provider
  • The service provider (Maximo) retrieves the authentication response and validates it using the certification and metadata
  • The identity of the user is established and the user is provided with Maximo access
 Steps to be followed:
1. Login to the operating system where WebSphere is installed
2. Install the default SAML ACS (Assertion Consumer Service) servlet supplied with WebSphere
  • If using Windows, open a command prompt
  • Navigate to WAS application bin directory (/opt/IBM/WebSphere/AppServer/bin on Linux/Unix or C:\Program Files\IBM\WebSphere\AppServer\bin on Windows)
  • We can install SAML ACS to a cluster or single-server. Please run the following command:    

Operating System

Command

Windows

wsadmin.bat -lang jython -f installSamlACS.py install clusterName 

(or)

wsadmin.bat -lang jython -f installSamlACS.py install nodeName serverName 

Linux/Unix

./wsadmin.sh -lang jython -f installSamlACS.py install clusterName

(or) 

./wsadmin.sh -lang jython -f installSamlACS.py install nodeName serverName

                where clusterName is the name of your WebSphere cluster ; nodeName and serverName are your node and server values respectively

  • If you are using a web server such as IBM HTTP Server in front of your application be sure that the newly installed EAR is targeted to the web server
    •  Login to the WebSphere Admin Console
    •  Using the left-hand menu go to Applications and then WebSphere enterprise  applications
    •  Click the link for WebSphereSamlSP
    •  Under Modules click Manage Modules
    • Confirm that both the cluster and the web server are assigned to the module

             If changes were required, generate and propagate the plugin configuration
    • Using the left hand side menu, go to Servers, then Server Types and click Web Servers
    • Click the checkbox next to your web server and click Generate Plug-in from the toolbar menu
    • Click the checkbox next to your web server and click Propagate Plug-in from the toolbar menu
    • Restart the web server
3. Create a new Security Domain 
  • Using the left-hand menu, select Security then Security Domains
  • Click New
  • Provide a name and description for security domain
  • Click OK
4. Confirm that Application Security is enabled
  • From the list of Security Domains, click the new domain you created 
  • Check the value next to Application Security. If the value is Enabled then you can continue on to step 5
  • Expand the Application Security section and select Customize for this domain
  • Enable the Enable application security checkbox and click Apply



5.  Configure a new Trust Association Interceptor

  • From the list of Security Domains, click the new domain you created
  • Expand the Trust Association section and select the Customize for this domain option
  • Click to enable the Enable trust association checkbox and click Apply
  • Click the Interceptors link under Trust Association
  • Click New
  • For the Interceptor class name enter com.ibm.ws.security.web.saml.ACSTrustAssociationInterceptor
  • Under Custom Properties enter the property name sso_1.sp.acsUrl with a value of your ACS URL 
  • Click New to add an additional property
  • Enter the name sso_1.sp.EntityID and provide a value for the SP entity ID and click OK


6.  Save settings and synchronize nodes

7.  Export SAML SP metadata

  • Navigate to the WAS application bin directory (/opt/IBM/WebSphere/AppServer/bin on Linux/Unix or C:\Program Files\IBM\WebSphere\AppServer\bin on Windows)
  • Launch the wsadmin tool

Operating System

Command

Windows

wsadmin.bat -lang jython

Linux/Unix

./wsadmin.sh -lang jython

  • Execute the following command 
AdminTask.exportSAMLSpMetadata('-spMetadataFileName sp_metadata.xml -ssoId 1 -securityDomainName DOMAINNAME')
    • DOMAINNAME --> should be the same name which is created on Step 3  
    • By default, metadatafile will be stored in this path "/opt/IBM/WebSphere/AppServer/profiles/ctgDmgr01"  
8. Share the Service Provider metadata to your IdP (Identity Provider like AWS or Azure Active Directory) with the following information:
  • Target URL of the application
  • IdP will provide its signing certificate inside the metadata file or request it separately and import it manually later
  • Please validate the certificate in the signature KeyInfo element of the assertion from IdP provider
9. After you received your IdP's sp_metadata.xml, ClientMaximo.cer and entityDescription file - Import them 
  • Launch the wsadmin tool using step 7
  • Execute the following commands
      • AdminTask.importSAMLIdpMetadata('-idpMetadataFileName idp_metadata.xml -signingCertAlias MyCertAlias -securityDomainName DOMAINNAME')
      • AdminConfig.save()
If the idp_metadata.xml file is not in the same path as the wsadmin tool, then you will need to specify the full path to the file.

The value for signingCertAlias can be any string; it will be used to identify the signing certificate in the WebSphere Trust Store so just choose a suitable name that is not already in the store (see Security > SSL certificate and key management > Key stores and certificates > CellDefaultTrustStore > Signer certificates for a list of keys already in the store)

DOMAINNAME - should be the same name which is created on Step 3
  • Exit the wsadmin tool and return to the WebSphere Admin Console
10. Verify TAI custom properties
  • Using the left hand menu, select Security and then Security Domains
  • Click the link to your security domain
  • Expand the Trust Association section and click the Interceptors link
  • Click com.ibm.ws.security.web.saml.ACSTrustAssociationInterceptor
 The following fields may be defined: 

Property

Value

sso_1.sp.acsUrl

value set in Step 5 - https://hostname/samlsps

sso_1.sp.EntityID

value set in Step 5 - https://hostname/

sso_1.sp.targetURL

https://hostname/maximo/webclient/login/login.jsp

sso_1.idp_1.certAlias

name of the certificate alias you provided in point 9

sso_1.idp_1.entityID

entity ID of the IdP which is provided in the IdP metadata file and will be automatically populated

sso_1.idp_1.singleSignOnUrl

URL endpoint for IdP authentication (automatically populated from metadata)

 
                  

sso_1.sp.filter – this is an optional property. We can filter out servers that can be exempted from using SSO. Usually, we enable SSO only for UI server, and filter out MIF/CRON/REPORT servers.

If you do not see the sso_1.idp_1.certAlias property then a certificate was not provided with the metadata file. We will need to obtain the certificate from the IdP and add it to WebSphere manually by going to Security > SSL certificate and key management > Key stores and certificates > CellDefaultTrustStore > Signer certificates and clicking Add.
 
Once imported, you will need to add a custom property to Trust Association Interceptor TAI   - sso_1.idp_1.certAlias and assign it the value of the new certificate alias you created

11. Finalize Security Domain setup
    • Using the left-hand menu select Security and then Security Domains
    • Expand User Realm, click the Customize for this domain radio button
    • Click Apply at the bottom of the screen and save changes
    • Go back to the Security Domain, expand User Realm (it should already be set to Customize) and click Configure... 
    • If you are not taken to the Trusted authentication realms - inbound page automatically then click the associated link in the lower right part of the screen (under Related Items)
    • Click the Add External Realm... button in the toolbar
    • Enter the value of the sso_1.idp_1.entityID from point 10 and click OK
    • Click Apply and save changes and return to the security domain configuration screen by following steps 
    • Click the Custom Properties link at the bottom of the screen
    • Add the following two properties:

Property

Value

com.ibm.websphere.security.DeferTAItoSSO

com.ibm.ws.security.web.saml.ACSTrustAssociationInterceptor

com.ibm.websphere.security.InvokeTAIbeforeSSO

com.ibm.ws.security.web.saml.ACSTrustAssociationInterceptor


    • Click OK and save changes
12. Assign security domain to servers or clusters
    • Assign server/cluster where WebSphereSamlSP.ear was deployed in point 2
    • Proceed to the security domain configuration screen as described in point 11
    • Under the heading Assigned Scopes expand the tree starting at Cell
    • Locate the server(s) or cluster(s) where you would like to enable SSO and click each one to enable it. If you are not using clusters then your servers will appear under the Nodes section. If your servers are in clusters then you must look under the Clusters section
    • Enable all appropriate servers click the OK button at the bottom of the page and save your changes

13. Restart application servers and web server to pick up configuration changes
14. Test SSO using the login URL provided by your IdP

Debugging
  • To debug issues with SAML, we need to enable trace logging on the server where the SAML ACS servlet has been installed
  • By default, WebSphere provides no feedback in the standard logs for most SSO issues 
  • If you are troubleshooting SAML in a cluster where multiple servers are running it’s recommended you stop all but one server to simplify diagnosing your problem
  • To enable trace logging for the server where the ACS servlet is installed, login to the WebSphere Admin Console and do the following
    • Using the left-hand menu select Servers then Server Types then WebSphere application servers

    • Locate the server where you installed the ACS servlet in point 2 of the Step-by-step guide and click it

    • Under Troubleshooting on the right side click Diagnostic trace service


    • Under Additional Properties click Change Log Detail Levels

    • Add the following log levels, separating each with a colon :  com.ibm.ws.security.*=all: com.ibm.wsspi.wssecurity.*=all: com.ibm.ws.wssecurity.saml.*=all:

    • Click OK and save your changes
    • Restart the server where you have enabled trace logging
    • Now test your SSO flow again and view the trace.log file in the log folder of your server for errors
    • The messages will generally give some indication of where the problem lies but you may need to find a proper person to escalate to if you cannot determine the problem 

Friday, March 18, 2022

Maximo Asset Hierarchy BIRT Report

Asset Hierarchy is used to define the physical or functional parent-child relationship of assets created within the Location application. 

The real advantage of developing such location/asset hierarchy is to easily locate an asset to perform work orders, grouping of assets under a location to represent plant systems, etc.,

OOB Maximo has Location Hierarchy by system report, but it doesn't have similar report to display the Asset Hierarchy. 

A report for asset hierarchy using ASSET and ASSETANCESTOR tables is available in gitlab asset_hierarchy.rptdesign

The flowchart describes the nested table structure used to design the hierarchy report.


                                               



Configure the dataSet with a Parameter to accept the value from the parent table design


Parent parameter should be linked to the assetnum of parent table


Always use - Auto Layout - Fixed Layout would cause the results go in separate pages.

Report Parameters need to be filled with attribute (siteid,location,assetnum) and lookup values (site,locations,asset) to let the user choose values from the lookup.
 

Report Output:

2) We can also build an Asset Hierarchy Data structure using a SQL query. But it won't be work in BIRT design file. 

SELECT

    level,  assetnum,  parent,  lpad (' ', level * 4, ' ') || assetnum,

    description,  location,  siteid

FROM asset

WHERE  siteid = 'BEDFORD'

START WITH siteid = 'BEDFORD'  AND parent IS NULL AND children = 1 

CONNECT BY PRIOR assetnum = parent


References:

Saturday, February 26, 2022

Forecast reports using Cross Tab feature in BIRT Maximo

Why Forecast is needed ? 
For planning of work or budgeting for long term operations, we need to forecast the upcoming work along with costs/labors/services/materials/tools associated with them.

What is Cross tab ? 
A Crosstab (or Cross Tabulation) is a table showing the relationship between two or more variables for quantitative analysis by showing the correlation change from one group of variables to another. It allows for the identification of patterns, trends and probabilities within data sets. Cross Tabulation is used across various industries, job roles and analysts. It benefits many people for forecasting cost or material requirement for the company.

Sample Requirement
I would like to create steps to develop a crosstab table in Maximo BIRT reporting functionality with a use case.; PM (Preventive Maintenance) Cost Forecast Reporting using Cross Tab feature.

Development steps
Create a report with predefined template – “Tivoli Maximo List Template”

Remove the “Detail Row” section from the template. Cross tab inserted into Detail section would result in duplicated rows, so it needs to be created in Header section.

 

Merge the cells in the Table header and insert the “Cross Tab” element into the table header


Construct the Data Set with all required output columns – this report shows all OOB fields to display the PM Cost forecast report with required Work Orders per month



Create a new Data Cube 

Associate the Data Set for Data Cube. Add Groups (Dimensions) for columns that we want to display in rows section and for column header (month in this case) of Cross Tab Section.

Summary field is the one which is at the right-hand side of Cross Tab element design.



Drag and drop the fields from Dimensions to rows section of the Cross Tab element.

Place the MONTH field from Dimension to column header section of cross tab

Place the Summarize field in the column field section. 



The design file is available in gitlab - https://gitlab.com/bysurendar/maximo/-/blob/master/reports/pmcostforecast.rptdesign

Output of the report:

Rows are the list of Preventive Maintenance records which have forecast generated for the given date range

Columns are months for which the forecast exists. 

Summarized values are the Work Orders that would be needed for a month to perform the Work.



Courtesy: Vijayabanu Pitachi 

References: 

https://www.ibm.com/docs/en/elo-mc/7.6.0?topic=tab-tutorial-creating-cross

https://www.youtube.com/watch?v=LKuCNuz67YA


Friday, October 22, 2021

Maximo Automation Scripting Best Practices for Performance

Automation Scripting is a feature in Maximo used to implement business logic using any JSR 223 specification compliant Scripting language

The automation script code gets compiled to Java bytecode, gets cached as part of Maximo runtime. When the script is invoked, it is cached bytecode that is executed by JVM in the same thread as other Maximo business logic written in Java. A poorly written code can cause performance issue, so we need to follow a few guidelines similar to Java customization.      

Cautious of having Multiple Scripts for Single Launch Point Event

Maximo allows to include multiple scripts for the same launch point triggering event. Maximo event topic is un-ordered map, where the events gets fired without any sequence. 
 
If you write script that need to be executed in specific order or need any pre-requisite actions on the same launch point, then it cause issues on the expected output.
 
Possible solution: Ensure that there is no dependency between scripts or combine them into a single script

Use of Single script for Multiple Launch points

We should try to create a single script where we need a generic business logic and attach it in multiple launch points. 

One such business case is to restrict description to 50 characters between Maximo and other external systems.

------------------------------------------------------------------------

strLen = len(mbo.getString("description")) ;

if(strLen > 50):

   service.error("system","DescriptionFieldLimit");

--------------------------------------------------------------------------

Above script can be attached to any MBO where description need to be restricted.


Choice of Launch point and Event

Launch points decide when the script need to be triggered. Choosing a right launch point can help avoid certain performance issue. 

Case 1: skipping an outbound integration message can be done using 2 launch points: User Exit Scripting or Publish Channel Event Filter. The best launch point to use is Event Filter because the skip action happens before the serialization of MBOs. Sample script for Event Filter to skip records based on status is below:

---------------------------------------------------------------------

If service.getMbo().getString("status")== "APPR":

evalresult = False

evalresult = True

--------------------------------------------------------------------

Case 2: Initialization of Attribute value can be set using Object Launch Point or Attribute Launch Point with Event - Initialize Value. We must use Attribute Launch Point, because Object Launch point can lead to performance issues when selecting a lot of MBOs in List tab, escalations or API actions. 

Naming Convention for Automation Script

As we know the launch points play an important role on the Scripting Performance. We need to have a way to identify the launch point of the script without navigating to the launch point tab & dialog in Scripting application.

The Name can be written in 

TABLE.SCRIPTTYPE.ATTRIBUTE.EVENT.DESCRIPTION

TABLE is WORKORDER, ASSET, PO, PR etc.,

SCRIPTTYPE can be Object, Library, Action, Attribute level, Integration and Custom Condition. 

ATTRIBUTE - Attribute name; It is used only for attribute launch point scripts 

EVENT - It's predefined triggering point. Before Save, After Save, After Commit, Attribute Initialize Value, etc.,

DESCRIPTION - can be any free text. You can ignore it based on your field length.
for example: WORKORDER.OBJECT.BEFORESAVE
WORKORDER.FLD.PRIORITY.MANDATE.INITVAL
COMMON.LIB.LIMITDESC

Avoid initialization Events from List tab

You can perform heavy initialization logic once the MBO is opened in Maximo Main tab. Doing the same action when it is loaded in List tab would cause poor response time on loading a lot of records. 

-----------------------------------------------------------------------------------------------------------------------

from psdi.common.context import UIContext

if UIContext.getCurrentContext() is not None and UIContext.isFromListTab() == False:

print ("the logic goes here")

-------------------------------------------------------------------------------------------------------------------------

Use MboSet.count() only once or isEmpty() instead

MboSet.count() calls sql script to Database every time it is executed in the script. 

In order to improve the performance, write the script once and store it in a variable. 

--------------------------------------------------------------------

cnt = mboset.count()

if cnt <= 1:

        service.log("skipping this as count is " + cnt)

-------------------------------------------------------------------

or use mboset..isEmpty() to check if the count is zero. isEmpty() will be called once if mboset is not initialized, any more calls to this function will be accessed from memory without hitting the database.

Close the MboSet opened from MXServer.getMXServer.getMboSet("")

Maximo framework will always release the mboset that are created by launch point mbo or related objects mbo.getMboSet().
 
But, for Mbosets opened using the server MXServer.getMXServer.getMboSet("") api, the programmer is responsible to close the mbosets. If it is not handled, it may lead to OutOfMemory Error. 

------------------------

try:

    xxxxxx

finally:

    mboset.cleanup()

-------------------------

Check if logging is enabled before writing log statements

As a thumb rule, write the print or log statements only after checking whether the logging is enabled or not. The file write operations would take reasonable amount of time to complete the execution of code. 

-----------------------------------------------------------------------------

from psdi.util.logging import MXLoggerFactory

logger = MXLoggerFactory.getLogger("maximo.script”);

debugEnabled = logger.isDebugEnabled()


if debugEnabled:

 service.log("MBOs saved in DB"+mboset.count())

-------------------------------------------------------------------------------

Prefer SQLFormat Class over String concatenation

If SQL query is necessary, you can leverage SqlFormat class for MBO data filtering instead of where clause with concatenated string using +

It helps in data type formatting across databases, parsing errors and improved security by avoiding SQL injection.

# Unsafe string concatenation 
from psdi.server import MXServer

whereClause = "orgid='" +mbo.getString("ORGID")+ "' and ticketid='"+mbo.getString("ORIGRECORDID")+ "' and siteid= '"+mbo.getString("SITEID")+"'" 

srSet = MXServer.getMXServer().getMboSet("SR",MXServer.getMXServer().getSystemUserInfo())

srSet.setWhere(whereClause)
srSet.reset()
--------------------------
 
# Use of SqlFormat
from psdi.mbo import SqlFormat
from java.util import Calendar

cal = Calendar.getInstance()
today = cal.getTime()
cal.add(Calendar.DATE, -30)
thirty_days_ago = cal.getTime()

sqlf = SqlFormat(mbo, "orgid = :1 and ticketid = :2 and siteid = :3 and statusdate > :4")
sqlf.setObject(1, "SR", "ORGID", mbo.getString("ORGID"))
sqlf.setObject(2, "SR", "ticketid", mbo.getString("ORIGRECORDID"))
sqlf.setString(3, mbo.getString("SITEID"))
sqlf.setDate(4,thirty_days_ago)

srSet = mbo.getMboSet("$SRLIST", "SR", sqlf.format())
-------------------------------


Avoid calling SAVE in middle of transaction
MBOs created or updated by a launch point script or by its related mboset are always part of the single transaction.i.e., committed into database by a single SAVE. 

If you create or update a MBO by MXServer.getMXServer().getMboSet("objectname"), those changes would be outside of the script execution. You need to add it by this code: 

------------------------------------------------------------------------------------------

newMboSet = MXServer.getMXServer.getMboSet("WORKORDER")

mbo.getMXTransaction.add(newMboSet)

------------------------------------------------------------------------------------------

In real time scenario, the chance of using this code is very rare and tricky too. 

References:

Scripting_Best_Practices_For_Performance.pdf

service-methods-automation-scripts.html