IBMDO YOU?Hi, I'm MBO!

Settings

Make the site feel at home on your screen.

Theme

Loading your theme preference.

Keyboard shortcuts

Open search from anywhere, then move through the results without leaving the keyboard.

Open settings
Ctrl,or⌘,
Open search
CtrlKor⌘K
Select a search result
↑↓
Open the selected result
Enter
Close an open dialog
Esc

I Build Maximo

Alter inbound Maximo MIF data with an object structure script

Use a beforeProcess object structure script to enrich inbound PO data before Maximo creates its business objects.

I Beat Maximo — confirmed solution

Enterprise service replacement rules handle many straightforward inbound transformations. Sometimes the value you need depends on a Maximo record, so the message must be enriched before the integration framework creates and validates its MBOs.

In this example, an external system sends a purchase order without SITEID. Each PO line contains a requisition number and line number, so an object structure automation script looks up the corresponding PRLINE and adds its site to the inbound PO and PO lines.

The better integration contract is for the sender to provide a valid site explicitly. Use a lookup like this only when the source cannot supply it and the relationship gives one unambiguous result.

The incoming message

The useful part of the message looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<SyncPOIFACE
  xmlns="http://www.ibm.com/maximo"
  creationDateTime="2019-04-03T11:02:42+01:00"
  baseLanguage="EN"
  messageID="7064815542857609687649"
>
  <POIFACESet>
    <PO action="">
      <ORGID>943</ORGID>
      <PONUM>102124045</PONUM>
      <STATUS>APPR</STATUS>
      <POLINE>
        <ORGID>943</ORGID>
        <POLINENUM>1</POLINENUM>
        <PRNUM>1579</PRNUM>
        <PRLINENUM>1</PRLINENUM>
        <SITEID />
      </POLINE>
    </PO>
  </POIFACESet>
</SyncPOIFACE>

The script must run before MBO creation because SITEID participates in record identity and validation. IBM provides beforeProcess(ctx) for logic that changes the inbound StructureData before the framework creates the business objects.

Create the integration script

In Automation Scripts, select Create > Script for Integration, then configure:

Setting Value
Integration type Object Structure
Object structure POIFACE
Direction Inbound Processing
Language JavaScript
Script name A clear site-specific name, such as POIFACE.SETSITE

Use the integration-script dialog rather than creating a normal launch-point script. Current Maximo versions store the object-structure association in the script configuration, so the script does not depend on an OSIN.<object structure> naming convention. Older environments may already use names such as OSIN.POIFACE; preserve that convention when maintaining an existing implementation.

A safer implementation

This script binds every query value with SqlFormat, closes each MBO set, uses PRNUM, PRLINENUM and ORGID as the lookup key, checks missing data, and prevents lines from different sites being placed on one PO.

var MXServer = Packages.psdi.server.MXServer;
var SqlFormat = Packages.psdi.mbo.SqlFormat;
 
function getRequiredValue(structureData, attribute, context) {
  var value = String(structureData.getCurrentData(attribute) || "").trim();
 
  if (!value) {
    throw new Error(context + " is missing " + attribute);
  }
 
  return value;
}
 
function findPrLineSite(userInfo, orgId, prNumber, prLineNumber) {
  var prLineSet = MXServer.getMXServer().getMboSet("PRLINE", userInfo);
 
  try {
    var where = new SqlFormat(
      "ORGID = :1 AND PRNUM = :2 AND PRLINENUM = :3"
    );
    where.setObject(1, "PRLINE", "ORGID", orgId);
    where.setObject(2, "PRLINE", "PRNUM", prNumber);
    where.setObject(3, "PRLINE", "PRLINENUM", prLineNumber);
 
    prLineSet.setWhere(where.format());
    prLineSet.reset();
 
    var prLine = prLineSet.moveFirst();
    if (prLine === null) {
      throw new Error(
        "No PRLINE found for " + orgId + "/" + prNumber + "/" + prLineNumber
      );
    }
 
    var siteId = String(prLine.getString("SITEID") || "").trim();
    if (!siteId) {
      throw new Error(
        "PRLINE " +
          orgId +
          "/" +
          prNumber +
          "/" +
          prLineNumber +
          " has no SITEID"
      );
    }
 
    return siteId;
  } finally {
    prLineSet.cleanup();
    prLineSet.close();
  }
}
 
function beforeProcess(ctx) {
  var data = ctx.getData();
  var poLines = data.getChildrenData("POLINE");
 
  if (poLines === null || poLines.size() === 0) {
    throw new Error("The inbound PO contains no POLINE records");
  }
 
  var headerOrgId = getRequiredValue(data, "ORGID", "PO");
  var headerSiteId = null;
 
  for (var index = 0; index < poLines.size(); index++) {
    var poLine = poLines.get(index);
    var lineContext = "POLINE " + (index + 1);
    var orgId = String(poLine.getCurrentData("ORGID") || headerOrgId).trim();
    var prNumber = getRequiredValue(poLine, "PRNUM", lineContext);
    var prLineNumber = getRequiredValue(poLine, "PRLINENUM", lineContext);
    var siteId = findPrLineSite(
      ctx.getUserInfo(),
      orgId,
      prNumber,
      prLineNumber
    );
 
    if (headerSiteId === null) {
      headerSiteId = siteId;
    } else if (headerSiteId !== siteId) {
      throw new Error("The inbound PO contains PR lines from different sites");
    }
 
    poLine.setCurrentData("SITEID", siteId);
    poLine.setCurrentData("TOSITEID", siteId);
  }
 
  data.setCurrentData("SITEID", headerSiteId);
}

For a production interface, replace the generic JavaScript errors with configured Maximo messages so the enterprise service receives a stable message group, key and useful parameters.

How the script works

beforeProcess(ctx) runs once for the inbound noun before its MBOs are created. ctx.getData() returns the mutable StructureData for that noun, while getChildrenData("POLINE") returns the line structures still waiting to be processed.

For each line, the script:

  1. Requires PRNUM and PRLINENUM rather than assuming they exist.
  2. Combines them with ORGID to identify the source PRLINE.
  3. Uses SqlFormat so message content never becomes executable SQL.
  4. Reads the source line's SITEID.
  5. Sets SITEID and TOSITEID on the inbound PO line.
  6. Verifies that every line resolves to the same header site.
  7. Sets the PO header SITEID after all lines pass validation.

The integration user supplies ctx.getUserInfo(), so its object and site permissions affect the lookup. Do not replace it with the system user merely to bypass an authorization failure.

Why the original lookup was unsafe

A query assembled like this should not be used with inbound data:

prSet.setWhere("PRNUM = '" + prNumber + "'");

Apart from SQL injection, PRNUM alone might not be unique across organizations or sites. Matching a PRLINE by description is even less reliable because descriptions can repeat or change. Bind values and use the actual business key available in the message.

Every set opened through MXServer.getMXServer().getMboSet() must also be released. Leaving sets open for each PO line can retain database resources and becomes expensive under integration load.

Test the transformation

Use a nonproduction external system or object structure service and test at least:

Input Expected result
One valid PR line Header and line receive its site
Several PR lines from the same site All records receive the same site
PR line does not exist Transaction fails with a controlled message
Missing PRNUM or PRLINENUM Transaction fails before MBO creation
Lines resolve to different sites Whole PO is rejected
Quote or other special character in an input Treated as data, never SQL
Integration user cannot read the source line Permission failure is visible
Reprocessed message Result remains consistent

Inspect the message-tracking record and the created PO. Confirm the header and every line have the expected site, no partial record was committed, and the source identifiers remain unchanged.

References

Find the fix

Search articles

Esc

Search titles, technical terms or error codes.