Infor XA SystemLink with Node-RED

This article explains how to use Infor XA SystemLink with Node-RED.
To understand this blog, you will need to have skills in SystemLink and Node-RED as well. This is not a tutorial for Node-RED. You should be familiar with Node-RED. We describe a very simple business case, an inquiry from item revision, but we hope it explains enough to use it as a starting point for own developments. Feel free to contact us for further questions.

You have any questions?

About Node-RED

Node-RED is a low-code programming tool for bringing together hardware devices, APIs and online services. It brings a browser-based flow editor that makes it easy to wire together flows by using a wide range of nodes in the palette.

Node-RED is Open Source and provided by the OpenJS Foundation. The flow-based programming was already invented in the 1970s by J. Paul Morrison. The first Node-RED project was started in 2013 as a side-project by two developers of the IBM’s Emerging Technology Services group. 

More information about Node-RED you can find also at nodered.org

Please note: Node-RED is a very powerful and free tool to connect systems. There are nodes available for many different systems, hardware and software. Node-RED has some limitations, so for your business cases, please be aware of these. For complex use cases and a seamless integration with Infor and external applications, we still recommend Infor OS as the better choice. And please apologize, some of the screenshots contain German content. We are using German browser settings.

Required nodes

You need to add some additional nodes to your standard Node-RED instance. Here is what you need:

  • jsonpath is used to extract content from the json-message
  • We made it fancy, so we are using a dashboard for this demo. Please add the needed nodes to your Node-RED instance.

XA NodeRed Dashboard

The flow

XA Node-RED flow

You can start the flow manually through the inject node or through the button on the dashboard. Next step is to build the complete System-Link request. To make it easier to handle, we build five components, which are joined at the end to one single request. I added the delays to simplify debugging (just add a connection to a debug node to the output of the delays). The join node Build SLR creates a string from the five components.

The SystemLink header is static:

msg.payload = "SystemLinkRequest=<!DOCTYPE System-Link SYSTEM 'SystemLinkRequest.dtd'><System-Link version='1.0' hostVersion='1.0' formatForDisplay='false' includeNewlines='false' useTimeZone='default' convertForSender='false'>";

The System-Login is hardcoded with credentials (please don’t tell anybody our top secret password):

msg.payload = "<Login userId='AMAPICS' password='AMAPICS' maxIdle='900000'  properties='com.pjx.cas.domain.EnvironmentId=DZ, com.pjx.cas.domain.SystemName=MJR400.MJRABER.LOCAL,com.pjx.cas.user.LanguageId=en'/>";

The SystemLink-Request header is static (parameters need to be adjusted for your context):

msg.payload = "<Login userId='AMAPICS' password='AMAPICS' maxIdle='900000' properties='com.pjx.cas.domain.EnvironmentId=DZ, com.pjx.cas.domain.SystemName=MJR400.MJRABER.LOCAL,com.pjx.cas.user.LanguageId=en'/>";

The SystemLink QueryObject is built from flow-variables:

msg.payload = "<QueryObject name='queryObject_ItemRevision_Default' domainClass='com.mapics.epdm.ItemRevision' includeMetaData='true'><Pql><![CDATA[SELECT unitWeight, weightUm WHERE site = '" + flow.get("site") + "' AND item = '" + flow.get("item") + "' AND revision = '" + flow.get("revision") + "' ]]></Pql></QueryObject>";

The flow variables can be set interactively from the input fields on the dashboard.

The EndRequest is static:

msg.payload = "</Request></System-Link>";

This is a very simple setup, to make it easy to understand. In a real world, it should be more sophisticated with dynamic content and encrypted credentials. In the next node Send post to XA, we send a http request to XA:

XA Node-RED http post

The URL looks like: http://your_ibm_i:36001/SystemLink/servlet/SystemLinkServlet
(to be adjusted)

In the next step (the xml node) we convert the xml to be able to filter the content in the jsonpath node. For this demo we converted text/string, numeric/number and Boolean.
Here are the jsonpath expressions and the functions to extract and format the System-Link response:

$.System-Link.LoginResponse[0].$.actionSucceededmsg.payload = Boolean(msg.payload);
$.System-Link.Response[0].QueryObjectResponse[0].DomainEntity[0].Property[1].Value[0]msg.payload = String(msg.payload);
$.System-Link.Response[0].QueryObjectResponse[0].DomainEntity[0].Property[0].Value[0]msg.payload = Number(msg.payload);
$.System-Link.Response[0].QueryObjectResponse[0].Exception[0].Message[0].Text[0]msg.payload = String(msg.payload);

For this simple example, we only returned four values:

  • actionSucceeded as boolean. In a real use case, it would make sense to evaluate hasErrors and hasWarnings from the response.
  • Weight and weightUM as number/string returns the value of these fields in the item revision record.
  • In case of exceptions, we show these error message as text.

You can download the full flow here:

[ { "id": "74891ed2e91a2d14", "type": "tab", "label": "XA SystemLink sample", "disabled": false, "info": "", "env": [] }, { "id": "f246af62619c7dc9", "type": "http request", "z": "74891ed2e91a2d14", "name": "Send Post to XA", "method": "POST", "ret": "txt", "paytoqs": "ignore", "url": "http://mjr400.mjraber.local:36001/SystemLink/servlet/SystemLinkServlet", "tls": "", "persist": false, "proxy": "", "insecureHTTPParser": false, "authType": "", "senderr": false, "headers": [], "credentials": {}, "x": 1200, "y": 260, "wires": [ [ "45c5a880bfd8896c" ] ] }, { "id": "d9e5acb45454516e", "type": "ui_button", "z": "74891ed2e91a2d14", "name": "", "group": "62444b9c83bfec34", "order": 7, "width": 0, "height": 0, "passthru": false, "label": "Query", "tooltip": "", "color": "", "bgcolor": "", "className": "", "icon": "", "payload": "", "payloadType": "date", "topic": "topic", "topicType": "msg", "x": 170, "y": 340, "wires": [ [ "dc7c37aee3718d67", "94125711f2b033f6", "bbe7c97d4bb797e7", "b310623c4cacf39f", "6895aaf02e254070" ] ] }, { "id": "5768b79b2cd34725", "type": "join", "z": "74891ed2e91a2d14", "name": "Build SLR (join)", "mode": "custom", "build": "string", "property": "payload", "propertyType": "msg", "key": "topic", "joiner": "", "joinerType": "str", "accumulate": false, "timeout": "", "count": "5", "reduceRight": false, "reduceExp": "", "reduceInit": "", "reduceInitType": "", "reduceFixup": "", "x": 1020, "y": 260, "wires": [ [ "f246af62619c7dc9" ] ] }, { "id": "9240d2f0255ab0e5", "type": "delay", "z": "74891ed2e91a2d14", "name": "Delay 100ms", "pauseType": "delay", "timeout": "100", "timeoutUnits": "milliseconds", "rate": "1", "nbRateUnits": "1", "rateUnits": "second", "randomFirst": "1", "randomLast": "5", "randomUnits": "seconds", "drop": false, "allowrate": false, "outputs": 1, "x": 790, "y": 180, "wires": [ [ "5768b79b2cd34725" ] ] }, { "id": "3776d52305f30c32", "type": "delay", "z": "74891ed2e91a2d14", "name": "Delay 200ms", "pauseType": "delay", "timeout": "200", "timeoutUnits": "milliseconds", "rate": "1", "nbRateUnits": "1", "rateUnits": "second", "randomFirst": "1", "randomLast": "5", "randomUnits": "seconds", "drop": false, "allowrate": false, "outputs": 1, "x": 790, "y": 220, "wires": [ [ "5768b79b2cd34725" ] ] }, { "id": "9754a115d287269e", "type": "delay", "z": "74891ed2e91a2d14", "name": "Delay 300ms", "pauseType": "delay", "timeout": "300", "timeoutUnits": "milliseconds", "rate": "1", "nbRateUnits": "1", "rateUnits": "second", "randomFirst": "1", "randomLast": "5", "randomUnits": "seconds", "drop": false, "allowrate": false, "outputs": 1, "x": 790, "y": 260, "wires": [ [ "5768b79b2cd34725" ] ] }, { "id": "0d1d9db27eddadda", "type": "delay", "z": "74891ed2e91a2d14", "name": "Delay 400ms", "pauseType": "delay", "timeout": "400", "timeoutUnits": "milliseconds", "rate": "1", "nbRateUnits": "1", "rateUnits": "second", "randomFirst": "1", "randomLast": "5", "randomUnits": "seconds", "drop": false, "allowrate": false, "outputs": 1, "x": 790, "y": 300, "wires": [ [ "5768b79b2cd34725" ] ] }, { "id": "ba832b4865973a3e", "type": "delay", "z": "74891ed2e91a2d14", "name": "Delay 500ms", "pauseType": "delay", "timeout": "500", "timeoutUnits": "milliseconds", "rate": "1", "nbRateUnits": "1", "rateUnits": "second", "randomFirst": "1", "randomLast": "5", "randomUnits": "seconds", "drop": false, "allowrate": false, "outputs": 1, "x": 790, "y": 340, "wires": [ [ "5768b79b2cd34725" ] ] }, { "id": "fb709929bad8b24e", "type": "inject", "z": "74891ed2e91a2d14", "name": "Manual start", "props": [ { "p": "payload" }, { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 190, "y": 180, "wires": [ [ "6895aaf02e254070", "b310623c4cacf39f", "bbe7c97d4bb797e7", "94125711f2b033f6", "dc7c37aee3718d67" ] ] }, { "id": "dc7c37aee3718d67", "type": "function", "z": "74891ed2e91a2d14", "name": "SL-Header (static)", "func": "msg.payload = \"SystemLinkRequest=<!DOCTYPE System-Link SYSTEM 'SystemLinkRequest.dtd'><System-Link version='1.0' hostVersion='1.0' formatForDisplay='false' includeNewlines='false' useTimeZone='default' convertForSender='false'>\";\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 450, "y": 180, "wires": [ [ "9240d2f0255ab0e5" ] ] }, { "id": "94125711f2b033f6", "type": "function", "z": "74891ed2e91a2d14", "name": "SL-Login (credentials hardcoded)", "func": "msg.payload = \"<Login userId='AMAPICS' password='AMAPICS' maxIdle='900000' properties='com.pjx.cas.domain.EnvironmentId=DZ,com.pjx.cas.domain.SystemName=MJR400.MJRABER.LOCAL,com.pjx.cas.user.LanguageId=en'/>\";\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 500, "y": 220, "wires": [ [ "3776d52305f30c32" ] ] }, { "id": "bbe7c97d4bb797e7", "type": "function", "z": "74891ed2e91a2d14", "name": "SLR-Header (static)", "func": "msg.payload = \"<Request sessionHandle='*current' workHandle='*new' broker='EJB' maxIdle='1000'>\";\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 450, "y": 260, "wires": [ [ "9754a115d287269e" ] ] }, { "id": "b310623c4cacf39f", "type": "function", "z": "74891ed2e91a2d14", "name": "SL-QueryObject (data from flow variable)", "func": "msg.payload = \"<QueryObject name='queryObject_ItemRevision_Default' domainClass='com.mapics.epdm.ItemRevision' includeMetaData='true'><Pql><![CDATA[SELECT unitWeight, weightUm WHERE site = '\" + flow.get(\"site\") + \"' AND item = '\" + flow.get(\"item\") + \"' AND revision = '\" + flow.get(\"revision\") + \"' ]]></Pql></QueryObject>\";\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 520, "y": 300, "wires": [ [ "0d1d9db27eddadda" ] ] }, { "id": "6895aaf02e254070", "type": "function", "z": "74891ed2e91a2d14", "name": "SL-EndRequest (static)", "func": "msg.payload = \"</Request></System-Link>\";\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 470, "y": 340, "wires": [ [ "ba832b4865973a3e" ] ] }, { "id": "45c5a880bfd8896c", "type": "xml", "z": "74891ed2e91a2d14", "name": "xml to json", "property": "payload", "attr": "", "chr": "", "x": 1370, "y": 260, "wires": [ [ "11921d2af42f41ad", "f2c42bfea40312c4", "a8c694e3ee3b96b5", "b1e2b0cafb2a8b68" ] ] }, { "id": "11921d2af42f41ad", "type": "jsonpath", "z": "74891ed2e91a2d14", "expression": "$.System-Link.LoginResponse[0].$.actionSucceeded", "split": false, "name": "Action succeeded?", "x": 950, "y": 400, "wires": [ [ "a1400141bfbeffae" ] ] }, { "id": "f2c42bfea40312c4", "type": "jsonpath", "z": "74891ed2e91a2d14", "expression": "$.System-Link.Response[0].QueryObjectResponse[0].DomainEntity[0].Property[0].Value[0]", "split": false, "name": "Get weight from response", "x": 970, "y": 480, "wires": [ [ "3125af29bc1407bb" ] ] }, { "id": "8203e1f915ccf108", "type": "ui_text", "z": "74891ed2e91a2d14", "group": "62444b9c83bfec34", "order": 5, "width": 0, "height": 0, "name": "", "label": "Weight", "format": "{{msg.payload}}", "layout": "row-spread", "className": "", "style": false, "font": "", "fontSize": 16, "color": "#000000", "x": 1550, "y": 480, "wires": [] }, { "id": "3125af29bc1407bb", "type": "function", "z": "74891ed2e91a2d14", "name": "JSON to numeric", "func": "msg.payload = Number(msg.payload);\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 1230, "y": 480, "wires": [ [ "8203e1f915ccf108" ] ] }, { "id": "a6dd3d27a6eb5431", "type": "ui_led", "z": "74891ed2e91a2d14", "order": 4, "group": "62444b9c83bfec34", "width": 0, "height": 0, "label": "Action succeeded", "labelPlacement": "left", "labelAlignment": "left", "colorForValue": [ { "color": "#ff0000", "value": "false", "valueType": "bool" }, { "color": "#008000", "value": "true", "valueType": "bool" } ], "allowColorForValueInMessage": false, "shape": "circle", "showGlow": true, "name": "Action succeeded LED", "x": 1500, "y": 400, "wires": [] }, { "id": "a1400141bfbeffae", "type": "function", "z": "74891ed2e91a2d14", "name": "JSON to boolean", "func": "msg.payload = Boolean(msg.payload);\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 1230, "y": 400, "wires": [ [ "a6dd3d27a6eb5431" ] ] }, { "id": "a8c694e3ee3b96b5", "type": "jsonpath", "z": "74891ed2e91a2d14", "expression": "$.System-Link.Response[0].QueryObjectResponse[0].DomainEntity[0].Property[1].Value[0]", "split": false, "name": "Get UM from response", "x": 960, "y": 440, "wires": [ [ "a3971fc3e3889ed1" ] ] }, { "id": "9a3c70043e611798", "type": "ui_text", "z": "74891ed2e91a2d14", "group": "62444b9c83bfec34", "order": 6, "width": 0, "height": 0, "name": "UM", "label": "WeightUM", "format": "{{msg.payload}}", "layout": "row-spread", "className": "", "style": false, "font": "", "fontSize": 16, "color": "#000000", "x": 1550, "y": 440, "wires": [] }, { "id": "a3971fc3e3889ed1", "type": "function", "z": "74891ed2e91a2d14", "name": "JSON to string", "func": "msg.payload = String(msg.payload);\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 1220, "y": 440, "wires": [ [ "9a3c70043e611798" ] ] }, { "id": "652d6518bffa2178", "type": "ui_text_input", "z": "74891ed2e91a2d14", "name": "Site input", "label": "Site", "tooltip": "", "group": "62444b9c83bfec34", "order": 1, "width": 0, "height": 0, "passthru": false, "mode": "text", "delay": 300, "topic": "", "sendOnBlur": true, "className": "", "topicType": "str", "x": 420, "y": 400, "wires": [ [ "8cfc96a9e659602f" ] ] }, { "id": "3cf35ee240823d98", "type": "ui_text_input", "z": "74891ed2e91a2d14", "name": "Item input", "label": "Item", "tooltip": "", "group": "62444b9c83bfec34", "order": 2, "width": 0, "height": 0, "passthru": true, "mode": "text", "delay": 300, "topic": "item", "sendOnBlur": true, "className": "", "topicType": "flow", "x": 420, "y": 440, "wires": [ [ "d596afb58324c7b1" ] ] }, { "id": "a38e6df7a472e505", "type": "ui_text_input", "z": "74891ed2e91a2d14", "name": "Revision input", "label": "Revision", "tooltip": "", "group": "62444b9c83bfec34", "order": 3, "width": 0, "height": 0, "passthru": true, "mode": "text", "delay": 300, "topic": "revision", "sendOnBlur": true, "className": "", "topicType": "flow", "x": 440, "y": 480, "wires": [ [ "81a492bb258c09f1" ] ] }, { "id": "8cfc96a9e659602f", "type": "function", "z": "74891ed2e91a2d14", "name": "Set flow variable", "func": "flow.set(\"site\", msg.payload);\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 640, "y": 400, "wires": [ [] ] }, { "id": "d596afb58324c7b1", "type": "function", "z": "74891ed2e91a2d14", "name": "Set flow variable", "func": "flow.set(\"item\", msg.payload); \nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 640, "y": 440, "wires": [ [] ] }, { "id": "81a492bb258c09f1", "type": "function", "z": "74891ed2e91a2d14", "name": "Set flow variable", "func": "flow.set(\"revision\", msg.payload); \nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 640, "y": 480, "wires": [ [] ] }, { "id": "d6319bb04919d609", "type": "ui_text", "z": "74891ed2e91a2d14", "group": "62444b9c83bfec34", "order": 8, "width": 0, "height": 0, "name": "", "label": "Exception", "format": "{{msg.payload}}", "layout": "col-center", "className": "", "style": true, "font": "Arial Narrow,Nimbus Sans L,sans-serif", "fontSize": "14", "color": "#000000", "x": 1540, "y": 520, "wires": [] }, { "id": "b1e2b0cafb2a8b68", "type": "jsonpath", "z": "74891ed2e91a2d14", "expression": "$.System-Link.Response[0].QueryObjectResponse[0].Exception[0].Message[0].Text[0]", "split": false, "name": "Get exception from response", "x": 980, "y": 520, "wires": [ [ "5191208a5e62a8b8" ] ] }, { "id": "5191208a5e62a8b8", "type": "function", "z": "74891ed2e91a2d14", "name": "JSON to string", "func": "msg.payload = String(msg.payload);\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 1220, "y": 520, "wires": [ [ "d6319bb04919d609" ] ] }, { "id": "62444b9c83bfec34", "type": "ui_group", "name": "Infor XA System-Link Demo", "tab": "52cd21947e7bd0fd", "order": 1, "disp": true, "width": "6", "collapse": false, "className": "" }, { "id": "52cd21947e7bd0fd", "type": "ui_tab", "name": "SL-Request XA", "icon": "dashboard", "disabled": false, "hidden": false } ]

 

Please note: this is only a simple example or PoC without any warranty.

For example: In a real scenario, you would need to handle multiple responses, and not only the first returned path (“[0]”).

I hope this helps to implement your first own prototype and more with Node-RED and XA. If you have any further question about this blog post, do not hesitate to contact us directly through this contact form:

    First name*:

    Name*:

    Company*:

    E-Mail address*:

    Message:

    IoT and ERP – chances of the Internet of Things in industry 4.0
    French website version launched