Rich HTML output from MEC

Here is a technique to give custom, rich, interactive, and user-friendly HTML responses to the users of Infor M3 Enterprise Collaborator (MEC), instead of giving them classic HTML, XML, or email responses.

Scope

MEC is an EAI tool that takes input messages, processes them, and produces output messages. I will only focus on interactive scenarios where the user needs an immediate response from MEC. I will only focus on the HTTP channels of MEC. I will assume we already have a request, and I will only focus on the HTML response. So this is a pretty specific scope of use. Note I have not been to a MEC training so my results by trial and error may not be optimal.

Classic responses

Canned response

By default, the HTTPIn channel of MEC produces this minimalist HTML response, with no piece of identifiable data about what it is referring to, no explanation of what “successfully processed” means – there could actually be a failure behind – it is just a basic acknowledgment of receipt, misspelled:

e-Collaborator HTTP Reply
The request was succesfully processed
e-Collaborate!

Custom XML response

With the HTTPSyncIn + HTTPSyncOut channels we can produce a custom response, typically in XML because MEC is XML-centric. In there, we can put identifiable data, and a detailed explanation. I illustrated this scenario in my previous post HTTP channels in MEC (part 3). But XML is not user-friendly thus not suitable for users.

Email response

Many developers setup their MEC agreements to send responses as emails to their users. However, 1) if a user takes an action that merits an immediate response from MEC, receiving an email disrupts the user context, 2) sending emails worsens mailbox pollution, 3) and most email clients block HTML and JavaScript features, thus thwarting interactivity. I prefer to show an HTML page as the primary confirmation, and optionally the email as the secondary confirmation.

Desired HTML response

My desired response is dynamic HTML from MEC. To it, I will add images and CSS for beautification, and JavaScript to manipulate the page dynamically. I will reflect the input identifiers onto the output as a feedback loop for the user. I will craft a format that is easy for the user to understand. I will put a detailed message of the processing’s result. And I will add buttons for the user to interact with the result in M3. Here is my example:

Here is my desired dynamic HTML output from MEC. I recommend using HTML5 instead of HTML4 to avoid the quirks:

<!DOCTYPE html>
<html>
   <head>
     <title>MEC response</title>
     <link rel="stylesheet" type="text/css" href="http://host1623:22107/mne/Test.css" />
   </head>
   <body>
     <img src="http://host1623:22107/mne/Test.png" alt="Test" />
     Company: <span id="CONO">910</span>
     Customer: <span id="CUNO">ACME</span>
     Customer added successfully to M3.
     <input id="btn" type="button" value="Open in CRS610/B" />
     <script type="text/javascript" src="http://host1623:22107/mne/Test.js"></script>
   </body>
</html>

Note: for an explanation of that /mne/ path, see the chapter further below about static files.

Here is my static CSS file, Test.css:

body { font-family: sans-serif }
span { font-weight: bold }

Here is my static JavaScript code, Test.js. It opens the customer record in CRS610/B. For that, it uses the Infor Smart Office Installation Point URL, the task parameter [1] and an MForms Bookmark URI [2]:

btn.addEventListener("click", function () {
   var CONO = document.getElementById("CONO").innerText;
   var CUNO = document.getElementById("CUNO").innerText;
   var isoUrl = "http://host1762:22107/mango/MangoClient.application?server=https://host1762:22108";
   var bookmarkUri = "mforms://bookmark/?program=CRS610&tablename=OCUSMA&keys=" + encodeURIComponent(["OKCONO", CONO, "OKCUNO", CUNO]);
   var url = isoUrl + "&task=" + encodeURIComponent(bookmarkUri);
   window.open(url, "_blank");
});

Note 1: You can use anchors <a> instead of buttons if you prefer.

Note 2: You can replace Smart Office by H5 Client if you prefer.

Unsuccessful attempts

I originally tried to produce the HTML output directly from MEC Mapper.

For that, I imported the XHTML 1.0 Strict XML Schema, but MEC Mapper tripped on <xs:import schemaLocation=”…xml.xsd”/>:

I removed it from the XSD and tried again, but then MEC Mapper tripped on <xs:attributeGroup ref>:
3_

I removed them all from the XSD and tried again, but then MEC Mapper added the document elements as a recursive list of all the possible elements and attributes of HTML, even the ones I did not need and the optional ones, with no flexibility to change:

Instead, I created a minimalist XSD for my sample HTML, but MEC Mapper deleted the values of my attributes, even if I enforced them in the XSD <xs:attribute>:

Then I tried to restore my attribute values with a Java function, but that quickly became un-maintainable for a large HTML document with frequent changes:
6_

And each time I change the XSD, I have to do the cycle of delete/un-publish/save/generate/publish/add/reload/activate [3], and it is too overwhelming for short development cycles.

Too complicated.

FAIL

ABORT

Back to XML

I will revert my mapping back to XML output:

Here is the desired XML output:

<?xml version="1.0"?>
<response>
   <CONO>910</CONO>
   <CUNO>ACME</CUNO>
   <result>OK</result>
</response>

As usual, to create the XSD, I use Microsoft SDK’s xsd.exe, I remove the Byte Order Mark (BOM), I remove the NewDataSet, and I import the resulting XSD in MEC Mapper.

Here is the output in MEC Mapper:

The result will be XML.

XSL Transform

I will use XSL Transformations (XSLT) to transform the XML into HTML. For that, create the XSLT file with your favorite editor, go to Partner Admin > Manage, and add an XSLT Definition:
14_

Then go to your agreement > Processes, add an XSL Transform process between XML Transform and Send, and set it to the XSLT Definition:
8

Here is a subset of my XSLT (the complete version includes the rest of the HTML):

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" doctype-system="about:legacy-compat" encoding="UTF-8" indent="yes" omit-xml-declaration="yes" />
   <xsl:template match="/response">
     <html>
       <body>
         Company: <xsl:value-of select="CONO" />
         Customer: <xsl:value-of select="CUNO" />
         <xsl:choose>
            <xsl:when test="result='OK'">Customer added successfully to M3.</xsl:when>
            <xsl:otherwise>Error: <xsl:value-of select="result" /></xsl:otherwise>
         </xsl:choose>
       </body>
     </html>
   </xsl:template>
</xsl:stylesheet>

The result will be HTML.

Note 1: I use a choose-when-otherwise to display the success and error message differently.

Note 2: I set the xsl:output properties like doctype and omit-xml-declaration in the XSLT file itself, because if I set them in the XSL Transform process properties in Partner Admin then there is no or an incorrect effect (MEC bug?).

Send

Set the Send process in the agreement to Content-type text/html:

Static files

As per the basic rules of web pages, I will make the CSS and JavaScript external, with the CSS at the top and scripts at the bottom.

We can put the static files (images, CSS, JavaScript) on any web server of our choice. I did not find a generic web server in MEC, so for now I put the files in the M3 UI Adapter (MUA) folder at \\host1623\D:\Infor\LifeCycle\?\grid\?\grids\?\applications\M3_UI_Adapter\webapps\mne\, and the files are now accessible at http꞉//host1623:22107/mne/ (I will move them to a sub-folder later):

Note: I had originally put the files in the Smart Office MangoServer webapp that serves the ISO Installation Point (IP) at \\host1762\D:\Infor\LifeCycle\?\grid\?\grids\?\applications\MangoServer\Client\IP\ but the server returned the JavaScript as an attachment, and the browser downloaded the JavaScript file instead of executing it. So I moved them to the mne folder which returns the correct content type.
Incorrect:
Content-Disposition: attachment; filename=Test.js
Content-Type: application/x-download

Correct:
Content-Type: application/x-javascript

Then, set the URLs accordingly in the HTML:

<link  href="http://host1623:22107/mne/Test.css".../>
<img    src="http://host1623:22107/mne/Test.png".../>
<script src="http://host1623:22107/mne/Test.js".../>

Result

You can now test the result. MEC will produce XML and will use XSLT to transform the XML into HTML, the browser will get the HTML and will follow the links to get the static files and render the result, and the user will see a nice HTML page that it can interact with.

Here is the result for one of my customers, and it is coming straight from MEC; users love it, they can click on the links to open the records in Smart Office, they can print it, save it as PDF, etc.:

What about you

If you have an existing MEC mapping/agreement that produces XML, and you want to get this HTML solution:

  1. Create the static files (images, CSS, JavaScript) and place them somewhere in a web server
  2. Create the XSLT file that transforms the XML into HTML, set the URL of the static files accordingly, and add the XSLT to the Partner Admin > XSLT Definitions
  3. Add an XSL Transform process to your agreement (between XML Transform and Send), and set the Send content type to text/html

There is nothing to change in the MEC mapping. There is no server to reload.

Maintenance

With this design we have separation of concerns between data, presentation, and control, and it is easier to maintain than my original idea to do HTML output from the mapping:

  • XML: maintain the XML as usual in MEC Mapper
  • Static files (images, CSS, JavaScript): update the files with your favorite editors, and save them directly to the file system
  • XSLT/HTML: update the file with your favorite editor, save directly to the file system, and update the XSLT Definition
  • XSLT Definition: in the Partner Admin, delete the XSL Transform from the agreement and save it, select the new XSLT file in the XSLT Definition, re-add the XSL Transform to the agreement and save

Each step can be maintained individually by the same developer or by separate developers.

Note: MEC is not collaborative for developers, so if two developers are working on the same agreement in MEC Partner Admin (e.g. one maintaining the XML Transform, the other maintaining the XSL Transform) then the agreement will be corrupted.

Future work

There is more work to be done:

  • Move the static files to another web server (who knows what will happen to the mne folder with upgrades of M3)
  • Select the environment dynamically for the Smart Office Installation Point (DEV, TST, PRD)
  • Set the text to the local language (English, Swedish, French, etc.)
  • Get the column headings (CONO, CUNO, etc.) from the M3 language constants using the M3 translate API [4] instead of hard-coding the values
  • Implement the Post/Redirect/Get pattern to avoid duplicate form submission when users click the back button, and to not sanction users that save the page in the browser bookmarks
  • For easy cross-reference to the MEC Management log files, output the manifest UUID to the XML output using the Java method com.intentia.ec.mapper.Mapping.getManifestInfo(“UUID”), and add a link to it in the HTML with the href to https://host1764:22108/grid/ui/#com.lawson.ec.gridui.page.MessageDetailPage/MECSRVDEV?uuid/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
  • One time I received the error “Internet Explorer has modified this page to help prevent cross-site scripting” (it is related to the same origin policy of browsers), but I have not been able to reproduce it. Otherwise it is working fine with my URLs spread across three hosts. To be troubleshot.

Conclusion

That was my solution for MEC to give custom, rich, interactive, and user-friendly HTML responses to the users. This is useful where the user needs an immediate response. All you have to do is add the XSL Transform and the static files without touching the existing mapping. In most of the cases, the classic responses – canned response, XML, or email – are sufficient.

That’s it! Special thanks to my colleague Sheryll Limmex for the help with the MEC mapping.

What solution did you design? Let me know in the comments below.

M3 API protocol dissector for Wireshark

Have you ever needed to troubleshoot M3 API calls in Wireshark? Unfortunately, the M3 API protocol is a proprietary protocol. Consequently, Wireshark does not understand it, and it just gives us raw TCP data as a stream of bytes.

Abstract

I implemented a simple protocol dissector for Wireshark that understands the M3 API protocol, i.e. it parses the TCP stream to show the M3 API bytes in a human-readable format, with company (CONO), division (DIVI), user id (USID), and MI program (e.g. CRS610MI). The dissector is currently not complete and only parses the MvxInit request phase of the protocol.

Reverse engineering

I reverse engineered the M3 API protocol thanks to MvxLib, a free and open source client implementation of the protocol in C# by Mattias Bengtsson (now deprecated), and thanks to MvxSockJ, the official and closed-source client implementation of the protocol in Java (up-to-date).

3 2

MvxInit

The MvxInit phase of the protocol is the first phase of the protocol for connection and authentication, and it has the following structure:

struct MvxInit
{
   struct request {
      int size;
      char Command[5]; // PWLOG
      char CONO_DIVI[32];
      char USID[16];
      char PasswordCiphertext[16]; // password ^ key
      char MIProgram[32]; // e.g. CRS610MI
      char ApplicationName[32]; // e.g. MI-TEST
      char LocalIPAddress[16];
   };
   struct response {
      int size_;
      char message[15];
   };
};

Wireshark Generic Dissector

I used the Wireshark Generic Dissector (wsgd) to create a simple dissector. It requires two files: a data format description, and a file description.

The data format description m3api.fdesc is very similar to the C struct above, where the header is required by wsgd:

struct header
{
   byte_order big_endian;
   uint16 id;
   uint16 size;
}
struct body
{
   struct
   {
      uint32 size;
      string(5) Command;
      string(32) CONO_DIVI;
      string(16) USID;
      string(16) PasswordCiphertext;
      string(32) MIProgram;
      string(32) ApplicationName;
      string(16) LocalIPAddress;
   } MvxInit;
}

Given the limitations of wsgd and its message identifier, I could not solve how to parse more than one type of message, so I chose the MvxInit request, and the rest will throw errors.

I made a test M3 API call to M3BE, I captured it in Wireshark, and I saved the TCP stream as a binary file (I anonymized it so I can publish it here):

Then, I used wsgd’s byte_interpret.exe (available on the wsgd downloads), using that test binary file, to fine tune my data format description until it was correct:
byte_interpret.exe m3api.fdesc -frame_bin Test.bin

Then, here is the wsgd file description m3api.wsgd; note how I listed the TCP port numbers of my M3 API servers (DEV, TST, PRD):

PROTONAME M3 API protocol
PROTOSHORTNAME M3API
PROTOABBREV m3api

PARENT_SUBFIELD tcp.port
PARENT_SUBFIELD_VALUES 16305 16405 16605 # DEV TST PRD

MSG_HEADER_TYPE header
MSG_ID_FIELD_NAME id
MSG_SUMMARY_SUBSIDIARY_FIELD_NAMES size
MSG_TOTAL_LENGTH size + 4
MSG_MAIN_TYPE body

PROTO_TYPE_DEFINITIONS
include m3api.fdesc;

To activate the new dissector in Wireshark, simply drop the wsgd generic.dll file into Wireshark’s plugins folder, and drop the two above files into Wireshark’s profiles folder:

Then, restart Wireshark, and start capturing M3 API calls. Wireshark will automatically parse the TCP stream as M3 API protocol for the port numbers you specified in the data format description.

Result

Here is a resulting capture between MI-Test and M3BE. Note how you can filter the displayed packets by m3api. Note how the protocol dissector understands the phase of the M3 API protocol (MvxInit), the company (CONO), division (DIVI), user id (USID), and MIProgram (CRS610MI). Also, I wrote C code to decrypt the password ciphertext, but I could not solve where to put that code in wsgd, so the dissector does not decrypt the password.

Limitations and future work

  • I am using M3BE 15.1.2. The M3 API protocol may be different for previous or future versions of M3.
  • I am doing user/password authentication. The M3 API protocol supports other methods of authentication.
  • Given the limitations of wsgd and its message identifier, I will most likely discontinue using wsgd.
  • Instead, I would write a protocol dissector in LUA.
  • Ideally, I should write a protocol dissector in C, but that is over-kill for my needs.

Conclusion

That was a simple M3 API protocol dissector for Wireshark that parses and displays M3 API bytes into a human readable format to help troubleshoot M3 API calls between client applications and M3 Business Engine.

About the M3 API protocol

The M3 API protocol is a proprietary client/server protocol based on TCP/IP for third-party applications to make API calls to Infor M3 Business Engine (M3BE). It was created a long time ago when Movex was on AS/400. It is a very simple protocol, lean, efficient, with good performance, it is available in major platforms (IBM System i, Microsoft Windows Intel/AMD, SUN Solaris, Linux, 32-bit, 64-bit, etc.), it is available in major programming languages (C/C++, Win32, Java, .NET, etc.), it supports Unicode, it supports multiple authentication methods, and it has withstood the test of time (since the nineties). It has been maintained primarily by Björn P.

The data transits in clear text. The protocol had an optional encryption available with the Blowfish cipher, but that feature was removed. Now, only the password is encoded with a XOR cipher during MvxInit. If you need to make secure calls, use the M3 API SOAP or REST secure endpoints of the Infor Grid.

For more information about M3 API, refer to the documentation in the M3 API Toolkit:
4

More interfaces

If you need to integrate Infor M3 with any of the following interfaces, here are most of the interfaces with which I have worked in the past two years for which I have not posted anything on this blog yet, and for which I may be able to help you if you have questions (contact me here). For that, I used a variety of Enterprise Collaborator, Smart Office scripts, and other code.

Also, check-out the other interfaces I have worked with.

Authorization hierarchies for approval flows in M3

Today I will illustrate authorization hierarchies for approval flows in Infor M3.

Approval flows

A common requirement in M3 projects is to implement approval flows, for example for purchase orders; where a buyer creates a purchase order of a certain amount; where one or more approvers must review the order, and either approve it, one approver after the other, either reject it for some reason; and where the approvers are selected from a hierarchy of managers and their maximum order amount.

There are many variations of these approval flows, each being specific to the requirements of the M3 project. Here is a simple approval flow with a single approver:
Approval flows get complex quickly, with many decisions to take, many levels of approval to have, many design trade-offs to consider, and all sorts of scenarios to support.

Infor Process Automation

To implement the approval flows we use Infor Process Automation (IPA). We can also use the former Lawson ProcessFlow Integrator (PFI). As for Infor ION, it is the new standard for implementing M3 approval flows, but its usage for M3 is still young, and it does not yet have as many features as IPA does.

M3 Purchase Authority – PPS235

To store the hierarchy of approvers and their maximum order amounts, we use the program M3 Purchase Authority – PPS235. It stores the user (AURE), the maximum order amount this user is authorized to approve (MPOA), and their manager who is the next level for authorization (MNGR). For orders that exceed this amount, authorization is required by a user with authorization rights for a higher amount.

1

Huh?

I am not an expert on PPS235, but it looks like it does not enforce integrity, and it is not in normal form, and that can result in logical inconsistencies.

Indeed, the hierarchy of managers and maximum order amount may contradict each other. For instance, PPS235 allowed me to set a user whose maximum order amount was higher than that of its manager, in which case routing the approval to that manager will result in a logical anomaly.

Also, there is a field to set the user’s authorization level (AUTL) which results in an alternate hierarchy of approvers, and PPS235 allowed me to enter illogical values there too.

That results in several possible hierarchies: either based on the managers, either based on the maximum order amounts, either based on the authorization levels, all possibly contradicting each other.

Also, PPS235 erroneously allows cycles in the hierarchy. For instance, it allowed me to set a user to be the manager of its manager. This will cause an infinite loop in the graph traversal.

Also, users can be unreachable if there does not exist a connection to that user in the hierarchy.

There is probably a logical explanation for these design decisions. Meanwhile, you must ensure the integrity of your data before proceeding.

MPAUTD

The data of PPS235 is stored in table MPAUTD – Authorization distribution as an adjacency list of users and their managers, for example:

AURE MNGR
Marie Eric
Keith Daniel
Eric Daniel
Charles Daniel
John Daniel
Daniel Joe
Joe Jeff

The resulting tree looks something like this (I use Graphviz to visualize the hierarchy and ensure it is correct):
input

To retrieve the hierarchy of managers for a certain user we traverse the adjacency list recursively using SQL’s common table expressions (CTE), for example for user Marie and for a purchase order in company 100:

WITH CTE AS (
SELECT ATCONO, ATAURE, ATMPOA, ATMNGR FROM MPAUTD WHERE ATCONO=100 AND ATAURE='Marie'
UNION
SELECT M.ATCONO, M.ATAURE, M.ATMPOA, M.ATMNGR FROM MPAUTD M JOIN CTE C ON M.ATCONO=C.ATCONO AND M.ATAURE=C.ATMNGR
)
SELECT ATAURE FROM CTE
ORDER BY ATMPOA

That results in the hierarchy of approvers and their maximum order amount starting at the specified buyer:
output

You can now create a loop of UserAction activity nodes in IPA to iterate thru that hierarchy. Here is an excerpt (you will need to add the SQL activity node and everything else that may be needed; you can also use the new ForEach activity node instead of my loop with an if-then-else Branch):
x

Conclusion

That was a quick illustration of authorization hierarchies for approval flows for purchase orders in M3, more specifically how to store the hierarchy of approvers in PPS235, how to recursively query it from MPAUTD, and how to do a loop of UserAction activity nodes.

(I meant to write about this many years ago. I finally got around to doing it after I learned how to do the recursive SQL portion for a customer last week. I hope it helps you.)

Custom message process in MEC

Here is an unofficial guide on how to create a custom message process in Infor M3 Enterprise Collaborator (MEC).

What is a message process?

A message process in MEC is one of the steps in a process flow. Technically speaking, it is a Java class that reads a stream of bytes as an input, does some processing on it, and writes a stream of bytes as an output, for example transform a flat file to XML, apply XSLT to an XML, remove an envelope, archive the message, or make a SOAP request. Message processes are chained together in a partner agreement in the Partner Admin Tool.

2_

Documentation

The Partner Admin Tool User Guide has some information about message processes:
1__

Java classes

The message processes are Java classes located in MEC’s core library:

D:\Infor\LifeCycle\host\grid\M3\grids\M3\applications\MECSRVDEV\MecServer\lib\ec-core-x.y.z.jar

Each message process is a Java class in package com.intentia.ec.server.process:4

Each message process may have a configuration dialog box in package com.intentia.ec.partneradmin.swt.agreement: 3

Database

The message processes are declared in the MEC database in table PR_Process:
5

Java code

To create your own message process follow these steps:

  1. Use the following skeleton Java code, fill with your code, set the file extension for the output message (in my example it is .something), use the in input and out output streams to process the message as you need, and eventually use the cat logger to write debug info in the log file:
    package somewhere;
    
    import java.io.InputStream;
    import java.io.OutputStream;
    import org.apache.log4j.Category;
    import com.intentia.ec.server.process.AbstractProcess;
    import com.intentia.ec.server.process.ProcessException;
    
    public class SomeProcess extends AbstractProcess {
    
      private static Category cat = Category.getInstance(SomeProcess.class.getName());
    
      public String getState() {
        return "SomeProcess";
      }
    
      public boolean hasOutput() {
        return true;
      }
    
      public String getFileExtension() {
        return ".something";
      }
    
      public void process(InputStream in, OutputStream out) throws ProcessException {
        // your code here
        cat.info("processing...");
      }
    
    }

    Note: I do not have a sample code for the dialog box, but you can get inspiration from one of the existing classes in package com.intentia.ec.partneradmin.swt.agreement.

  2. Compile the Java code with:
    javac -extdirs D:\Infor\LifeCycle\host\grid\M3\grids\M3\applications\MECSRV\MecServer\lib\ SomeProcess.java
  3. Copy the resulting Java class to the classpath of the MEC server and Partner Admin Tool, in the folder corresponding to the package (in my case it was package somewhere):
    D:\Infor\LifeCycle\host\grid\M3\grids\M3\applications\MECSRV\MecServer\custom\somewhere\SomeProcess.class
    D:\Infor\MECTOOLS\Partner Admin\classes\somewhere\SomeProcess.class

    Note: You can probably also put the Java class in a JAR file; to be tested.

  4. In the MEC database, add the process to the PR_Process table, where ?? is a new ID, for example 27:
    INSERT INTO MECDBDEV.dbo.PR_Process (ID, Name, Description, ConfigurationClass, WorkClass, Standard) VALUES (??, 'Thibaud Process', 'My custom message process', null, 'somewhere.SomeProcess', 1)
  5. In the Infor Grid, restart the MECSRV application to pick up the new Java class:
    6
  6. In the Partner Admin Tool, create a partner agreement and add the message process:
    2
  7. Reload the MEC server to pick up the new agreement:
    7
  8. Run the partner agreement, for example I have a channel detection with a HTTPIn receive channel listening on port 8084, and I make an HTTP request to that port number to trigger the partner agreement.
  9. Check the message received (.rcv) and the message produced (in my case it was extension .something); for that, you will need one Archive process in your partner agreement, before and after your custom process:
    128
  10. You can also open the files directly in the folder specified:
    11
  11. And if you used the logger, you can check your logs in the Event tab:
    10

Real-world example

In my case, I needed a custom message process for the following real-world scenario. My current customer does Procurement PunchOut with its partners using cXML, an old protocol from 1999. In that protocol, there is a step (PunchOutOrderMessage) that sends an XML document in a hidden field cxml-urlencoded of an HTML form. That results in a POST HTTP request (to MEC) with Content-Type: application/x-www-form-urlencoded, with the XML document that is URL-encoded as a value of parameter cxml-urlencoded in the request body. Unfortunately, MEC does not have a message process to extract a specific parameter value of a message, and URL-decode it. So I developed my custom message process as explained above, to take the request body, extract the desired parameter value, URL-decode it, and output the resulting XML. I may write a detailed post about it some day, maybe not.

Conclusion

That was a guide on how to create a custom message process in MEC, doing Java development, to take an input message in a partner agreement, do some custom processing on it, and produce an output message. This is an unofficial solution that I figured out by de-compiling and hacking MEC. There may be a simpler solution, I do not know.

That’s it! Thank you for supporting this blog, please like, subscribe, share around you, and come author the next blog post with us.

Procurement PunchOut with cXML

Hi colleagues. It has been a while since I posted anything. Today I will write a quick post as part of an interface I am currently developing to do procurement PunchOut using cXML, an old protocol from 1999, for my customer and its suppliers. This will eventually end up in their Infor M3 and M3 Enterprise Collaborator implementation.

I only needed to test the Message Authentication Code (MAC) so I wrote a quick prototype in Python.

The cXML User’s Guide describes the MAC algorithm using HMAC-SHA1-96:

Here is my implementation in Python:

# Normalize the values
data = [fromDomain.lower(),
        fromIdentity.strip().lower(),
        senderDomain.lower(),
        senderIdentity.strip().lower(),
        creationDate,
        expirationDate]

# Concatenate the UTF-8-encoded byte representation of the strings, each followed by a null byte (0x00)
data = b''.join([(bytes(x, "utf-8") + b'\x00') for x in data])

# Calculate the Message Authentication Code (MAC)
digest = hmac.new(password.encode("utf-8"), data, hashlib.sha1).digest()

# Truncate to 96 bits (12 bytes)
truncated = digest[0:12]

# Base-64 encode, and convert bytearray to string
mac = str(base64.b64encode(truncated), "utf-8")

# Set the CredentialMac in the XML document
credentialMac = xml.find("Header/Sender/Credential").find("CredentialMac")
credentialMac.attrib["creationDate"] = creationDate
credentialMac.attrib["expirationDate"] = expirationDate
credentialMac.text = mac

Here is my resulting MAC, and it matches that of the cXML User’s Guide, good:

I posted the full source code in my GitHub repository at https://github.com/M3OpenSource/cXML/blob/master/Test.py .

That’s it!

Thank you for continuing to support this blog.

Experimenting with middle-side modifications

With Infor M3, there are server-side modifications, client-side modifications, and the unexplored middle-side modifications. I will experiment with servlet filters for the M3 UI Adapter.

Modification tiers

There are several options to modify M3 functionality:

  • Server-side modifications are M3 Java mods developed with MAK; they propagate to all tiers, including M3 API, but they are often avoided due to the maintenance nightmare during M3 upgrades, and they are banned altogether from Infor CloudSuite multi-tenant. There are also Custom lists made with CMS010 which are great, they are simply configured by power users without requiring any programming, and they survive M3 upgrades.
  • Client-side modifications for Smart Office are Smart Office scripts in JScript.NET, Smart Office SDK features, applications and MForms extensions in C#, Smart Office Mashups in XAML, and Personalizations with wizards. They do not affect M3 upgrades but they apply only to Smart Office. And client-side modifications for H5 Client are H5 Client scripts in JavaScript, and web mashups converted from XAML to HTML5. Likewise, they do not affect M3 upgrades but they apply only to H5 Client.
  • Middle-side modifications are servlet filters for the M3 UI Adapter. They propagate to all user interfaces – Smart Office AND H5 Client – but this is unexplored and perilous. In the old days, IBrix lived in this tier.

M3 UI Adapter

The M3 UI Adapter (MUA), formerly known as M3 Net Extension (MNE), is the J2EE middleware that talks the M3 Business Engine protocol (MEX?) and serves the user interfaces. It was written mostly single-handedly by norpe. It is a simple and elegant architecture that runs As Fast As Fucking Possible (TM) and that is as robust as The Crazy Nastyass Honey Badger [1]. The facade servlet is MvxMCSvt. All the userids/passwords, all the commands, for all interactive programs, for all users, go thru here. It produces an XML response that Smart Office and H5 Client utilize to render the panels. The XML includes the options, the lists, the columns, the rows, the textboxes, the buttons, the positioning, the panel sequence, the keys, the captions, the help, the group boxes, the data, etc.

For example, starting CRS610/B involves:
com.intentia.mc.servlet.MvxMCSvt.doTask()
com.intentia.mc.command.MCCmd.execute()
com.intentia.mc.command.RunCmd.doRunMovexProgram()
com.intentia.mc.engine.ProtocolEngine.startProgram()

The following creates a list with columns:

import com.intentia.mc.metadata.view.ListColumn;
import com.intentia.mc.metadata.view.ListView;

ListView listView = new ListView();
ListColumn listColumn = new ListColumn();
listColumn.setWidth(length);
listColumn.setConstraints(constraints);
listColumn.setCaption(new Caption());
listColumn.setConditionType(1);
listColumn.setHeader(headerSplitterAttr);
listColumn.setName("col" + Integer.toString(columnCount));
listColumn.setJustification(1);
listColumns.add(listColumn);
listView.addFilterField(posField, listColumn);
listView.setListColumns((ListColumn[])listColumns.toArray(new ListColumn[0]));

Here is an excerpt of the XML response for CRS610/B that shows the list columns and a row of data:
Fiddler

Experiment

This experiment involves adding a servlet filter to MvxMCSvt to transform the XML response. Unfortunately, MNE is a one-way function that produces XML in a StringBuffer, but that cannot conversely parse the XML back into its data structures. Thus, we have to transform the XML ourselves. I will not make any technical recommendations for this because it is an experiment. You can refer to the existing MNE filters for examples on how to use the XML Pull Parser (xpp3-1.1.3.4.O.jar) that is included in MNE. And you can use com.intentia.mc.util.CstXMLNames for the XML tag names.

To create a servlet filter:

/*
D:\Infor\LifeCycle\host\grid\XYZ\runtimes\1.11.47\resources\servlet-api-2.5.jar
D:\Infor\LifeCycle\host\grid\XYZ\grids\XYZ\applications\M3_UI_Adapter\lib\mne-app-10.2.1.0.jar
javac -cp servlet-api-2.5.jar;mne-app-10.2.1.0.jar TestFilter.java
*/

package net.company.your;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import com.intentia.mc.util.Logger;

public class TestFilter implements Filter {

    private static final Logger logger = Logger.getLogger(TestFilter.class);

    public void init(FilterConfig filterConfig) throws ServletException {}

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("Hello, World");
        }
        chain.doFilter(request, response);
    }

    public void destroy() {}

}

Add the servlet filter to the MNE deployment descriptor at D:\Infor\LifeCycle\host\grid\XYZ\grids\XYZ\applications\M3_UI_Adapter\webapps\mne\WEB-INF\web.xml:

<filter>
    <filter-name>TestFilter</filter-name>
    <filter-class>net.company.your.TestFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>TestFilter</filter-name>
    <servlet-name>MvxMCSvt</servlet-name>
</filter-mapping>

Then, reload the M3UIAdapterModule in the Infor Grid. This will destroy the UI sessions, and users will have to logout and logon M3 again.

Optionally, set the log level of the servlet filter to DEBUG.

Limitations

MvxMCSvt is the single point of entry. If you fuck it up, it will affect all users, all programs, on all user interfaces. So this experiment is currently a Frankenstein idea that would require a valid business case, a solid design, and great software developers to make it into production.

Also, changes to the web.xml file will be overriden with a next software update.

Discussion

Is this idea worth pursuing? Is this another crazy idea? What do you think?