Mashup quality control #5

Today I will verify if the MForms Bookmarks of the Infor Smart Office Mashups have any leftover hard-coded values – such as company (CONO), division (DIVI), facility (FACI), or warehouse (WHLO) – that developers may have forgotten to un-hard-code; in this fifth episode of Mashup quality control.

MForms Bookmark URI

I am looking for MForms Bookmark URI in Mashups. They look like this:

<mashup:Event SourceEventName="Click" LinkUri="mforms://bookmark/?program=OIS300&amp;tablename=OOHEAD&amp;startpanel=B&amp;includestartpanel=True&amp;requirepanel=True&amp;suppressconfirm=False&amp;source=MForms&amp;sortingorder=1&amp;view=E01&amp;name=Customer+Order.+Open+Toolbox+-+OIS300%2fB&amp;keys=OACONO%2c100%2cOAORNO%2c%2b&amp;fields=WWFACI%2cABC%2cWWDEL%2c0%2cWFORSL%2c05%2cWTORSL%2c99%2cWFORST%2c05%2cWTORST%2c99%2cW1OBKV%2c2" />

In my case, they are in <mashup:Event LinkUri=…> and <mashup:Link Uri=…>

Un-hard-coded values → OK

Un-hard-coded parameters are of the form CONO,{CONO},DIVI,{DIVI} where Smart Office will replace the names in curly braces with their runtime values. That’s the correct form.

Hard-coded values → error ⚠

If a URI has hard-coded values – such as CONO,200,DIVI,AAA – the user may get error messages such as “You must log on to company X before using the bookmark”:

Can you spot the hard-coded values in the example above? There are nine. It’s difficult for me to find them visually. I need a tool.

Bookmark Class

To parse a Bookmark URI, I could use the MForms.Mashup.Bookmark class:
b4

import MForms.Mashup;
var uri: String = "mforms://bookmark/?...";
var bookmark: Bookmark = new Bookmark(new Uri(uri));
debug.WriteLine(bookmark);

Test0

The result shows the keys. But the fields and parameters are missing or not accessible from this scope. Instead, I will parse the URI query myself.

Source code

Here is the final source code that will scan all Mashups, all XAML files, all MForms Bookmarks URI, in all keys, fields, and parameters, for hard-coded values; I use the built-in Regex to find the values not in curly braces:

import System;
import System.Collections.Specialized;
import System.IO;
import System.Text;
import System.Text.RegularExpressions;
import System.Web;
import System.Xml;
import Mango.Core.Util;
import Mango.UI.Services.Mashup;
import Mango.UI.Services.Mashup.Internal;

package MForms.JScript {
    class Test {
        public function Init(element: Object, args: Object, controller : Object, debug : Object) {
            var regex: Regex = StringUtil.GetRegex(ParameterBracket.Curly); // {(?<param>[\u0000-\uFFFF-[}]]*)}
            var mashups /*IList<FileInfo>*/ = PackageHelper.GetSharedMashupList();
            for (var mashup: FileInfo in mashups) {
                var baseUri: Uri = UriHelper.CreateBaseUri(new Uri(mashup.Name, UriKind.RelativeOrAbsolute));
                var manifest: Manifest = PackageHelper.GetManifest(mashup);
                var list /*IList<FileInformation>*/ = manifest.CreateFileInformationList();
                for (var information: FileInformation in list) {
                    if (information.MimeType == Defines.MimeTypeXAML) {
                        var relativeUri: String = information.Path;
                        var stream: Stream = PackageHelper.GetStream(baseUri, new Uri(relativeUri, UriKind.Relative));
                        var document: XmlDocument = new XmlDocument();
                        document.Load(stream);
                        var nodes: XmlNodeList = document.SelectNodes(" //@*[name()='Uri' or name()='LinkUri'] ");
                        for (var attribute: XmlAttribute in nodes) {
                            if (!attribute.Value.StartsWith("{Binding") && !attribute.Value.EndsWith(".xaml")) {
                                try {
                                    var uri: Uri = new Uri(attribute.Value);
                                    if (uri.Scheme == "mforms" && uri.Host == "bookmark") {
                                        var collection: NameValueCollection = HttpUtility.ParseQueryString(new Uri(uri).Query);
                                        for (var name: String in collection) {
                                            if ("keys,fields,parameters".Contains(name, StringComparison.InvariantCultureIgnoreCase)) {
                                                var pairs: String[] = collection[name].Split(",");
                                                for (var j: int = 0; j < pairs.Length; j = j + 2) {
                                                    var key: String = pairs[j];
                                                    var value: String = HttpUtility.UrlDecode(pairs[j + 1], Encoding.UTF8).Trim();
                                                    if (!String.IsNullOrEmpty(value)) {
                                                        if (!regex.IsMatch(value)) {
                                                            debug.WriteLine([mashup.Name, relativeUri, attribute.OwnerElement.Name, attribute.Name, key, value]);
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                } catch (ex: UriFormatException) {
                                    debug.WriteLine([ex, attribute.Value]); 
                                }
                            }
                        }
                        stream.Close();
                    }
                }
            }
        }
    }
}

I made it based on:

Mango.Core.Util.StringUtil
Mango.Core.Util.WebUtil.ReplaceParameters
Mango.UI.Services.Mashup.Link.LaunchLink
MForms.Mashup.Bookmark

Note 1: I originally had the XPath expression searching too widely as //@*[starts-with(., ‘mforms://’)] but that unnecessarily caught MForms Automation URIs like mforms://_automation?data= and URIs that start M3 programs like mforms://CRS610 . Then I made the XPath expression too precise as //@*[starts-with(., ‘mforms://bookmark/?’)] but that failed to catch several valid URIs: those that start with a white space, those that have backslashes such as mforms:\\ , those that are mixed case such as MFORMS:// , and those that do not have the slash in the query such as mforms://bookmark? , all of which are valid. I could have used the XPath function normalize-space() for trimming, but then XPath 1.0 does not have the lower-case() function, only XPath 2.0 does. So I eventually decided to change the XPath expression to look for attributes Uri and LinkUri, and search for scheme mforms and host bookmark in script once the URI is parsed.

Note 2: This code assumes the MForms Bookmark URIs are in either an attribute named Uri or LinkUri, with attribute value not ending in .xaml .

Result

The result is the following: a list of Mashups and XAML files, with MForms Bookmark URI that contain hard-coded values:

The most worrisome hard-coded values are: company (CONO), division (DIVI), and warehouse (WHYA); I will review those first. The hard-coded facility (FACI) are probably OK as they define a range from/to. There are also hard-coded dates (FVDT/LVDT), filters and positioners; I will review those next. The hard-coded inquiry type (QTTP) is probably OK.

The metrics in this case are:

  • 13 Mashups
  • 178 XAML files
  • 339 URIs
  • 61 of them are MForms Bookmark
  • 774 name/values pairs in the URIs
  • 284 of them are keys, fields, and parameters
  • 43 of them have hard-coded values (15% of above)
  • 10 of them to review (23% of above)

In other words, I was able to quickly scan about 800 values and identify 10 hard-coded-values to review. I would not have been able to pinpoint it as fast and as accurately manually.

Future work

Also, I want to:

That’s it!

Please comment, like, subscribe, share, author. Thanks for your support.

Related posts

Mashup quality control #4

In the previous post about my Mashup quality control tool for Infor Smart Office Mashups, I used the Smart Office API to list the Mashups and their XAML files; today I will load the XAML files and run my predicate rules.

GetStream

Mashup files are packed with ZIP compression. The Smart Office API has methods to unpack the XAML files with System.IO.Packaging, and to get their stream of bytes so we can load them with System.Xml.XmlDocument:
1

The Smart Office Mashup SDK documentation does not have much useful information, but I show it anyway:
2

Source code

Here is the source code to run a predicate rule on each XAML file of each Mashup; for illustration purposes, this example will list the <m3:ListPanel> controls that are missing the property IsListHeaderVisible, but you can replace this rule with whatever rule you wish:

 import System;
 import System.IO;
 import System.Xml;
 import Mango.UI.Services.Mashup;
 import Mango.UI.Services.Mashup.Internal;
 
 package MForms.JScript {
     class Test {
         public function Init(element: Object, args: Object, controller : Object, debug : Object) {
             var mashups /*IList<FileInfo>*/ = PackageHelper.GetSharedMashupList();
             for (var mashup: FileInfo in mashups) {
                 debug.WriteLine(mashup.Name);
                 var baseUri: Uri = UriHelper.CreateBaseUri(new Uri(mashup.Name, UriKind.RelativeOrAbsolute));
                 var manifest: Manifest = PackageHelper.GetManifest(mashup);
                 var list /*IList<FileInformation>*/ = manifest.CreateFileInformationList();
                 for (var information: FileInformation in list) {
                     if (information.MimeType == Defines.MimeTypeXAML) {
                         var relativeUri: String = information.Path;
                         var stream: Stream = PackageHelper.GetStream(baseUri, new Uri(relativeUri, UriKind.Relative));
                         var document: XmlDocument = new XmlDocument();
                         document.Load(stream);
                         var nsmanager: XmlNamespaceManager = new XmlNamespaceManager(document.NameTable);
                         nsmanager.AddNamespace("m3", "clr-namespace:MForms.Mashup;assembly=MForms");
                         var nodes: XmlNodeList = document.SelectNodes("//m3:ListPanel[not(@IsListHeaderVisible=\"True\")]", nsmanager);
                         if (nodes.Count == 0) {
                             debug.WriteLine("\t" + relativeUri + " PASSED");
                         } else {
                             debug.WriteLine("\t" + relativeUri);
                             for (var e: XmlElement in nodes) {
                                 debug.WriteLine("\t\t" + e.Attributes["Name"].Value + " FAILED");
                             }
                         }
                         stream.Close();
                     }
                 }
             }
         }
     }
 } 

It was inspired by:
Mango.UI.Services.Mashup.Internal.MashupApplication.InitMashups
Mango.UI.Services.Mashup.Internal.PackageHelper.GetStream
Mango.UI.Services.Mashup.MashupInstance.CreateInstance.

Note: For the namespace, I should use the fields NamespacePrefix and NamespaceUri, or even better the field NamespaceManager, from MForms.Mashup.MashupUtil, but they are internal; I may use Reflection next time.

Result

In my case, the result is the list of <m3:ListPanel> controls and whether they PASSED or FAILED the predicate rule:

For the metrics:

  • Number of Mashups: 13 (7 FAILED, 54% error)
  • Number of XAML files: 178 (40 FAILED, 29% error)
  • Number of <m3:ListPanel>: 347 (115 FAILED, 33% error)

In other words, with this tool I was able to quickly scan about 200 files, covering over half of the Mashups, and find errors in a third of the <m3:ListPanel>. I would not have been able to do the same manually so fast with such accuracy.

Future work

There is more work to be done. Next time I will explore Mango.UI.Services.Mashup.Internal.MashupNameScope.CreateInstance. Stay tuned.

That’s it.

Please comment, like, subscribe, share, author. Thanks for your support.

Related posts

Mashup quality control #3

I am attempting to develop a quality control tool for Infor Smart Office Mashups. In my original post I was using Python. This time I will use the Smart Office API to get the list of Mashups.

List of Mashups in Smart Office

Smart Office automatically installs the Mashups on each user’s computer:
3

The list is based on what is installed in LifeCycle Manager:
4

Smart Office uses the Mango web services CategoryFilesManager.ListAllCategoryFileInfo and ListCategoryFileInfo to get the list of Mashups:
6

And it stores the files locally in a temporary user based path:
5

The administrator can fine tune the Mashups access:
7

That is more than sufficient information for my needs. Let’s move on.

Smart Office API

The Smart Office API has a class PackageHelper for Mashups:
1

I could not find it in the Smart Office Mashup SDK Documentation, but here is a screenshot of the documentation anyway:
1_

Source code

Here is the source code to get the list of Mashups, the Manifest, the XAML files, and the resources:

import System.IO;
import System.Xml;
import Mango.UI.Services.Mashup.Internal;

package MForms.JScript {
  class Test {
    public function Init(element: Object, args: Object, controller : Object, debug : Object) {
      var mashups /*IList<FileInfo>*/ = PackageHelper.GetSharedMashupList();
      for (var i: int = 0; i < mashups.Count; i++) {
        var mashup: FileInfo = mashups[i];
        debug.WriteLine(mashup.ToString());
        var manifest: Manifest = PackageHelper.GetManifest(mashup);
        var xml: XmlDocument = manifest.Document;
        var files: XmlNodeList = xml.SelectNodes("/Manifest/Files/File");
        for (var j: int = 0; j < files.Count; j++) {
          var node: XmlElement = files[j];
          var relativeUri: String = node.GetAttribute("Path");
          debug.WriteLine(relativeUri);
        }
        debug.WriteLine("");
      }
    }
  }
} 

It was inspired by PackageHelper.GetSharedMashupList and Manifest.CreateFileInformationList.

In addition to GetSharedMashupList(), we can also use GetPrivateMashupList()GetLocalMashupList() to get the various Mashups origins: shared, private, and local.

Reminder: To deploy a shared Mashup, use LifeCycle Manager > Admin > Upload Products > Upload; to deploy a Mashup privately, use Mashup Designer > Deploy > Private; and to deploy a Mashup locally, use Smart Office > Show > My Local Applications > Install.

Result

Here is the resulting list of files: *.mashup, *.xaml, *.png, etc.:
2

Future work

The next steps are:

  • Load the XAML and run my predicate rules
  • Deal with the name scoping across multiple files, e.g. <mashup:Event SourceName=”CustomerList”> where CustomerList is in a separate file
  • Instead of simple static analysis of the XAML files, do dynamic analysis as well, i.e. when the Mashups are running in Smart Office.
  • Explore the UriHelper.CreateMashupUri, Package.Open, Package.GetParts, PackageHelper.GetStream, MashupInstance.CreateInstance, MashupInstance.Load, MashupNameScope, etc.

That’s it.

Please comment, like, subscribe, share, author. Thanks for your support.

Related posts

Missing columns? Restore columns

Somehow I am doing a lot of maintenance and troubleshooting of Infor Smart Office Mashups these days.

Missing columns

Today I had the unusual case of a user that was missing many columns in a list. The list is an <m3:ListPanel> of STS300/B1 in a Mashup. The problem did not occur when running STS300 directly, it did not occur for other users, and it did not occur with his userid on another computer. The problem occurred only with the specific combination of his userid, on his computer, on that M3 program, in that Mashup. Good luck troubleshooting that.

Here is an illustration of missing columns, shown here simply with CRS046/B:

Troubleshooting in vain

We wasted time troubleshooting in vain: the XAML source code, the sorting order, the view, personal views. the filters, the personalizations, the Bookmark in MTS043, the M3 program Java source code, the interactive subsystem, and we uninstalled/re-installed Smart Office, without progress.

Duh, restore the columns

Then, the user selected right-click > Restore Columns in the list, and that fixed the problem [FACEPALM] It is one of those little things that are easy to forget. We do not know how 32 columns accidentally went missing in the first place, it is not something one does by mistake without remembering. That is still a mystery.

Anyway, here is the reminder for next time:

User specific data storage

Where are those settings stored you ask? They are stored in the file MFormsColumnDefinitions.xml in the user specific data storage path, and are persisted to disk when the user logs out of Smart Office:
b5

And those settings are loaded and saved in MForms.List.ListColumnManager:
b6

That’s it.

Please comment, like, subscribe, share, author. Thanks for your support.

Mashup quality control #2

Today I had to troubleshoot why a refresh icon in a Smart Office Mashup was not refreshing, and after correcting it, I included the correction as a new predicate rule in my Mashup quality control tool to automatically spot similar errors in other Mashups.

IconButtons and CommandBarButtons

Here is a screenshot of the icon bar and its XAML code:

You can find more information about the Smart Office Design System’s Icons, IconButtons and CommandBarButons on norpe’s blog post.

The error

After some manual troubleshooting, I found that the icon was refreshing the wrong list, i.e. the Click event had the TargetName property set to the wrong <m3:ListPanel>, it was set to PurchaseOrderList (incorrect) instead of RequisitionList (correct). Probably the developer copy/pasted the code from another tab and forgot to change the TargetName. That’s hard to find, quick to fix.

<ds:CommandBarButton IconName="Refresh" ToolTip="{mashup:Constant Refresh, File=Mango.UI}">
    <ds:CommandBarButton.CommandParameter>
        <mashup:Event TargetName="PurchaseOrderList" TargetEventName="Refresh" />
   </ds:CommandBarButton.CommandParameter>
</ds:CommandBarButton>

Finding other errors

Assuming the icons are grouped in a <StackPanel> followed by their <m3:ListPanel>, I can quickly find similar errors with the following XPath expression that lists all the icons TargetName:

//*[name()='ds:IconButton.CommandParameter' or name()='ds:CommandBarButton.CommandParameter']/mashup:Event/@TargetName

The Python code would be:

import os
import glob
import lxml.etree as etree

for f in glob.glob(os.path.join(r'C:\BuyerPortal', '*.xaml')):
    tree = etree.parse(f)
    r = tree.xpath("//*[name()='ds:IconButton.CommandParameter' or name()='ds:CommandBarButton.CommandParameter']/mashup:Event/@TargetName", namespaces={'mashup': 'clr-namespace:Mango.UI.Services.Mashup;assembly=Mango.UI', 'ds': 'clr-namespace:Mango.DesignSystem;assembly=DesignSystem'})
    for element in r:
        print(f, element)

Result

Then I visually inspect the result for outliers. In my case, I see the error I found earlier, and a new error I did not know about:
3

This is a quick way to help identify errors before users have to.

Future work

This still requires a visual inspection of the result. A better solution would be to calculate the distance between the icon and its target m3:ListPanel in the XAML tree, where a minimum distance would indicate a low probability of error, and a maximum distance would indicate a high probability of error.

That’s it!

Please comment, like, subscribe, share, author. Thanks for your support.

Related posts

Mashup quality control #1

It has become difficult for me to manually maintain the Smart Office Mashups of my customer – there are about 50 files, 1,000 controls, and 10,000 lines of XAML – so I am developing a software verification tool that does automatic quality control for me.

How?

I defined a set of predicate rules, and I use XPath to validate the Mashup against those rules. I am using Python for now because of its expressiveness and interactivity, but I will port it to JScript.NET or C# soon to benefit from the Smart Office API.

Sample rule

As a sample rule, I want all the <m3:ListPanel> controls to have the property IsListHeaderVisible=”True” such that users have the ability to expand the list header and change the sorting order, view, and apply filters. If one of the list panels does not have that property I want to know about it and correct it. Note this is my own preference, and other developers may have the opposite preference.

Here is the property in Mashup Designer:
3

The following XPath expression will return the list panels not validating the rule:

//m3:ListPanel[not(@IsListHeaderVisible="True")]

Here is a Python code to validate that rule:

import os
import glob
import lxml.etree as etree

for f in glob.glob(os.path.join(r'C:\RentalCounterMashup', '*.xaml')):
    tree = etree.parse(f)
    r = tree.xpath('//m3:ListPanel[not(@IsListHeaderVisible="True")]', namespaces={'m3': 'clr-namespace:MForms.Mashup;assembly=MForms'})
    for element in r:
        print(f, element.attrib['Name'])

The result is the following, a list of XAML filenames and <m3:ListPanel> names that fail the rule:
4

Result

In my example, out of 63 list panels, 46 had the property, and 17 were missing the property, that’s 27% of list panels not passing the quality control. In other words, I was able to quickly identify a third of the list panels to correct.

Future work

I have many more ideas to implement, for example:

  • Ensure there are no hard-coded values in the MForms Bookmarks, Links, and MForms Automation, such as hard-coded CONO or DIVI
  • Automatically correct the Mashup, e.g. set IsListHeaderVisible=”True” if missing, and save

That’s it!

Let me know in the comments below if you have other rules to control the quality of Mashups.

Please click Like, share this post with your colleagues, click Follow to subscribe, come write the next blog post with us, and send some love to the other M3 blogs as well. This is a volunteer-based community, and your participation keeps the community alive and growing. Thank you.

Related posts

Inspect tool for Mashups

How great the Inspect tool is for developing Mashups in Infor Smart Office! I mentioned it a long time ago in another post about developing scripts, and I want to mention it here again.

I am currently maintaining a Mashup that other developers created. That Mashup has 30 XAML files, 10,000 lines of code, 500 controls, 80 tabs in three levels, etc. Any time I need to modify the Mashup, I have to follow a thread to find the relevant line of source code.

With the Inspect tool, I can point at a control in the Mashup (watch cursor) to find any of its parts, find its name, its parent tab, its XAML file, etc. That saves me valuable time.

Here are some screenshots:
1 2 3

I wish the Mashup Designer had the same watch cursor feature. Maybe it is easy to implement with the Smart Office SDK. To be explored.

There is also the Snoop tool I mentioned in the other post. Try that too.

That’s it!

Let me know in the comments below what other tools you use. Share this post. Click Like. Follow this blog. And come write the new blog post with us. This is a volunteer-based community, and your participation keeps it going. Thank you.

Default country in Mashup ComboBox

Quick illustration of how to set the user’s country as the default selection in a Mashup ComboBox in Infor Smart Office.

Suppose you have a <m3:MIComboBox>, and you want it to be a list of countries (e.g. FR-France, SE-Sweden, US-United States, etc.), populated from the M3 API CRS045MI.LstByCode, displaying CSCD and TX40, and you want the default selection to be the user’s country (e.g. US).

For that, I will use the System.Globalization.RegionInfo.CurrentRegion’s property TwoLetterISORegionName and assume that CRS045 uses the same two-letter ISO codes.

Here is the code with the relevant lines:

<StackPanel
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:mashup="clr-namespace:Mango.UI.Services.Mashup;assembly=Mango.UI"
   xmlns:glob="clr-namespace:System.Globalization;assembly=mscorlib"
   xmlns:m3="clr-namespace:MForms.Mashup;assembly=MForms">
    <TextBlock Name="MyCountry" Text="{Binding Source={x:Static glob:RegionInfo.CurrentRegion}, Path=TwoLetterISORegionName}" Visibility="Hidden" />
    <m3:MIComboBox Name="Countries" SortField="CSCD" SelectedValuePath="[CSCD]" SelectedValue="{Binding ElementName=MyCountry, Path=Text}" Width="200">
       <m3:MIComboBox.Events>
          <mashup:Events>
             <mashup:Event SourceEventName="Startup" />
          </mashup:Events>
       </m3:MIComboBox.Events>
       <m3:MIComboBox.DataSource>
          <m3:MIDataSource Program="CRS045MI" Transaction="LstByCode" OutputFields="CSCD,TX40" IsCacheable="True" />
       </m3:MIComboBox.DataSource>
       <m3:MIComboBox.ItemTemplate>
          <DataTemplate>
             <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Path=[CSCD]}" />
                <TextBlock Text=" - " />
                <TextBlock Text="{Binding Path=[TX40]}" />
             </StackPanel>
          </DataTemplate>
       </m3:MIComboBox.ItemTemplate>
    </m3:MIComboBox>
 </StackPanel>

Here is the result:
blog

blog_

That’s it. Please like, share, subscribe, author.

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?

Application messages in Infor Smart Office

Here is an illustration of application messaging in Infor Smart Office to send, broadcast, and receive messages, and process responses between applications in Smart Office, whether in scripts, Mashups, or other entities.

Documentation

The Infor Smart Office SDK Developer’s Guide has Chapter 19 Application messages, and the Smart Office SDK help file has the API reference for Mango.UI.Services.Messages.ApplicationMessageService:
3

Note: For more information on the Smart Office SDK refer to my previous post.

Scripts

Here are some examples of sending, broadcasting and receiving messages, and processing responses in Smart Office scripts; the other party in the communication can be another script, a Mashup or another entity in Smart Office.

To send a message and process the response:

import Mango.UI.Services.Messages;

package MForms.JScript {
	class ApplicationA {
		public function Init(element: Object, args: Object, controller : Object, debug : Object) {
			// send message
			var message: ApplicationMessage = new ApplicationMessage();
			message.Sender = "ApplicationA";
			message.Recipient = "ApplicationB";
			message.Parameter = "Hello World";
			var response: ApplicationMessageResponse = ApplicationMessageService.Current.SendMessage(message);
			// process response
			response.MessageStatus;
			response.Result;
		}
	}
}

To receive a message and return a response:

import Mango.UI.Services.Messages;

package MForms.JScript {
	class ApplicationB {
		public function Init(element: Object, args: Object, controller : Object, debug : Object) {
			ApplicationMessageService.Current.AddRecipient("ApplicationB", OnMessage);
		}
		function OnMessage(message: ApplicationMessage): ApplicationMessageResponse {
			message.Sender;
			message.Recipient;
			message.Parameter;
			var response: ApplicationMessageResponse = new ApplicationMessageResponse();
			response.MessageStatus = MessageStatus.OK;
			response.Result = "Bonjour";
			return response;
		}
	}
}

To broadcast a message (there is no response):

import Mango.UI.Services.Messages;

package MForms.JScript {
	class ApplicationC {
		public function Init(element: Object, args: Object, controller : Object, debug : Object) {
			var broadcastMessage: ApplicationMessage = new ApplicationMessage();
			broadcastMessage.Sender = "ApplicationC";
			broadcastMessage.Recipient = "GroupX";
			broadcastMessage.Parameter = "HELLO WRRRLD!!!!!!";
			ApplicationMessageService.Current.SendBroadcastMessage(broadcastMessage);
		}
	}
}

To receive a broadcasted message (there is no response):

import Mango.UI.Services.Messages;

package MForms.JScript {
	class ApplicationD {
		public function Init(element: Object, args: Object, controller : Object, debug : Object) {
			ApplicationMessageService.Current.AddBroadcastRecipient("GroupX", OnBroadcastMessage);
		}
		function OnBroadcastMessage(message: ApplicationMessage) {
			message.Sender;
			message.Recipient;
			message.Parameter;
		}
	}
}

Mashups

Here are some examples of sending, broadcasting and receiving messages in Smart Office Mashups (there are no responses); the other party in the communication can be another Mashup, a script or another entity in Smart Office.

To send a message (there is no response):

<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ui="clr-namespace:Mango.UI.Controls;assembly=Mango.UI" xmlns:mashup="clr-namespace:Mango.UI.Services.Mashup;assembly=Mango.UI">
	<Button Name="BtnMessage" Content="Send" Width="150" />
	<mashup:ApplicationMessageControl Name="test">
		<mashup:ApplicationMessageControl.Events>
			<mashup:Events>
				<mashup:Event SourceName="BtnMessage" SourceEventName="Click" TargetEventName="Send" Debug="True">
					<mashup:Parameter TargetKey="Sender" Value="MashupE" />
					<mashup:Parameter TargetKey="Recipient" Value="MashupF" />
					<mashup:Parameter TargetKey="Parameter" Value="Hello World" />
				</mashup:Event>
			</mashup:Events>
		</mashup:ApplicationMessageControl.Events>
	</mashup:ApplicationMessageControl>
</Grid>

To receive a message (there is no response):

 <Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ui="clr-namespace:Mango.UI.Controls;assembly=Mango.UI" xmlns:mashup="clr-namespace:Mango.UI.Services.Mashup;assembly=Mango.UI">
	<mashup:ApplicationMessageControl Name="test" Recipient="MashupF">
		<mashup:ApplicationMessageControl.Events>
			<mashup:Events>
				<mashup:Event SourceEventName="Received" Debug="True">
					<mashup:Parameter SourceKey="Sender" />
					<mashup:Parameter SourceKey="Recipient" />
					<mashup:Parameter SourceKey="Parameter" />
				</mashup:Event>
			</mashup:Events>
		</mashup:ApplicationMessageControl.Events>
	</mashup:ApplicationMessageControl>
</Grid>

To broadcast a message:

<mashup:Event...TargetEventName="Broadcast">

To receive a broadcasted message:

<mashup:ApplicationMessageControl...BroadcastRecipient="Everyone">

Illustration

Here is an illustration of messages going in all directions between scripts and Mashups:
r

More

The ApplicationMessageService API has more methods and properties available:

  • You can send multiple parameters and multiple result values with Dictionary<string, Object>.
  • You can return different MessageStatus values depending on your needs, for example OK, Failed or InvalidMessage.
  • You can send a message asynchronously to not block the UI; but there is no response.
  • You should verify if the recipient already exists before adding a message handler; otherwise you will get the exception “A recipient with the key has already been added.”
  • You can remove recipients.

Refer to the SDK documentation for more information.

That’s it. Please like, comment, share, follow, contribute.