SOAP WS-Security in the context of M3

Here is my high-level understanding of SOAP Web Services Security (WS-Security, or WSS), at least the WSS X.509 Certificate Token Profile, and how to apply it in the context of Infor M3.

WS-Security

WS-Security is a standard by the OASIS consortium to provide message encryption and digital signature, the usual security properties to prevent eavesdropping and tampering of a message. It uses asymmetric cryptography with public-private keys and digital certificates. There is an additional property which is central to WSS: the security happens at the message level, not at the transport level, i.e. the security follows the message even across proxies and deep packet inspection gateways, for end-to-end security. WSS is common for example in financial institutions that need to inspect and route a message through several nodes that can read the non-secure part of the SOAP envelope yet not reveal the secret in the message, until it reaches the appropriate destination. If a node on the path gets compromised, the security of the message is not compromised. Despite its continued use, WSS has only had few major updates in 10 years, is not considered secure [1] [2], the Internet agrees it is complicated and design-by-committee, and there is no industry momentum behind it.
versus1_

SSL/TLS, on the other hand, provides similar security properties with encryption and digital signature, using public key cryptography as well, but the security happens at the transport level, i.e. before the message level, for point-to-point security only. Thus, intermediate proxies and deep packet inspection gateways are unable to reveal the message to inspect it and route it, unless they have a copy of the destination’s private key. The workaround is to setup a chain of TLS segments, but the compromise of a node on the path, would compromise the message. TLS has additional security properties such as cipher suite negotiation, forward secrecy, and certificate revocation. TLS is constantly being updated with the latest security properties, and is widely supported and documented.

I have seen WSS interfaces used at banks and credit card companies that still have ancient mainframes and old middleware, and WSS is always used in combination with TLS, with peer authentication, thus four sets of public/private keys and digital certificates.
versus3

Infor Grid

Several applications of the Infor Grid expose SOAP web services, but I could not find how to setup WS-Security at the Grid level, so I assume it is not supported at the Grid level, only at the application level; that’s OK as SOAP over HTTPS is sufficient for the Grid’s needs.

grid

M3 Web Services (MWS)

The MWS application does have settings to configure WS-Security (X.509 Token policy); that would be useful for an external partner to call M3 with message-level security (otherwise there is always SOAP over HTTPS); I will explore this in a future post:
MWS

M3 Enterprise Collaborator (MEC)

The MEC application on the Grid does not have built-in SOAP web services. But in MEC Partner Admin the MEC developer can setup SOAP. The SOAP client Send Web Service process does not support WS-Security; this is most unfortunate as here is precisely where I want to setup the secure interfaces with the banks and credit card companies, bummer, I will have to develop my own WS-Security client in Java. On the other hand, the SOAP server WebServiceSyncIn does support WS-Security; I will explore this in a future post:
1

Future work

In future posts, I will:

  • Explore WS-Security in MWS
  • Explore WS-Security in MEC SOAP client
  • Explore WS-Security in MEC SOAP server

Conclusion

That was my high level understanding of WS-Security, at least the WSS X.509 Certificate Token Profile, and how to apply it in the context of M3. I am no expert in WS-Security, but this understanding is sufficient for my needs with M3.

That’s it!

Please subscribe, comment, like, share, and come author with us.

–Thibaud

 

Removing BEGIN statements from Streamfiles with Java

In this example, we have a standard M3 Streamfile (PPS307PF – M3 Put-Away document) that contains separate BEGIN statements (the keyword that signifies a new event to Streamserve) for each item received against a purchase order.  My client would like to see one document with all received items shown as separate lines.  This will save a tremendous amount of paper and allow for easier processing.  With the help of Java and Streamserve filter chains, we can remove unwanted BEGIN statements and quickly achieve a single event document without modification in M3.

In addition to Java knowledge, you’ll also need to have:

  1. Experience with the M3 Output Solution including Streamserve Control Center and Design Center (version 5.6 is used in this example).  Specifically:
    • Creating new Streamserve Applications
    • Modifying, Exporting, and Deploying Streamserve Projects
  2. Administrator access to the Streamserve server.

Now that we’re armed with some basic knowledge, let’s get started.

Step 1:  Examine the Streamfile

Here is my example Streamfile:

*SERVER	server.company.com
*PORT	 22110
*NAME	PPS307PF
*USER	USER
*JOBID	135979716657926744PPS307PF
*MAIL	YES
*TOMAIL1	mnjones@ciber.com
*FRMAIL1	m3apps@hertz.com
*ARCHIVE1	0
*COUNTRY1	US
*ReportFiletype1	PDF
*LANGUAGE	GB
*ZDCONO	111
*ZDDIVI	AAA
*ZDFACI	FAC
*ZDWHLO	DS5
BEGINPPS3070H
*TIME	20160317153921
*LANGUAGE	GB
*ZZCONO	0
*ZZDIVI
*ZZFACI	FAC
*ZZWHLO	WHS
*LAYOUT	LETTER
0HINDCTR	000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000010000000
0HDSUSS	USER
0HMWLNCD	GB
0HPADAT	03-17-16
0HPATIM	15:39:21
0HWDROW3	COMPANY US (111/AAA)
0HZDROW2	M3 13.2 DEV

1MINDCTR	000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000010000000
1MIABUYE	USER
1MIBECVE
1MIBIRCV
1MIBITNO	100000099
1MIBPITD
1MIBPLPN	0000000
1MIBPNLI	010
1MIBPNLS	000
1MIBPRCS
1MIBPROD
1MIBPUNO	8888888
1MIBPURC	USER
1MIBPUUN	EA
1MIBRORC	0
1MIBRORL
1MIBRORN
1MIBSITE
1MIBSUFI
1MIBSUNO	99999999
1MIBWHLO	WHS
1MICREPN	   2111030001
1MMLBANO	7777777
1MMLBREF
1MMLBRE2
1MMMHAC1
1MMMHAC2
1MMMHAC3
1MMMITDS	Excavator
1MMMPPUN
1MMMSLDY
1MMMUNMS
1MMUCOFA
1MMWWHNM	Main Warehouse
1MWTBUYE	Patty Purchaser
1MWTHAC1
1MWTHAC2
1MWTHAC3
1MWTPROD
1MWTPURC	Patty Purchaser
1MWWCAWE
1MWWFUDS	Excavator
1MWWPQTY
1MWWRPQA	         1
1MWWRPQT
1MWWSUNM	Excavator Supply
1MWWWHSL	SERVICE

0NINDCTR	000000000000000000000000000000000000000000000000001000000000000000001000000000000000000000000000000
0NIBGRMT	DP0
0NP7QCLV	4
0NWTGRMT	Direct Put-Away (No Doc)
0NWWNEAC	No more activity
0NWWQCQT	

CFINDCTR	000000000000000000000000000000000000000000000000001000000000000000001000000000000000000000000000000
CFWWTX60	MAKE BOBCAT

CFINDCTR	000000000000000000000000000000000000000000000000001000000000000000001000000000000000000000000000000
CFWWTX60	MODEL D12A
BEGINPPS3070H
*TIME	20160317153921
*LANGUAGE	GB
*ZZCONO	0
*ZZDIVI
*ZZFACI	FAC
*ZZWHLO	WHS
*LAYOUT	LETTER
0HINDCTR	000000000000000000000000000000000000000000000000001000000000000000001000000000000000000000010000000
0HDSUSS	USER
0HMWLNCD	GB
0HPADAT	03-17-16
0HPATIM	15:39:21
0HWDROW3	COMPANY US (111/AAA)
0HZDROW2	M3 13.2 DEV

1MINDCTR	000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000010000000
1MIABUYE	USER
1MIBECVE
1MIBIRCV
1MIBITNO	100000099
1MIBPITD
1MIBPLPN	0000000
1MIBPNLI	010
1MIBPNLS	000
1MIBPRCS
1MIBPROD
1MIBPUNO	8888888
1MIBPURC	USER
1MIBPUUN	EA
1MIBRORC	0
1MIBRORL
1MIBRORN
1MIBSITE
1MIBSUFI
1MIBSUNO	99999999
1MIBWHLO	WHS
1MICREPN	   2111030002
1MMLBANO	8191811
1MMLBREF
1MMLBRE2
1MMMHAC1
1MMMHAC2
1MMMHAC3
1MMMITDS	Excavator
1MMMPPUN
1MMMSLDY
1MMMUNMS
1MMUCOFA
1MMWWHNM	Main Warehouse
1MWTBUYE	Patty Purchaser
1MWTHAC1
1MWTHAC2
1MWTHAC3
1MWTPROD
1MWTPURC	Patty Purchaser
1MWWCAWE
1MWWFUDS	Excavator
1MWWPQTY
1MWWRPQA	         1
1MWWRPQT
1MWWSUNM	Excavator Supply
1MWWWHSL	SERVICE

0NINDCTR	000000000000000000000000000000000000000000000000001000000000000000001000000000000000000000000000000
0NIBGRMT	DP0
0NP7QCLV	4
0NWTGRMT	Direct Put-Away (No Doc)
0NWWNEAC	No more activity
0NWWQCQT	

CFINDCTR	000000000000000000000000000000000000000000000000001000000000000000001000000000000000000000000000000
CFWWTX60	MAKE BOBCAT

CFINDCTR	000000000000000000000000000000000000000000000000001000000000000000001000000000000000000000000000000
CFWWTX60	MODEL D12B

CFINDCTR	000000000000000000000000000000000000000000000000001000000000000000001000000000000000000000000000000
CFWWTX60	Air Condition:Yes

We want to process PPS307PF as one document with multiple lines as opposed to the standard document which has multiple documents (indicated by the multiple BEGINPPS3070H statements in the Streamfile) each with a single line.

There is only one type of BEGIN statement in this Streamfile, but some Streamfiles have more than one, so we’ll take that into account in the code.

Step 2:  Create Java filter to remove the unwanted BEGIN statements

In the Streamfile above we found two instances of BEGINPPS3070H.  In order to remove the second (and any subsequent appearances) while keeping the first, we’ll create a class that will remove these lines as the Streamfile gets passed into Streamserve.  We’ll also take into account that we may need to filter Streamfiles other than PPS307, so we’ll use a regular expression to find the instances of BEGIN in the file that end in ‘H’ (This way we’re excluding 0A BEGIN statements while including 0H, 1H, AH, etc.).

import java.io.*;
public class SingleEvent {
 public static void main(String[]args) {
  BufferedReader br = null;
  int counter = 0;
  String regex = "BEGIN[a-zA-Z0-9]+H$";
  try {
   String sCurrentLine;
   br = new BufferedReader(new InputStreamReader(System.in));
   while ((sCurrentLine = br.readLine()) != null) {
    if (sCurrentLine.matches(regex)) {
     counter++;
     if (counter > 1) {
      sCurrentLine = "***BEGIN STATEMENT SKIPPED***";
     }
    }
    System.out.println(sCurrentLine);
   }
  } catch (IOException e) {
   e.printStackTrace();
  }
  finally {
   try {
    if (br != null)
     br.close();
   } catch (IOException ex) {
    ex.printStackTrace();
   }
  }
 }
}

Our code creates a class called SingleEvent which reads the incoming Streamfile one line at a time and checks the current line to see if it matches our regular expression.  If so, a counter is increased and if the counter is larger than one, it replaces the current line with new text.  This text can be anything other than BEGINPPS3070H as Streamserve is looking for the BEGIN statements to create new documents.  Each (new) line is then written to the console and Streamserve picks up each of the lines before processing the Streamfile.

When we replace the BEGIN statements with alternative text (in this case replacing them with the text “***BEGIN STATEMENT SKIPPED***”).  Streamserve rejects the line, notes it in the Control Center log, and moves to the next line. Later in the process, we’ll see our skipped lines in the log.

Next, compile the code and create a SingleEvent.jar using your favorite tool.  Copy the SingleEvent.jar to an accessible folder on the Streamserve server.  Now we can begin prepping Streamserve to use Java.

Step 3:  Prep Streamserve to use Java

Because we’ll be adding a filter on both the TCP/IN port and DirScan folder, it’s best to create a new Streamserve Application specifically for our new SingleEvent Streamfiles.  We don’t want to place this filter on our base Streamserve Application and filter all Streamfiles.   Follow the standard steps to create the new application in the correct domain in Control Center.  For this example, we named our application “Dev_SingleEvent_22104” to indicate the domain, application purpose, and incoming port number.   Next, right click on the application and select Java Configuration.

beginsts_31

When the Java Configuration screen opens, we see the properties list.  Click in the Value box and select the appropriate vendor.  In my case, this was Oracle.

beginsts_32

Next, we’ll need to import our SingleEvent.jar and add the new filter chain in the Streamserve Global_Resourceset. In the Global_Resourceset, right click and select “Import…”.

beginsts_33

Navigate to the folder on the Streamserve server where you stored SingleEvent.jar.  Highlight SingleEvent.jar and click Open.

Streamserve will automatically load the correct Resource Type (Java).

beginsts_34

Click OK and your new Java resource will appear in the Global_Resourceset.

Now, let’s create our new filter chain.  The filter chain is the heart of this transaction as Streamserve will filter the Streamfile through the Java program as it comes into the TCP port or is dropped into the DirScan folder.

First, navigate to the Filter Chain folder in the Global_Resourceset, right click and select New > Filter Chain.

beginsts_35

We’ll name the filter chain “Platform – SingleEvent”.  It will be used at both the TCP/IP and Directory input connectors.

beginsts_36

Then, right click and select Start Editor.

There will be an existing Codepage Filter already in place.  We won’t make any changes here.  From the Filter Chain menu, select Add Filter > External Filter.   Type “java.exe –jar ..\data\java\SingleEvent.jar” in the Value box, save and close.

beginsts_37

Navigate to the Platform next, where we’ll copy/paste the standard TCP_MOVEX and DirScan_MOVEX input connectors and rename them as “SingleEvent_TCP_MOVEX” and “SingleEvent_DirScan_MOVEX”, respectively.  Make the necessary changes to ports and input folders for the physical platform you’re using.

Now, we’ll add the Platform – SingleEvent filter chain to each of these input connectors. In the Platform, right click on the SingleEvent_TCP_MOVEX input connector and select Settings.

beginsts_38

From the logical platform, click on the “Filter chain” button and add the new filter chain.

beginsts_39

Repeat these steps for the SingleEvent_DirScan_MOVEX connector.

Verify all the changes, save and export the project and then deploy in Control Center to your new application.

Step 4:  Test

To test, drop the Streamfile into the DirScan folder in the Management Gateway and see the Filter Chain at work in the Control Center log. Here is a snip from the log:

beginsts_41

We can see our “Unknown line (ignored):***BEGIN STATEMENT SKIPPED***” message line and each duplicate header fields that has been ignored.  We also see the “ExternFilter exitCode:0” message indicating the filter completed successfully.

Here is our new document with one event and multiple lines:

beginsts_42

Final Thoughts

We’ve successfully written a Java class that works as a filter to remove unwanted BEGIN statements from a Streamfile, setup Streamserve to use Java and our new filter, and processed a test Streamfile through the filter.  Let me know how it works for your project and any tweaks you made for it to deliver the document you want.

Special thanks to the posters at StreamShare whose excellent variety of posts on filtering and using Java with Streamserve allowed me to combine them to achieve a solution.  If you work with Streamserve, make sure to check out the forums.

Now that we’ve seen how Java works with Streamserve, next time let’s discuss using Java to connect to M3 APIs and add information to a Streamserve PageOut.

Good luck,

Neil

Calling SOAP web services from MEC

Here is a primer on how to invoke SOAP web services from Infor M3 Enterprise Collaborator (MEC) using the Send Web Service process, where MEC is the SOAP client calling SOAP servers.

MEC process

I am using MEC version 11.4.3.

We find the Send Web Service process in Partner Admin > Agreement > Processes:
1

The properties are the following:
1_

The MEC Partner Admin Tool User Guide does not have much information:
3

The MEC training workbook does not have information either.

I decompiled MEC and found the Java class com.intentia.ec.server.process.SendWebServiceProcess. I was expecting it to use a legitimate SOAP client such as Apache CXF, but it uses a mere java.net.HttpURLConnection:
2

Consequently, this process does no more than the HTTPOut process albeit the additional SOAP action property.

Problems

There are many problems with this type of implementation:

  • It does not validate the message against the web service’s WSDL, the XML Schemas, not even against XML syntax
  • It does not have a factory to create client stubs
  • It is byte-based (we could send whatever content) whereas SOAP clients are more RPC-like with setters and getters for parameters
  • It is HTTP-centric, not SOAP-centric
  • It is restricted to HTTP whereas SOAP is agnostic to the underlying transport protocol, e.g. SOAP supports FTP
  • It does not support WS-Security for XML Encryption and XML Signature
  • It does not support the use of HTTP proxy
  • Etcetera

Anyway, let’s give it a try.

Sample web service

I have a sample web service from TMW SystemsLink, a Transportation Management Software. It is available on my customer’s network. It is only setup for HTTP (not HTTPS), without authentication, thus it is insecure, but it is easy for illustration purposes.

First, I ensure I can get to the WSDL:
5_

Test with SoapUI

Then, I test the web service with a SOAP client such as SoapUI:

Then, I get the SOAP action and content type to be used later (we can get them from the WS-A and http log tabs of SoapUI or from Fiddler):

Test with Java – optional

Optionally, I test the web service with the URLConnection in Java, from the same location and JRE as MEC:

javac Test.java && java -cp . Test
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;

class Test {
	public static void main(String[] args) throws Exception {
		URL url = new URL("http://tmwsl/TMWSystemsLink/APIWCFServices.svc");
		URLConnection con = url.openConnection();
		con.setDoOutput(true);
		con.setRequestProperty("Content-Type", "text/xml;charset=UTF-8");
		con.setRequestProperty("SOAPAction", "http://tempuri.org/IAPIWCFServices/RetrieveCarrier");
		String data = "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:tem=\"http://tempuri.org/\" xmlns:tmw=\"http://schemas.datacontract.org/2004/07/TMWSystems.SystemsLink.APIClasses\"><soapenv:Header/><soapenv:Body><tem:RetrieveCarrier><tem:criteria><tmw:CarrierID>JONCOL</tmw:CarrierID></tem:criteria></tem:RetrieveCarrier></soapenv:Body></soapenv:Envelope>";
		con.setRequestProperty("Content-Length", "" + data.length());
		OutputStreamWriter out = new OutputStreamWriter(con.getOutputStream());
		out.write(data);
		out.close();
		BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
		String s;
		while ((s = in.readLine()) != null) {
			System.out.println(s);
		}
		in.close();		
	}
}

The result is the SOAP response:
7

Test in Partner Admin

Now, I am ready to test in Partner Admin. I create a simple test agreement with the Send Web Service process configured to the end point address, content type, and SOAP action:
8

Then, I add a simple detection such as the default DiskIn, I reload the communication channel in the MEC Grid Management Pages, I create a file with the sample SOAP request, I drop the file in the DiskIn folder, I wait for MEC to process the file, and I check the result; the usual steps in MEC.

Result

Here is the result in MEC Grid Management Pages, it successfully finished sending the SOAP request, and getting the SOAP response:
9

Here is the resulting POST HTTP request: 10

At this point we can use Partner Admin and MEC Mapper to transform an M3 MBM and generate the SOAP request, we can process the SOAP response, etc.

Conclusion

That was an illustration of how to call SOAP web services from Infor M3 Enterprise Collaborator (MEC) using the Send Web Service process, for MEC to be a SOAP client calling SOAP servers. Despite “Web Service” in its name, the process is deceitfully not very SOAP oriented, but we can manage with it.

Future work

In future posts I will:

  • Explore how to securely call a web service over HTTPS (not HTTP); if I use a custom server certificate, I have to setup the JRE keystore, or explore the Partner Admin > Manage > Certificate Keystores:
    future1
  • Explore how to use the Partner Admin > Manage > Web Service Definitions; I do not know what this is for:
    future2
  • Explore how to use the Manage > Advanced > WebServiceSyncIn/Out; I think that is for MEC to be a SOAP server, accept requests, and serve responses:
    future3 future3_

That’s it.

Please leave a comment, click Like, click Follow to subscribe, share around you, and come write the next blog post.

PDF Merger – a starting point

One of our customer service’s (CS) requirement was to be able to retrieve from M3 a combined .pdf file containing the 3 main documents they usually send to the forwarding agent :

  • CMR (MWS610PF) : used for customs, international shipments
  • Customer invoice (OIS199PF)
  • Packing list (MMS480PF)

This requirement is not possible to meet in standard, even with Infor Document Management (IDM) installed within our M3 environment.

At the time being, CS team must :

  • retrieve one by one each correct .pdf file (we have duplicates on CMR… to solve)
  • print each document
  • scan the batch of files and send it to their personal mailbox
  • forward it to the proper “mail to”

The idea here is to offer the possibility to the final user to easily trigger “on demand” the combined .pdf files for one delivery index and receive it automatically in their mailbox.

 

This post is a starting point and present what can be done. The process can certainly be enhanced, so feel free to comment.

 

Technical choice made :

  • create a widget in Smart Office : allow document selection and delivery number entry
  • check data entered, return error if needed
  • trigger a MEC mapping that will fetch the requested files through a XQuery in IDM database (that’s where the IDM API play a role) + merge the files + send the result by email with attachment

note : MEC has been used because i actually don’t know well how to use an assembly in .NET but i am sure we can build a java program and call it directly from the script.

 

IDM – Infor Document Managment

In IDM, you can XQuery the database, asking for different documents tied to a given delivery number for example, but you can only select one line at a time. And there is no merge possibility at the time being.


Infor provides useful java APIs for IDM, already discussed in Thibaud’s post https://m3ideas.org/tag/document-archive/

 

The useful one is icp.jar that you can decompile :

 

The widget

Inspired by another post from Thibaud (https://m3ideas.org/2012/05/08/stand-alone-scripts-for-smart-office/), we create a shortcut on the canvas that will launch the script behind.


 

The code is pretty straightforward :

var task = new Task(new Uri('jscript://'));
task.AllowAsShortcut = false;
task.VisibleName = 'PDF Merger';
var runner =DashboardTaskService.Current.LaunchTask(task,HostType.Widget);
runner.Status = RunnerStatus.Running;
var host = runner.Host;
host.HostContent = CreateWindow();

host.HostTitle = 'PDF Merger';
host.ResizeMode = ResizeMode.NoResize;
host.Show();
host.Width = 370;
host.Height = 400;


The CreateWindow() function contains a WrapPanel with buttons, labels, textbox and a StatusBar to show some message if needed (it helped me debug).

 

The GO button has OnClick event.

//Button GO
var butGO= new Button();
butGO.Margin = new Thickness(10, 0, 10, 0);
butGO.Content = "GO !";
butGO.Width = "40";
butGO.Background = new SolidColorBrush(Colors.Blue);
butGO.Foreground = new SolidColorBrush(Colors.White);
wrapPanel.Children.Add(butGO);
butGO.add_Click(OnClickbutGO);

 

We monitor at least that :
– the delivery number exist in the system (MIWorker MWS410MI/GetHead)
– the email address of the user exist in CRS111 type 04 (MIWorker CRS111MI/Get)
– the user has selected at least one document (check property IsChecked on checkboxes)

public function OnClickbutGO(sender: Object, e: RoutedEventArgs) 
{
   
   if(cb1.IsChecked == false && cb2.IsChecked == false && cb3.IsChecked == false)
   {
           ConfirmDialog.ShowErrorDialogWithoutCancel("Select at least one document.");     
   }
   else if(EMAIL == '')
   {
        ConfirmDialog.ShowErrorDialogWithoutCancel("Email address missing in M3.","Check CRS111 type 04 and relaunch the widget.");
   }
   else if(dlixBox.Text != '')
   {
           //check DLIX value
        try
        {
          var record = new MIRecord();
          record["CONO"] = wCONO;
          record["DLIX"] = dlixBox.Text;
          
          MIWorker.Run("MWS410MI", "GetHead", record, OnRunCompletedMWS410MI);

        }
         catch (ex)
        {
          debug.WriteLine("ex : "+ex);
        }
   }
   else
   {
           ConfirmDialog.ShowErrorDialogWithoutCancel("DLIX number is mandatory.");
   }
   dlixBox.Focus();
}

… and if everything is OK then we trigger the MEC mapping by dropping an XML file into the proper directory.

 

The XML file is built by using  the XmlWriter (System.Xml).

I chose a classic MBM initiator detection in MEC (MBM_S2_R1) and built the global MBM intiator.

This kind of file can be dropped into the input directory. It’s also possible to go for a channel detection where you can drop a more light xml file with the minimum of tags; in this case, you can drop all the tags used for detection by the partner administrator.

Minimum mandatory data to transfer to MEC is :

  • the XQuery : tag “DocumentVariant”
  • the email address of the user (extracted from CRS111MI) : tag “DocumentNumber”

 

You can change the tags at your convenience!

function buildXMLTrigger() 
{
    var request : String = "(";
    if(cb1.IsChecked == true)  request = request + "/CMR|";
    if(cb2.IsChecked == true)  request = request + "/CustomerInvoice|";
    if(cb3.IsChecked == true)  request = request + "/DeliveryNote|";
    request = request + ")";

    statusbar.AddMessage(EMAIL);
    try
    {
         var xmlWriter = XmlWriter.Create("\\"+"\\YourServer\\PDF_Merger\\trigger"+dlixBox.Text+".xml");

         //MvxEnvelope
         xmlWriter.WriteStartDocument();
         xmlWriter.WriteStartElement("MvxEnvelope");
            //SubmitterID
            xmlWriter.WriteStartElement("SubmitterID");
                  xmlWriter.WriteStartElement("Host");
                  xmlWriter.WriteString("GGM3");
                  xmlWriter.WriteEndElement();

                  xmlWriter.WriteStartElement("EnvironmentID");
                  xmlWriter.WriteString("M3FDBPRD");
                  xmlWriter.WriteEndElement();

                  xmlWriter.WriteStartElement("Program");
                  xmlWriter.WriteString("PDF_MERGER");
                  xmlWriter.WriteEndElement();
            xmlWriter.WriteEndElement();//SubmitterID

            //Target
            xmlWriter.WriteStartElement("Target");
               //e-collaborator
               xmlWriter.WriteStartElement("e-collaborator");
                  //Sender
                  xmlWriter.WriteStartElement("Sender");
                     //Reference1
                     xmlWriter.WriteStartElement("Reference1");
                        //Data
                        xmlWriter.WriteStartElement("Data");
                        xmlWriter.WriteString(UserContext.CurrentCompany);
                        xmlWriter.WriteEndElement();//Data
                     xmlWriter.WriteEndElement();//Reference1
                     //Reference2
                     xmlWriter.WriteStartElement("Reference2");
                        //Data
                        xmlWriter.WriteStartElement("Data");
                        xmlWriter.WriteString(UserContext.CurrentDivision);
                        xmlWriter.WriteEndElement();//Data
                     xmlWriter.WriteEndElement();//Reference2    
                  xmlWriter.WriteEndElement();//Sender

                  //Recipients
                  xmlWriter.WriteStartElement("Recipients");
                     //Recipient
                     xmlWriter.WriteStartElement("Recipient");
                        //Reference1
                        xmlWriter.WriteStartElement("Reference1");
                           //Data
                           xmlWriter.WriteStartElement("Data");
                           xmlWriter.WriteString("PDF_MERGER");
                           xmlWriter.WriteEndElement();//Data
                        xmlWriter.WriteEndElement();//Reference1
                     xmlWriter.WriteEndElement();//Recipient    
                  xmlWriter.WriteEndElement();//Recipients

               xmlWriter.WriteEndElement();//e-collaborator
            xmlWriter.WriteEndElement();//Target
            //MvxBody   
            xmlWriter.WriteStartElement("MvxBody");
                  //MovexBusinessMessageInitiator
                  xmlWriter.WriteStartElement("MovexBusinessMessageInitiator");

                     xmlWriter.WriteStartElement("DocumentNumber");
                     xmlWriter.WriteString(EMAIL);
                     xmlWriter.WriteEndElement();

                     xmlWriter.WriteStartElement("DocumentVariant");
                     xmlWriter.WriteString(request+"[@M3_DLIX="+dlixBox.Text+"]");
                     xmlWriter.WriteEndElement();

                     //MessageKeys
                     xmlWriter.WriteStartElement("MessageKeys");
                        //CONO
                        xmlWriter.WriteStartElement("MessageKey1");
                           xmlWriter.WriteStartElement("Field");
                           xmlWriter.WriteString("CONO");
                           xmlWriter.WriteEndElement();
                           xmlWriter.WriteStartElement("Value");
                           xmlWriter.WriteString(UserContext.CurrentCompany);
                           xmlWriter.WriteEndElement();
                        xmlWriter.WriteEndElement();
                        //DIVI
                        xmlWriter.WriteStartElement("MessageKey2");
                           xmlWriter.WriteStartElement("Name");
                           xmlWriter.WriteString("DIVI");
                           xmlWriter.WriteEndElement();
                           xmlWriter.WriteStartElement("Value");
                           xmlWriter.WriteString(UserContext.CurrentDivision);
                           xmlWriter.WriteEndElement();
                        xmlWriter.WriteEndElement();
                        //DLIX
                        xmlWriter.WriteStartElement("MessageKey3");
                           xmlWriter.WriteStartElement("Name");
                           xmlWriter.WriteString("DLIX");
                           xmlWriter.WriteEndElement();
                           xmlWriter.WriteStartElement("Value");
                           xmlWriter.WriteString(dlixBox.Text);
                           xmlWriter.WriteEndElement();
                        xmlWriter.WriteEndElement();
                     xmlWriter.WriteEndElement();

                  xmlWriter.WriteEndElement();//MovexBusinessMessageInitiator
         
            xmlWriter.WriteEndElement();//MvxBody
         xmlWriter.WriteEndDocument();//MvxEnvelope

         xmlWriter.Close();
       }
       catch(ex)
       {
            statusbar.AddMessage(ex);
       }
}

 

MEC mapping

Some .jar are required to read the IDM database and to merge the .pdf files. We already have icp.jar at our disposal.

A quick search on a famous search engine and we find the wonderful and free (!)  pdfbox-app-2.0.2.jar that will do the job for us.

https://pdfbox.apache.org/download.cgi

In the ION mapper, import those .jar to the library :

 

The mapping is reduced the following one :

  • one function to retrieve the urls of all the .pdf files based on the user’s document selection + build the report in HTML
try 
{
            // Create and connect the connection
            com.infor.daf.icp.Connection conn = new com.infor.daf.icp.Connection("https://yourServer:20108/ca/", "login", "password", com.infor.daf.icp.Connection.AuthenticationMode.BASIC);
            conn.connect();
            
            String FlagFirst = "1";
            String docType = "";
            
            org.apache.pdfbox.multipdf.PDFMergerUtility ut = new org.apache.pdfbox.multipdf.PDFMergerUtility();
            
            // Execute an XQuery search and print the display name for all items 
            com.infor.daf.icp.CMItems items = com.infor.daf.icp.CMItems.search(conn, DocumentVariant, 0, 100);
            
            
            for(com.infor.daf.icp.CMItem item : items) 
            {
                for(com.infor.daf.icp.CMResource res : item.getResources().values()) 
                {
                    
                    if(res.getMimeType().equals("application/pdf"))
                    {
                        if(FlagFirst.equals("1"))
                        {
                            
                            String InitHTML = "<h1>PDF Merger report</h1><TABLE COLS=\"3\" FRAME=\"ALL\" BORDER-COLOR=\"Black\" BORDER=\"1\" VALIGN TD=\"MIDDLE\">";
                            String Titles = "<tr><th BGCOLOR=\"Navy\"><font COLOR=\"WHITE\">Doc type</font></th><th BGCOLOR=\"Navy\" <font COLOR=\"WHITE\">Doc name</font></th><th BGCOLOR=\"Navy\" <font COLOR=\"WHITE\">Version</font></th></tr>";
                            
                            TEXT = InitHTML + Titles;
                            
                            FlagFirst = "0";
                        }
                        if(res.getFilename().contains("MMS480PF")) docType = "Packing list";
                        if(res.getFilename().contains("MWS610PF")) docType = "CMR";
                        if(res.getFilename().contains("OIS199PF")) docType = "Cust. invoice";
                        
                        TEXT = TEXT + "<tr><td ALIGN =\"MIDDLE\">"+docType+"</td><td ALIGN =\"MIDDLE\">" +res.getFilename()+ "</td><td ALIGN =\"MIDDLE\">"+item.getVersion()+"</td></tr>";
                        
                        ut.addSource(res.getUrlStream());  
                    }
                }
                System.out.println("\n NodeName : " + item.getNodeName());
            }
            if(FlagFirst.equals("0"))
            {
                TEXT = TEXT + "</TABLE>";
            }
            
            ut.setDestinationFileName(getManifestInfo("agr:pathFrom")+"Docs_"+iValue+".pdf");
            fileAttachment = getManifestInfo("agr:pathFrom")+"Docs_"+iValue+".pdf";
            ut.mergeDocuments();
            
            conn.disconnect();
        } 
        catch(Exception e) 
        {
            e.printStackTrace();
        }
        
        EMAIL = DocumentNumber;
        DLIX = iValue;
  • one function to send an email with attachment
try
        {
            // SMTP
            java.util.Properties props = new java.util.Properties();
            props.put("mail.smtp.host", constMailServer);

            // session
            javax.mail.Session session = javax.mail.Session.getDefaultInstance(props, null);

            javax.mail.internet.InternetAddress addressFrom = new javax.mail.internet.InternetAddress(constFrom);
            

            // Create msg
            javax.mail.Message msg = new javax.mail.internet.MimeMessage(session);
            
            msg.setFrom(addressFrom);
            
            javax.mail.internet.InternetAddress addressTo[] = javax.mail.internet.InternetAddress.parse(EMAIL);
            msg.setRecipients(javax.mail.Message.RecipientType.TO, addressTo);
            msg.setSubject("PDF MERGED documents for delivery "+DLIX);
            
            javax.mail.internet.MimeBodyPart messageBodyPart = new javax.mail.internet.MimeBodyPart();
            messageBodyPart.setContent(TEXT,"text/html");
            javax.mail.Multipart multipart = new javax.mail.internet.MimeMultipart();
            multipart.addBodyPart(messageBodyPart);
            
            messageBodyPart = new javax.mail.internet.MimeBodyPart();
            javax.activation.DataSource source = new javax.activation.FileDataSource(fileAttachment);
            messageBodyPart.setDataHandler(new javax.activation.DataHandler(source));
            messageBodyPart.setFileName("Documents_"+DLIX+".pdf");
            multipart.addBodyPart(messageBodyPart);
            
            msg.setContent(multipart);
            
            javax.mail.Transport.send(msg);
        }
        catch(javax.mail.MessagingException e)
        {
            throw new MeCError(e.getMessage());
        }

 

 

Last but not the least, the final result in a screenshot :

Fig11

The attachment’s name contains the delivery number and the report displays all the .pdf retrieved from IDM.

Note : there are 3 invoices in the delivery (according to customer settings), 1 packing list and 1 CMR (with a duplicate…)

 

That’s it! Happy coding !

 

Maxime.

 

MForms Automation in Mashups

Let’s explore MForms Automation in Infor Smart Office Mashups.

MForms Automation

MForms Automation execute sequences of steps through M3 programs, e.g. run CRS610, enter a customer number in field W1OBKV, and press ENTER:
3_

MForms Automation Builder is located at C:\SmartOfficeSDK\External\M3\Tools\MFormsAutomationBuilder.exe:
1

In Mashups

To use them in Mashups, we tell MForms Automation Builder to create an MForms URI encoded, we copy/paste the URI into the desired property in Mashup Designer which will XML-encode it, and we carefully replace the hard-coded values by parameter names in curly braces with the corresponding parameter binding:

Smart Office will do the variable substitution, and will execute a CMDTP=RUN with the XML:

I find the whole process of URI-encoding, XML-encoding, and curly brace replacement too error-prone and time consuming for routine maintenance, so I found an alternative.

Alternative

I find it easier to preserve the original XML in a CDATA section in the XAML:
8_

Note: We must omit spaces between the CDATA start and XML declaration; the rest can be indented.

I find this alternative to be more elegant and easier to maintain than the double-encoded surgical process above.

Let me know what you think in the comments below.

That’s it.

260 subscribers!

More links

Here are more links to M3-related sites:

I added them to the list. There have been many recent new blogs. Fantastic!