Building an Infor Grid Lab – Part 1

These days I am doing a lot of work with the Infor ION Grid – to learn, troubleshoot, and do penetration testing – and I need to setup my own laboratory. I will follow the footsteps of PotatoIT’s Lab.

Grid concepts

The Infor ION Grid is a proprietary application framework to run Java applications in a distributed, redundant, fail-over, load balanced, scalable, performant, and secure environment, sort of a crossing between IBM WebSphere Application Server (WAS) and Platform as a Service (PaaS), for the purposes of Infor products, and that over the years has become a rich framework that helps power the Infor CloudSuite. Grid concepts are explained in the Infor documentation and in my previous work. Basically, there are: hosts (physical/virtual machines), a registry (to keep track of the nodes), nodes (JVM), applications (e.g. M3), routers (to direct network traffic), and more.

Download

The Grid is available for download from the Infor Xtreme Product Download Center:

Documentation

The Installation Guide has a chapter Installing Infor ION Grid:

LCM? No.

The documentation says Infor LifeCycle Manager (LCM) is a prerequisite to install the Grid. But in my previous encounter with LCM I had concluded I can reproduce installation steps manually without LCM, albeit with a lot of work. Anyway, for my purposes I just need a minimal Grid without Infor M3 which makes the installation easier. To that end, I set out to learn how to install a minimal Grid manually without LCM. I will split my learning into several blog posts.

Version 0.x

In my archives of 10 years ago I found an early internal development unreleased version of the Grid with some documentation. It was a pure Java application that started Grid hosts, nodes, routers, registry, and user interface. It did not have database, certificates, configuration, or web server. It was not available publicly. Thanks to its simplicity, I will use it as a starting point of my learning.

1) Start the registry

java -cp grid.jar com.lawson.grid.Startup -registry -groupName THIBAUD

2) Start a node

java -cp grid.jar com.lawson.grid.Startup

3) Start a router

java -cp grid.jar com.lawson.grid.Startup -router

4) Start the user interface

java -jar grid.jar localhost 44444

Result

We have a minimal Grid with a host, a registry, a node, a router, and a user interface.

Future work

In my next blog posts, I will:

  • Install a later version of the Grid
  • Use the new Grid installer
  • Install the Grid on Linux and PostgreSQL

Conclusion

That was a starting point for me to learn how to install a minimal Infor ION Grid manually without LifeCycle Manager. I will continue in the next post.

That’s it!

Related posts

Access Control on Grid Management Pages

It is my great pleasure to be an author of M3 Ideas. Thanks very much for Thibaud’s invitation.

I put my foot into M3 water five years ago and M3 Ideas has been giving me great help in developing my technical skills since then. It is my great honor to share my work and make contributions to it.

The issue of access control on grid management pages has been a trouble to me for nearly two years. The installation web page of ISO, in our case, http://BE Server:19005/LSO/index.html is very close to the grid information page, http://BE Server:19005/grid/info.html and the grid management pages, http://BE Server:19005/grid/ui/#. It is fairly easy for a user with web skills to figure out the latter two web pages and he/she can explore and check basically all the grid information.

Although the grid management pages requires credentials to log on to be able to start or stop a process, a user actually can try the default account,  and the famous password, which are mentioned by the Companion, to get full control. This is not a joke, but a real case. It makes M3 system really vulnerable. Even if users do not make any actions to it, there is no sense and it is ridiculous to disclose all the system information to the public.

We did raise InforXtreme case for Infor to fix it. However, they could not give a robust solution after a long time discussion. The reason is the grid management pages and LSO is sharing the same Java process in the BE server. If you check the TCP connections and processes using netstat in the BE server, you would find there is no way to tell which connection is from the grid management pages visit or from LSO request, which is the biggest technical challenge.

After many tests it seems the most feasible solution is to analyze each incoming and outgoing TCP packet in the BE sever to see whether there are any patterns. If we can be 100% sure that a remote IP address is visiting the grid management pages according to the patterns, then we can apply IPSec to block it automatically. Although it means that IP address cannot use LSO as well, it can be sorted out by manually removing it from the black list given a promise is made not to visit again. If the promise is broken, there is no second chance for them. It is something like Damocles’ Sword.

So I made a Console Application using C# as follows.


using System;
using System.Collections.Generic;
using SharpPcap;
using SharpPcap.LibPcap;
using System.Text;
using System.Net.Mail;
using System.Diagnostics;

namespace CA_Grid
{
class CA_Grid
{
static List<string> jsIPList;

static void Main(string[] args)
{
jsIPList = new List<string>();

var varDevices = CaptureDeviceList.Instance;

if(varDevices.Count<1) { Console.WriteLine("No devices were found on this machine"); return; } Console.WriteLine("The following devices are available on this machine:"); Console.WriteLine("----------------------------------------------------"); Console.WriteLine(); int i = 0; foreach(var varDec in varDevices) { Console.WriteLine("{0}) {1}", i, varDec.Description); i++; } Console.WriteLine(); Console.Write("-- Please choose a device to capture: "); int varChoice = 0; if (!int.TryParse(Console.ReadLine(), out varChoice) || varChoice>=i)
{
Console.WriteLine("The device is not valid!");
return;
}

ICaptureDevice varDevice =varDevices[varChoice];

varDevice.OnPacketArrival += Device_OnPacketArrival;

varDevice.Open();
varDevice.Filter = "ip and tcp";
Console.WriteLine();
Console.WriteLine("-- Listening on {0}, hit 'Ctrl-C' to exit...",varDevice.Description);

varDevice.Capture();

varDevice.OnPacketArrival -=Device_OnPacketArrival;
varDevice.Close();
}

private static void Device_OnPacketArrival(object sender, CaptureEventArgs e)
{
try
{
string varMIP = "BE Server IP Address";
if (e == null || e.Packet == null)
return;

if (e.Packet.Data == null)
return;

var varPacket = PacketDotNet.Packet.ParsePacket(e.Packet.LinkLayerType, e.Packet.Data);
if (varPacket == null)
return;

if (varPacket.GetType()!=typeof(PacketDotNet.EthernetPacket))
return;

var varIP = (PacketDotNet.IpPacket)varPacket.Extract(typeof(PacketDotNet.IpPacket));
if (varIP == null)
return;

string varDIP = varIP.DestinationAddress.ToString();
string varSIP = varIP.SourceAddress.ToString();

if (varSIP != varMIP)
return;

var varTCP = (PacketDotNet.TcpPacket)varPacket.Extract(typeof(PacketDotNet.TcpPacket));
if (varTCP == null)
return;

if (varTCP.PayloadData == null)
return;

var varData = Encoding.UTF8.GetString(varTCP.PayloadData);

if (varData == null)
return;

string varDPort = varTCP.DestinationPort.ToString();
string varSPort = varTCP.SourcePort.ToString();

bool varHTTPS = varSPort == "443";
bool varHTTP = varSPort.Contains("19005") && varData.Contains("text/html") && varData.Contains("gzip");
if ((varHTTPS || varHTTP) && !jsIPList.Contains(varDIP))
{
jsIPList.Add(varDIP);

string varWay = varHTTP ? "HTTP" : "HTTPS";
string varM1 = "----------------------------------------------------";
string varM2 = string.Format("{0}, {1}, {2}", DateTime.Now, varDIP, varWay);
string varM3 = "Original IP packet: " + varIP.ToString();
string varM4 = "Original TCP packet: " + varTCP.ToString();
string varM5 = "Original TCP Header: " + varData;

if (varDIP != "IP to Exclude")
{
using (var varProcess = new Process())
{
varProcess.StartInfo.FileName = "cmd.exe";
varProcess.StartInfo.UseShellExecute = false;
varProcess.StartInfo.RedirectStandardInput = true;
varProcess.StartInfo.RedirectStandardOutput = true;
varProcess.StartInfo.RedirectStandardError = true;
varProcess.StartInfo.CreateNoWindow = true;
varProcess.Start();

string varCommand = "netsh ipsec static add filter filterlist=\"IP_Filter_Grid\" srcaddr=" + varDIP + " dstaddr=me protocol=TCP mirrored=No";
varProcess.StandardInput.WriteLine(varCommand);
varProcess.StandardInput.WriteLine("exit");
varProcess.StandardInput.AutoFlush = true;
varProcess.WaitForExit();
}
}

Console.WriteLine(varM1);
Console.WriteLine(varM2);
Console.WriteLine();
Console.WriteLine(varM3);
Console.WriteLine();
Console.WriteLine(varM4);
Console.WriteLine();
Console.WriteLine(varM5);
Console.WriteLine();

using (var varSMTP = new SmtpClient("Mail Forwarder IP Address"))
{
using (var varMail = new MailMessage())
{
varMail.From = new MailAddress("from address");
varMail.To.Add("my e-mail address");
varMail.Subject = "Unauthorised Access to Infor Grid Management Pages";
varMail.Body = varM1 + "\r\n" + varM2 + "\r\n" + "\r\n" + varM3 + "\r\n" + "\r\n" + varM4 + "\r\n" + "\r\n" + varM5;
varSMTP.Send(varMail);
}
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}

SharpPcap can be downloaded from GitHub. It has two Dlls, SharpPcap for capturing packets and Packet.Net for packet analysis. WinPcap needs to be downloaded and installed into the BE server. There is no need to restart the server after installation.

The console application is running in an asynchronous mode, so there is trivial impact on the M3 performance.

The script starts with detecting all the devices first. Then it asks for a device number to listen to. Each device could be a network adapter. A filter is applied to the device for IP and TCP packets. An event to capture the packets is attached to the device. Finally the listening starts.

The event handler has the key logic.

1. Two patterns to find the visit to the grid management pages.

(1) HTTPS: The source port would be 443.

(2) HTTP: The packet contains text/html and gzip.

HTTPS communication is encrypted, so we cannot analyze the packet content. Fortunately LSO never uses the port of 443.

2. If the destination IP address is not on the white list, then it would be added to the IPSec filter list. We need to manually create a IPSec policy and a IPSec filter & Action beforehand.

3. Console would output the captured information.

4. An e-mail alert would be sent with the captured information.

5. A global variable of jsIPList is used to keep the IP addresses already captured to avoid repeated alerts.

Then it works. There were three IT users from local division, who still tried to access to the grid management pages on Monday as usual. Then they found they could not visit it any more, as well as LSO. So far there are no normal users to be blocked, no matter whether they run M3 programs, or download LSO from the installation web page. All of sudden the two-year headache is gone.

If you have the similar concerns to me, I hope the above solution would give you a ride. If you have got any issues using the solution, please reply to this post or send me an e-mail at warren.dahai.hou@gmail.com.

Finally my heartfelt appreciations to all the authors on this website, as well as the author of SharpPcap and Packet.Net.

Grid CLI

Here is a quick peek at the Infor ION Grid command line interface (CLI).

Early days

The earliest reference I found is the following. The Grid Scripting Utility, a.k.a. Grid Script Client, a.k.a. Scripting Client, was introduced in Lawson Grid 10.1.8.0 in 2011, and can control the Grid and Grid applications, e.g. start/stop the Grid and applications, as well as query the Grid for status information:
doc0

java –cp grid-core.jar;grid-ui.jar com.lawson.grid.util.ScriptingClient agentAddress agentPort -ks keystoreFileName keystorePassword command target

It does not seem to be available anymore, at least I could not find grid-ui.jar. And as per NCR 6975“CLI tool is to be seen as a replacement for Scripting Client that has been deprecated and will be discontinued in Grid 2.0.”

Nowadays

In the Grid 11.1.13, there is a command line interface (CLI), a.k.a. Grid Commander:
4

D:\LifeCycle\host\grid\DEV\tools>java -jar grid-cli.jar

3

I could not find documentation about it, but here is the first set of commands:

grid
    start                     Start the Grid
    stop                      Stop the Grid
    info                      Display Grid name, version and state
    offline                   Set Grid offline
    online                    Set Grid online


router
    list                      List routers


host
    start                     Start the specified hosts. Defaults to the local host if no host is specified
    stop                      Stop the specified hosts. Defaults to the local host if no host is specified
    list                      List all hosts in the Grid
    configureHost             Update host configuration, if the address is changed and the new address is not valid for the current host certificate the certificate will be regenerated, this command requires the grid to be stopped
    setSecondaryHost          Set secondary host, this host will get the registry and administrative router in case of the primary host becomes unavailable
    changePrimaryHost         Move Registry and Administrative router to a second host, make sure <gridname>.ks and <gridname>.pw is available in secure folder before this is performed. Also note that the secondary host configuration will be reset
    configurePrimaryHost      Reconfigure registry port and adminrouter ports and external address, only possible to run on the current primary host
    updateHttpsCertificate    Create a new HTTPS certificate for this host with a specific address, requires <gridname>.ks and <gridname>.pw to be available in secure the folder
    add                       Add a new host
    remove                    Remove a host from the Grid
    installer                 Download a host installer


app
    permanentlyOffline        Set a specified application or all applications permanently offline
    propertiesXml             Print application properties as XML
    permanentlyOfflineState   Print application permanently offline state
    start                     Start application
    stop                      Stop application
    list                      List application versions, started and global state
    uninstall                 Uninstall an application
    info                      Print detailed application information
    scaleOut                  Deploy the application to additional host(s)
    scaleIn                   Remove this application from host(s)
    upgrade                   Upgrade an existing application
    online                    Set an application online
    offline                   Set an application offline
    listBindings              List all bindings for a given application
    install                   Install an application
    deploymentStatus          List deployment status for a given application


keystore
    pemFormat                 Convert Java keystore to PEM format


database
    info                      Print details about the Grid database
    config                    Update database url, user and password


keyValueStore
    load                      Loads a byte array property from KeyValueStore and writes it into a file
    save                      Saves a file as a byte array property in the KeyValueStore
    delete                    Delete the property for the given application and key
    listProperties            List all properties for given application and key
    listKeys                  List keys for given application
    deleteAll                 Deletes all stored data for the given application and key


persisteddata
    load                      Loads a byte array property from persisted data and stores it into a file
    save                      Saves a file as a byte array property into persisted data
    delete                    Delete the given property for the given application
    list                      List all properties for given application
    deleteAll                 Deletes all stored data for the given application


node
    log                       Print node log
    stop                      Stop a given node
    list                      Print a list of all running nodes in the Grid
    info                      Print details about a Grid node
    start                     Launch a binding on a specified host


repository
    list                      List gar files in the repository
    clean                     Remove all non deployed gar files of a specific application type from the repository, specifying -version allows to remove gar files matching a specific version

Each tool references a grid-launch-class in the Maven POM, e.g. change-db-password:
6

Wait, there’s more

It seems the old script client is still available, renamed:
5

Conclusion

That was a quick peek at the Grid command line interface (CLI). It is useful for system administrators to manage the Grid as an alternative to the graphical user interface (GUI). In a previous post I was looking for a command line to install Mashups (*.mashup) onto LifeCycle Manager (LCM). But this CLI does not seem to do that, it seems to only install Grid applications (*.gar).

That’s it.

M3 Ideas, now 250 subscribers.

How to call M3 API from the Grid application proxy

Here is how to call M3 API using the MI-WS application proxy of the Infor Grid.

This is useful if we want to benefit from what is already setup in the Grid and not have to deal with creating our own connection to the M3 API server with Java library, hostname, port number, userid, password, connection pool, etc.

Note: For details on what Grid application proxies are, refer to the previous post.

MI-WS application proxy

The MI-WS application is part of the M3 Business Engine Foundation. We will need foundation-client.jar to compile our classes:
1b

Step 1. Logon to the Grid

First, login to the Grid from your application and get a SessionId and optionally a GridPrincipal.

From a Grid application:

import com.lawson.grid.proxy.access.GridPrincipal;
import com.lawson.grid.proxy.access.SessionController;
import com.lawson.grid.proxy.access.SessionId;

// get session id
SessionId sid = ??? // PENDING
GridPrincipal principal = ??? // PENDING;

From a client application outside the Grid:

import com.lawson.grid.proxy.access.GridPrincipal;
import com.lawson.grid.proxy.access.SessionId;
import com.lawson.grid.proxy.access.SessionProvider;
import com.lawson.grid.proxy.access.SessionUtils;
import com.lawson.grid.proxy.ProxyException;

// logon and get session id
SessionUtils su = SessionUtils.getInstance(registry);
SessionProvider sp = su.getProvider(SessionProvider.TYPE_USER_PASSWORD);
SessionId sid;
try {
    sid = sp.logon(userid, password.toCharArray());
} catch (ProxyException e) {
    ...
}
GridPrincipal principal = su.getPrincipal(sid);

Step 2. Call the M3 API

Second, call the M3 API, for example CRS610MI.LstByNumber, and get the result:

import java.util.ArrayList;
import java.util.List;
import com.lawson.grid.proxy.ProxyClient;
import com.lawson.grid.proxy.ProxyException;
import com.lawson.miws.api.data.MIParameters;
import com.lawson.miws.api.data.MIParameters.ColumnList;
import com.lawson.miws.api.data.MIRecord;
import com.lawson.miws.api.data.MIResult;
import com.lawson.miws.api.MITransactionException;
import com.lawson.miws.proxy.MIAccessProxy;

// get the proxy
MIAccessProxy proxy = (MIAccessProxy)registry.getProxy(MIAccessProxy.class);

// login to M3
ProxyClient.setSessionId(proxy, sid);

// prepare the parameters
MIParameters paramMIParameters = new MIParameters();
paramMIParameters.setProgram("CRS610MI");
paramMIParameters.setTransaction("LstByNumber");
paramMIParameters.setMaxReturnedRecords(10);

// set the return columns
ColumnList returnColumns = new ColumnList();
List<String> returnColumnNames = new ArrayList<String>();
returnColumnNames.add("CONO");
returnColumnNames.add("CUNO");
returnColumnNames.add("CUNM");
returnColumns.setReturnColumnNames(returnColumnNames);
paramMIParameters.setReturnColumns(returnColumns);

// execute
MIResult result;
try {
	result = proxy.execute(paramMIParameters);
} catch (MITransactionException e) {
	...
} catch (ProxyException e) {
	...
}

// show the result
List<MIRecord> records = result.getResult();
for (MIRecord record: records) {
	record.toString();
}

Note: When I use ColumnList it throws java.io.NotSerializableException: com.lawson.miws.api.data.MIParameters$ColumnList. It appears to be a bug in that the ColumnList class is missing implements Serializable. I reported it in Infor Xtreme incident 8629267.

That’s it. Please let me know what you think in the comments below.

Application proxies in the Infor Grid

Here is how to create and invoke application proxies of the Infor Grid.

What is an application proxy?

An application proxy is an interface for a Grid application that other applications of the Grid or client applications outside the Grid can invoke; it is inter-process communication in a distributed environment.

Example. The proxy for Grid application C is invoked from grid application A of a different host, from grid application D of the same host, and from client application K outside the Grid.

How to create an application proxy?

To create a proxy for a Grid application:

package net.company.your;

import com.lawson.grid.node.application.ApplicationEntryPoint;
import com.lawson.grid.node.application.ModuleContext;
import com.lawson.grid.proxy.ProxyException;
import com.lawson.grid.proxy.ProxyServer;

public interface HelloWorldProxy {
    public String hello(String name) throws ProxyException;
}

public class HelloWorld implements HelloWorldProxy {
    public String hello(String name) {
        return "Hello, " + name;
    }
}

public class HelloWorldApp implements ApplicationEntryPoint {
    public boolean startModule(ModuleContext context) {
        ProxyServer.registerProxy(HelloWorldProxy.class, new HelloWorld());
        return true;
    }
    public void stopModule() {
    }
}

Note: For details on how to create a Grid application, refer to the previous post on the subject.

The proxy is now ready to be invoked:
8

How to call the proxy from a Grid application?

To invoke the proxy from a Grid application:

The short source code is:

Registry registry = Node.getRegistry();
HelloWorldProxy proxy = registry.getProxy(HelloWorldProxy.class);
String result = proxy.hello("Thibaud");

The long source code is:

package net.company2.your;

import com.lawson.grid.node.application.ApplicationEntryPoint;
import com.lawson.grid.node.application.ModuleContext;
import com.lawson.grid.node.Node;
import com.lawson.grid.proxy.ProxyException;
import com.lawson.grid.registry.Registry;
import net.company.your.HelloWorldProxy;

public class HelloWorldApp2 implements ApplicationEntryPoint {
	public boolean startModule(ModuleContext context) {
		// connect
		Registry registry = Node.getRegistry();
		if (!registry.isConnected()) {
			System.err.println("failed to connect");
			return false;
		} else {
			System.out.println("connected to " + registry.getGridName());
		}
		// get the proxy
		HelloWorldProxy proxy = (HelloWorldProxy)registry.getProxy(HelloWorldProxy.class);
		if (proxy == null) {
			System.err.println("failed to get proxy...exiting");
			return false;
		} else {
			System.out.println("got proxy");
		}
		// invoke the proxy
		try {
			String result = proxy.hello("Thibaud");
			System.out.println(result);
		} catch (ProxyException e) {
			System.err.println(e.getMessage());
			e.printStackTrace();
			return false;
		}
		return true;
	}

	public void stopModule() {
	}

}

How to call the proxy from a client application?

To invoke the application proxy from a client application outside the Grid:

First, choose a Grid registry’s hostname and port number (a registry is a special node that keeps track of all nodes in a Grid):

Then, connect to the Grid, get the proxy and invoke its methods.

The short source code is:

ClientRegistry registry = new ClientRegistry("Test");
registry.connect(hostname, portNumber);
HelloWorldProxy proxy = registry.getProxy(HelloWorldProxy.class);
String result = proxy.hello("Thibaud");

The long source code is:

import com.lawson.grid.proxy.ProxyConnectionFailedException;
import com.lawson.grid.proxy.ProxyException;
import com.lawson.grid.proxy.access.GridPrincipal;
import com.lawson.grid.proxy.access.SessionId;
import com.lawson.grid.proxy.access.SessionProvider;
import com.lawson.grid.proxy.access.SessionUtils;
import com.lawson.grid.registry.ClientRegistry;
import net.company.your.HelloWorldProxy;

public class Test {
    public static void main(String[] args) {

        // connect
        ClientRegistry registry = new ClientRegistry("Test");
        try {
            registry.connect("host1623", 22102);
        } catch (ProxyConnectionFailedException e) {
            System.err.println(e.getMessage());
            e.printStackTrace();
            return;
        }
        if (!registry.isConnected()) {
            System.err.println("failed to connect");
            return;
        } else {
            System.out.println("connected to " + registry.getGridName());
        }

        // get the proxy
        HelloWorldProxy proxy = (HelloWorldProxy)registry.getProxy(HelloWorldProxy.class);
        if (proxy == null) {
            System.err.println("failed to get proxy...exiting");
            return;
        } else {
            System.out.println("got proxy");
        }

        // invoke the proxy
        try {
            String result = proxy.hello("Thibaud");
            System.out.println(result);
        } catch (ProxyException e) {
            System.err.println(e.getMessage());
            e.printStackTrace();
            return;
        }

        // disconnect
        registry.close();
        if (!registry.isClosed()) {
            System.err.println("failed to close");
        } else {
            System.out.println("registry closed");
        }
    }

}

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

Java Debugging an Infor Grid application at runtime

Here is an illustrated example of how to use the Java Debugger in Eclipse to debug an existing Infor Grid application at runtime for which we don’t have the source code; for example I will debug the M3 Web Services (MWS) server, line by line, while I make a SOAP request.

For that, I will use the technique I learned in my previous post, Hacking Infor Grid application development (part 6), where I learned how to use the Java Debugger for my own Grid application for which I had the source code, and I will extend that technique to an existing Infor Grid application for which I don’t have the source code but for which I have the JAR files.

I had to learn this technique in order to troubleshoot a timeout issue we are currently having in MWS when making a SOAP request. This helped me pinpoint the modules, packages, classes, methods and lines of code that were at cause and report the results to Infor Support so they can report them to Infor Product Development for help. To that effect, please join the campaign and sign the petition asking Infor Product Development to make their source code available so we can better troubleshoot issues and learn. Also, this week is Open Access Week and it’s a great opportunity to ask Infor to make more of their documentation available.

Logging and DEBUG level

In order to narrow down the search space for the issue I was troubleshooting, I increased the log level of the application’s node to DEBUG level. In my case, I selected ALL modules: SYSTEM and MWS. I had also selected TRACE but it didn’t produce any log.
2

I then opened the log file and found interesting information about the bottleneck issue, that helps me narrow down which modules, packages, and classes to debug. In my case they are: MWS com.lawson.webservices.m3.program.M3Session, and SYSTEM DistributedLock:
2_

Remember to revert the log level changes after you’re done to avoid clogging the disk space with increasing log.

Setup the Java Project in Eclipse

Let’s setup a new Java Project in Eclipse with the JAR files of the Infor Grid application (e.g. MWS in my case). For that:

  1. Create a new Java Project in Eclipse, give it a name, and select Add External JARs:
    1.6_
  2. Select the JAR files of the Infor Grid application in LifeCycle Manager (e.g. MWS\lib\*.jar in my case):
    1.7
  3. Optionally, you can also add the JAR files of the Grid runtime (grid-core.x.y.z.jar, etc.).
  4. Open the desired Java class (double-click), Eclipse will open that class in the Class File Editor, and for now it will say “Source not found”, we will fix that later:
    1.11

Start debugging

Now let’s debug the application, even without the source code:

  1. Toggle the breakpoints at the desired methods:
    2.0
  2. Prepare the Infor Grid application for debugging as detailed in my previous post:3
  3. Start debugging in Eclipse:
    2.1_
  4. Make the application go through your breakpoint (in my case I make a SOAP request, the application will call M3Session.logon, and Eclipse will suspend at my breakpoint).
  5. At this point, even without the source code, you can use the debugger as usual, to execute line by line, inspect variables, evaluate expressions, etc.; each class will show “Source not found” for now:
    2.6

Add the Java decompiler

Now let’s use a Java decompiler for Eclipse to recover the source code (not the original source code though) and attach it to the classes.

  1. I installed the JD-Eclipse plugin by Emmanuel Dupuy:
    0
  2. JD-Eclipse will change the file association in Eclipse to open class files that don’t have source, decompile them, and attach the decompiled source back to the class:
    1.9
  3. Also, JD-Eclipse will realign the line numbers of the recovered source code so they match with the debug information of the class, otherwise the debugger will show the wrong lines of source code and it will be very confusing:
    1.10
  4. Now open a class file (double-click) and JD-Eclipse will show the recovered source code with line numbers realigned:
    2.1
  5. Now debug again with the breakpoints. This time the debugger will show the source code. This is the ideal scenario for debugging line by line:
    3.3

Problem

I realized that as I’m debugging, JD-Eclipse will not decompile classes which it hasn’t previously decompiled. So if the debugger steps through a class that hasn’t been previously decompiled, the debugger will show the usual “Source not found”.

The workaround is to open all the classes we need the source code for, prior to debugging, one by one, so JD-Eclipse can decompile them, and attach the source code back to the class, and then we can try debugging again.

UPDATE 2014-10-29: That is not true anymore, and I found the solution: we have to change the default file association for the *.class files in order to use Java Decompiler’s Class File Editor, and we must do so every time we start Eclipse because Eclipse will revert to the default Class File Viewer when we exit Eclipse:
0

Future work: batch decompilation with realignment

I tried the plugin Realignment for JD-Eclipse by Alex Kosinsky that does batch decompilation, but it doesn’t realign the line numbers, so that doesn’t work. I also tried the plugin JDEclipse-Realign by Martin “Mchr3k” Robertson, it does decompile, and it does realign the line numbers, but I couldn’t get the source code to show while debugging, even after having previously decompiled the class, and I don’t know if it does batch decompilation.

UPDATE 2014-10-29: Also, I tried Java Decompiler’s > Save All Sources, but it doesn’t realign the line numbers, even though JD-Core’s change log for version 0.7.0 says “Added an algorithm to realign the decompiled source code to the original line numbers”:
y

That’s future work to solve.

Summary

That was an illustrative guide of how to do Java debugging on an Infor Grid application, at runtime, without having the source code of the application, having just the JAR files from LifeCycle Manager. This technique helped my troubleshoot an issue with MWS. I hope it will help you too.

That’s it! If you learned something, please click Like below, click Follow to receive notifications about new blog posts, share around you, sign the petition, and write the next blog post with me, you are great. Thank you.

Hacking Infor Grid application development (part 6)

Here is the sixth article in the series on Hacking Infor Grid application development, and today I will illustrate how to debug the Grid application at runtime. This is useful for development and troubleshooting purposes. For that, I will use the standard Java Platform Debugger Architecture (JPDA) that is included in Java.

I will do the illustration gradually: I will start by debugging a simple HelloWorrrld class from the command line, then I will debug it remotely, then I will debug it from Eclipse, and finally I will debug the Infor Grid application.

Remember the intention is to hack ethically, to learn and to push the boundaries of what is possible. Also, remember to join the campaign and sign the petition asking Infor Product Development to make their source code available so we can do better work. Also, next week is Open Access Week, and it is a great time to ask Infor Product Development to make more of their documentation available.

Debugging from the command line

Here is a quick illustration of how to use jdb, the Java Debugger, to debug a simple HelloWorrrld class from the command line.

Here is the simple HelloWorrrld class:

public class HelloWorrrld {
    public static void main(String[] args) {
        int i = 11;
        int j = 13;
        int k = i * j;
    }
}

Suppose we want to debug the class at runtime and set a breakpoint at line 6 to see the value of variable k.

For that, we compile the class with the -g command line parameter to generate all debugging information, including local variables; by default, only line number and source file information is generated:

javac -g HelloWorrrld.java

Then, we have jdb launch a new Java Virtual Machine (VM) with the main class of the application to be debugged by substituting the command jdb for java in the command line:

jdb HelloWorrrld

Then, we set the breakpoint at line 6:

stop in HelloWorrrld:6

We could have also stopped execution at the beginning of the main method instead of at a specific line number:

stop in HelloWorrrld.main

Then, we start the execution of the debugged application:

run

The debugger will execute up to the breakpoint, at which point we can display the value of k:

print k

It will display k = 143.

Then, we advance execution to the next line and so on until the end:

step

Here is a screenshot of the steps:
1

Debugging remotely

Now I will illustrate how to debug the same class remotely by attaching jdb to a Java VM that is already running.

First, we find a port number that is available on the host where we will run the class, for example I use netstat, and for example I will try port number 1234:

netstat -an | findstr :1234

If netstat returns LISTENING then that port number is already in use and unavailable; otherwise you can use it.

Then, we start the Java VM (the debuggee) with the following options to load in-process debugging libraries and specify the kind of connection to be made and allow jdb to connect to it at a later time:

java -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:1234,server=y,suspend=y HelloWorrrld

-agentlib:jdwp loads the native agent library for the Java Debug Wire Protocol (JDWP).
suspend=y makes the JVM wait on startup for a debugger to attach before running.
transport=dt_socket sets the transport specification to socket transport using a single stream TCP/IP connection between the debugger application and the target VM.
address=1234 is the port number to which the debugger should attach to the JVM. This number should normally be above 1024. Verify that it is available.
server=y indicates that the JVM is accepting connections from debuggers.

Then, on the remote host (the debugger), we start jdb and attach it to the remote Java VM (the debuggee), and we specify the path of the source files:

jdb -sourcepath ./src/ -connect com.sun.jdi.SocketAttach:hostname=host,port=1234

Here is a screenshot of the steps:
2.1
2.2

Debugging from Eclipse

Now I will illustrate how to debug the same class remotely from Eclipse.

First, create a new Java Project in Eclipse, and import the source code of the Java class, and set the desired breakpoints:
3.1

Then, select Run > Debug Configurations.

Then, click New launch configuration, select the Project, Connection Type Standard (Socket Attach), Host, Port, Source, and click Debug:
3.3

Open the Debug perspective, and debug as usual with threads, stepping, immediate console, local variables, etc.:
3.4

Debugging Infor Grid application

Finally, I will illustrate how to debug our Infor Grid application remotely at runtime. We will need the source code of that Grid application, for example I will use the source code I wrote in part 4 of this series.

As a reminder, an Infor Grid application runs in a Grid Node, and a Grid Node is a Java VM. We will set the JDWP debugging options on that Java VM (debuggee). For that, we must change the Java command line options. Fortunately, there is a convenient node property Debug Port in the Node properties that takes care of it.

Select Infor ION Grid Management Pages > Applications > HelloWorld > Configuration:
4.0

Select Edit Properties:
4.1

Select Debug Port:
4.2

Set the application’s debug port number (specify a zero port to assign any free ephemeral port), and save the configuration changes:
4.3

Then, go to the Node Properties (Node > Advanced > Properties), ensure the debugging options were set, and take note of the port number:
4.4

Now, stop the Node, and wait until it restarts automatically, for the Java VM debug options to take effect:
4.4

Now, open the Java project with the source code in Eclipse, and set the desired breakpoints:
5.1

When you compiled the source code, verify that the Compiler Preferences included the debugging options:
5.2

Now, go to the Debug Configuration, set the port number, and click Debug:
5.2

The debugger will attach to the remote Java VM, and will show the current threads:
5.3

Now, reload the Module so we reach our breakpoints:
5.3

Finally, the debugger will reach our breakpoints, and now we can introspect the variables, step through the code, and so on:
5.4

Conclusion

That was an illustration of how to remotely debug an Infor Grid application at runtime with the Java Debugger and Eclipse. This is useful for learning, development and troubleshooting.

Note that when the Grid application threads are suspended the Node will show as status NOK_STALE, and unexpected consequences might happen in the Grid, such as timeouts or attempts to recover.

Also, remember this technique is not official and untested as we are still learning how to deal with Infor Grid applications.

 

That’s it! Please thumbs up if you like this, share around you, follow this blog, sign the petition, and author the next article.

How to run a Google Glass app in Infor Grid

Today I will detail the steps to run a Google Glass app in Infor Grid. This is part of my project to have M3 Picking Lists in Google Glass.
glass

For that, I will develop a very simple Glassware using the Google Mirror API Java Quick Start Project, and I will use the technique I learned in Hacking Infor Grid application development. The integration will be bi-directional: the Grid app will communicate to the Glass API on Google’s servers to insert cards in the timeline, and conversely when the user replies to a timeline card Google’s servers will send notifications to the Grid app provided it is located at a routable address with a valid SSL certificate.

This is a great demo of the integration capabilities of the Infor Grid. I worked a little bit here and there on evenings and week-ends over several months, and I distilled the resulting steps here and in a 15mn video so you can play along. You will need a pair of Google Glass.

STEP 1: Setup Eclipse with Maven

I will start with the instructions for the Google Mirror API Java Quick Start Project:
step1a

For the Prerequisites I need Java 1.6 and Apache Maven for the build process. I will download Eclipse IDE for Java Developers that has the Maven plugin integrated:step1

STEP 2: Setup the Glass Mirror API Java Quick Start Project

Then, I will download the Glass Mirror API Java Quick Start Project from the GitHub repository:
step2.1

Then, I will import it in Eclipse as an Existing Maven Project with the pom.xml:
step2.2

I will import the Infor Grid library grid-core.jar:
step2.3

Then, I will replace some of the source code to adapt it to the Infor Grid, using Eclipse File Search and Replace:
step2.4

I will replace the code for the Logger in all files (from/to):

import java.util.logging.Logger;
import com.lawson.grid.util.logging.GridLogger;
Logger LOG = Logger.getLogger
GridLogger LOG = GridLogger.getLogger
LOG.severe
LOG.error
LOG.fine
LOG.info
LOG.warning
LOG.warn

Then, I will add the context path to the URLs of all files (from/to):

href="/
href="
src="/static
src="static
url.setRawPath(
url.setRawPath(req.getContextPath() +
RegEx:
(getRequestURI\(\).*\()"/
$1httpRequest.getContextPath() + "/

For the subscription to notifications I will replace the callback URL in NewUserBootstrapper.java by a routable FQDN or IP address with a valid SSL certificate to handle the notification:

Subscription subscription = MirrorClient.insertSubscription(credential, WebUtil.buildUrl(req, "/notify").replace("m3app-2013.company.net", "11.22.33.44"), userId, "timeline");

Then, I will replace the code in NotifyServlet.java that processes the notification from the HTTP request body because apparently notificationReader.ready() always returns false in the Infor Grid and that throws IllegalArgumentException: no JSON input found. Here is the new code:

int lines = 0;
String line;
while ((line = notificationReader.readLine()) != null) {
	notificationString += line;
	...
}
notificationReader.close();

Then, I will setup the Project in the Google Developers Console with the Google Mirror API, the client ID and client secret credentials for OAuth 2.0, and the Consent screen:
step2.5a step2.5b step2.5c

Then, I will paste the client ID and secret in the oauth.properties of the project:
step2.6

Then, I will create and run a new Maven Build Configuration using goal war:war:
step2.7a

That will create a WAR file that I will use to deploy as a web application in my Grid application:
step2.7b

STEP 3: Setup the Infor Grid application

Then, create and install an Infor Grid application GoogleGlass based on the HelloWorld app:
step3.2b_i step3.2b_ii step3.2b_iii step3.2b_iv

STEP 4: Test

Then, launch the app:
step4.1a

Authenticate to the Google account associated with Glass, and click Accept to grant app permissions:
step4.1b

Use the app, insert cards in the timeline:
step4.2step4.5

You can also tap Glass to reply to a timecard:
step4.8

And the Grid app will receive the notification with a JSON string:
step4.12

Resulting video

Here is the video with hours of work distilled in 15mn (I recommend watching in full screen, HD, and 2x speed):

STEP 5: Summary

That was how to run a Google Glass app in Infor Grid. The main steps are:

  1. Setup Eclipse with Maven
  2. Setup the Glass Mirror API Quick Start Java project
  3. Setup the Infor Grid application
  4. Test

The integration is bi-directional: the Grid app adds cards to the Glass timeline, and when the user takes action on a card Google’s servers send a JSON notification to the Grid app.

The result is great to demo the integration capabilities of the Infor Grid, and it will be useful for my project to show M3 picking lists in Glass.

Future work

In future work, I will use the bi-directional communication for pickers in a warehouse to tap Glass to confirm picking lists, have Google’s servers send the JSON notification to the Grid app, and have the Grid app call an M3 API MHS850MI AddCOPick and AddCfmPickList to confirm picking.

That’s it. If you liked this, please give it a thumbs up, leave your comments, share around you, and contribute back by writing your own ideas. Thank you.

Hacking Infor Grid application development (part 5)

Here is the fifth part in the series Hacking Infor Grid application development. This time I will show you how to deploy a web application archive, WAR file. It is trivial and quick as the Infor Grid automatically deploys it.

Create the archive

Simply ZIP the contents of the web application into a WAR file:
1

File & folder structure

Move the WAR file into the webapps folder of the Grid application, and delete the rest of the webapps content (WEB-INF, JSP, etc.) so that there is only the WAR file left:

[...]
|
\---webapps
        HelloWorld.war

Deploy

Create the Grid application archive GAR and deploy it in the Grid as usual. The Infor Grid will automatically deploy the WAR file under webapps:
2

GitHub

I updated the GitHub repository with a Tag for part5.

Petition

Remember to join the campaign in asking Infor Product Development to release their Grid Development Tools and tutorials and to make their source code available so we can learn how to make good Grid applications.

Meanwhile, remember this is a hack with the intention to learn and progress.

Hacking Infor Grid application development (part 4)

Continuing the series Hacking Infor Grid application development, today I will show: how to decompile Java applications, how to write to the Grid application log files, how to read Grid application properties, how to secure the Java web application, and how to start playing with the HelloWorld app on GitHub. Remember this is currently a hack, it is at your own risk, there are limitations as discussed on part 1, and please join the campaign and sign the petition to Infor Product Development for making their source code available. The intention is to learn the internals of the applications we use every day, push their limits, and develop great software.

Decompile Java applications

First of all, check your licenses to determine if you are legally allowed to decompile the Java application you have in mind.

For this series I decompiled the Infor Grid core grid-core-1.11.27.jar to understand how to write to the Grid application log files and how to get Grid application properties. I also often decompile applications like M3_UI_Adapter (MNE), Event Hub, M3 Web Services, and Infor Process Automation to understand how they work, make enhancements, troubleshoot, and find bugs. All the JAR files are located in LifeCycle Manager at E:\Infor\LifeCycle\<host>\grid\<grid>\grids\<grid>\applications\. It is already common practice to decompile Infor Smart Office with RedGate Reflector (C#) to push the limits of Smart Office scripts and Smart Office SDK applications. I wish the source code of all applications were available.

For decompiling Java applications, I use the great Java Decompiler and its GUI. I simply drag and drop a JAR file and it automatically decompiles all files recursively:
1

Write to Grid application log files

To write to the Grid application log files, get the logger and log at the information, warning, error, debug, or trace levels:

import com.lawson.grid.util.logging.GridLogger;
[...]
private static final GridLogger log = GridLogger.getLogger(HelloWorld.class);
[...]
log.info("Yay, I'm writing to the log file :)");
log.warn("Hey, it feels hot in here!");
log.error("Ouch, that hurt :(");
log.debug("Useful data 48656C6C6F576F726C64");
log.trace("Wooohooo 01001000 01100101 01101100 01101100 01101111 00100000 01010111 01101111 01110010 01101100 01100100 00100001");

Set the log levels accordingly:

Check the result in the Grid application log file:
3

The log files are also directly in the log folder for use with a tail program or so:
5

Read Grid application properties

Declare Grid application properties in the application deployment descriptor GRID-INF\application.xml:

<property name="message">Hello Wrrrld!</property>

6

Then, read the properties with:

import com.lawson.grid.node.properties.GridProperties;
[...]
GridProperties p = ModuleContext.getCurrent().getProperties();
String message = p.getProperty("message");
log.info("The message is: " + message);

Re-deploy the application and check the application properties:
7

And here is the result:
4

Secure Java web application

To secure the Java web application in WEB-INF\web.xml:

<security-constraint>
    <web-resource-collection>
        <url-pattern>/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>grid-user</role-name>
    </auth-constraint>
</security-constraint>

Set the User and Role Mappings in the Grid Configuration Manager accordingly:8

Restart the application.

The Grid will now challenge for authentication:
9

HelloWorld app on GitHub

I published the HelloWorld Grid application source code on my GitHub repository so you can play along, learn, and contribute your code, and I added tags – part1, part2, part3, and part4 – so you can download the source code that corresponds to the desired part in this series:
10

Summary

That was an illustration of how to decompile Java applications, how to write to the Grid application log files, read Grid application properties, secure the Java web application, and start playing with the HelloWorld app on GitHub. Remember to play responsibly, know the limitations, sign the petition, and contribute your findings to the community.

That’s it! If you liked this, please click Like, write your comments, click Follow to subscribe to the blog, share around you, and author your own ideas. Thank you.