JVM Memory Options

/2019-08-28/

Q: In the /bin/q2 script of jPOS, -Xmx1G is set. Will it be ok to set it to -Xmx16G or higher for a Windows Server of 32RAM? Advice and directions will be highly appreciated. Thanks great community.

A: It’s totally OK to increase, but if you increase it, watch out your GC configuration. This blog post is old, and the switches have changed in more recent JVMs, but it’s useful to shed some light on the kind of things that you want to watch out in order to avoid and monitor stop-the-world GC cycles.

QServer as client

/2019-08-29/

Q: I need to send a message out the door to the TCP/Client that is connected to this server.

A:

Despite the fact that QServer is a server from a TCP/IP standpoint, it can be a client at the application level.

sequenceDiagram
    participant Acquirer
    participant QServer
    Acquirer->>QServer: Acquirer initiates TCP/IP connection
    QServer->>Acquirer: ISO-8583 request
    Acquirer->>QServer: sends ISO-8583 response

This can be easily achieved by adding an <in> and <out> queue to the QServer configuration, in the same way you’d use it in a ChannelAdaptor, i.e.:

<server class="org.jpos.q2.iso.QServer" logger="Q2" name="xml-server">
 <attr name="port" type="java.lang.Integer">8000</attr>
 <channel class="org.jpos.iso.channel.XMLChannel"
     logger="Q2" packager="org.jpos.iso.packager.XMLPackager">
    <property name="timeout" value="3600000" />
 </channel>
 <in>remote-send</in>
 <out>remote-receive</out>
</server>

Those in/out queues can be connected to a QMUX, i.e.:

<mux class="org.jpos.q2.iso.QMUX" logger="Q2" name="isobridge">
 <in>remote-receive</in>
 <out>remote-send</out>
</mux>

(Quick reminder: in/out queues are seen from the component’s perspective, when you want to “send” something to say a ChannelAdapter, you push the ISOMsg to its in queue. Whatever the ChannelAdaptor receives on its TCP/IP ISO-8583 side, it will place it in its out queue - same goes for the QServer, and QMUX).

If in addition to sending messages to the remote TCP/IP client you want to receive messages coming from it, you can use an ISORequestListener. The typical use has the ISORequestListeners in the QServer, but in this configuration, you need to move them to the QMUX.

So the QServer would just use the in/out queues to communicate with other components, and the QMUX would take care of handling outgoing as well as incoming messages.

graph LR
Client --> QServer
QServer --> QMUX
QMUX --> QServer
QMUX --> ISORequestListener
ISORequestListener --> QServer

Now, this is important: when client code uses the QMUX to send out a request to the remote host, by placing it in the QServer’s in queue, QServer will send it out through the last connected ISOChannel (there’s a handy QServer.getLastConnectedISOChannel() method for that).

When QServer listens on a given port, many clients can connect:

graph LR
   Client1 --> QServer
   Client2 --> QServer
   Client3 --> QServer
   QServer --> QMUX

If the connections come in order (client 1, client 2, client 3), if you call mux.request, QServer will send it through the socket connection to client 3.

If this is your intended use-case, you can stop here. But there are some other use cases, for example, when you want to initiate a network management message upon connection:

sequenceDiagram
    participant Acquirer
    participant QServer
    Acquirer->>QServer: Acquirer initiates TCP/IP connection
    QServer->>Acquirer: 0800
    Acquirer->>QServer: 0810
    Acquirer->>QServer: 0200
    QServer->>Acquirer: 0210


    

In this situation, one typically want to send a 0800 message on ALL connections to QServer.

In addition to the request-listener that one can add to the QServer configuration (or QMUX), there’s also a connection-listener that gets called once QServer accepts a connection and can be used to trigger a 0800, or to register the new channel with a custom connection manager.

The configuration would look like this:

<server class="org.jpos.q2.iso.QServer" logger="Q2" name="xml-server-8000" realm="xml-server-8000">
    <attr name="port" type="java.lang.Integer">8000</attr>
    <channel class="org.jpos.iso.channel.XMLChannel"
             packager="org.jpos.iso.packager.XMLPackager"
             type="server"
             logger="Q2"
             realm="xml-server-8000">
        <property name="timeout" value="180000"/>
    </channel>
    <request-listener class="org.jpos.iso.IncomingListener" logger="Q2" realm="incoming-request-listener">
        <property name="queue"  value="TXNMGR" />
        <property name="ctx.DESTINATION"  value="jPOS-AUTORESPONDER" />
    </request-listener>
    <connection-listener class="com.my.custom.Logon" />
</server>

The Logon class can look like this:

import org.jpos.iso.*;
import org.jpos.util.Log;
import java.util.Date;
import java.util.EventObject;
import java.util.concurrent.atomic.AtomicLong;

public class Logon extends Log implements ISOServerEventListener {
    AtomicLong stan = new AtomicLong();
    @Override
    public void handleISOServerEvent(EventObject event) {
        if (event instanceof ISOServerAcceptEvent) {
            ISOServer server = (ISOServer) event.getSource();
            ISOChannel channel = (ISOChannel) event.getISOChannel();
            try {
                channel.send(createMsg("001"));
            } catch (Exception e) {
                error(e);
            }
        }
    }
    private ISOMsg createMsg (String msgType) throws ISOException {
        ISOMsg m = new ISOMsg ("0800");
        m.set (7, ISODate.getDateTime (new Date()));
        m.set (11, ISOUtil.zeropad (Long.toString (stan.incrementAndGet()), 6));
        m.set (70, msgType);
        return m;
    }
}

This simple example blindly sends a 0800 message on the newly created channel, but users may want to register the channel internally so that network management messages can be regularly sent.

WARNING: If you keep a reference to channel in your ISOServerEventListener callback, we suggest to use a WeakReference so that the channel doesn’t create a memory leak once disconnected.

In addition to registering channels at connection time using a connection listener, QServer has a handy getISOChannelNames() method that returns a space-separated list of channel names. That list can be split and iterated using the getISOChannel(String) method to get a reference to a connected channel, and that channel can be used to regularly fire network management messages.

Upgrade old jPOS project

/2019-09-09/

Q: I have a project with jPOS that is build with ant, It's a very antique project and I want to update it to a newer version using gradle. Is there a standard procedure or a tutorial to follow in this case?

A: You may want to take a look at the jPOS-template. We use it as a reference whenever we have to create a new jPOS project. You may also want to check jPOS tutorials - in particular, the second one guides you through the creation of a simple jPOS project.

ProtectedLogListener

/2019-09-18/

Q: How can I remove sensitive information on the logs, and what is the proper way to handle logging process in jPOS?

A:

You can add the ProtectedLogListener to your logger configuration. If you’re using the default Q2 logger, configured in deploy/00_logger.xml, you could add something like this:

<logger name="Q2" class="org.jpos.q2.qbean.LoggerAdaptor">
  <log-listener class="org.jpos.util.ProtectedLogListener">
    <property name="protect" value="2 14 34 35 45 111.2 111.14 111.35 111.45" />
    <property name="wipe"    value="52 55" />
  </log-listener>
  <log-listener class="org.jpos.util.SimpleLogListener" />
  <log-listener class="org.jpos.util.DailyLogListener">
    <property name="prefix" value="log/q2" />
    <property name="date-format" value="-yyyy-MM-dd-HH"/>
  </log-listener>
</logger>

By adding the ProtectedLogListener before other log listeners, the fields configured in the protect will be properly masked, and those configured as wipe will get wiped.

The message would look something like this:

    <isomsg direction="incoming">
      <field id="0" value="0200"/>
      <field id="2" value="411111______1111"/>
      <field id="3" value="003000"/>
      <field id="4" value="000000000100"/>
      <field id="7" value="0903190443"/>
      <field id="11" value="253632"/>
      <field id="12" value="044359"/>
      <field id="13" value="0903"/>
      <field id="14" value="____"/>
      <field id="18" value="5533"/>
      <field id="19" value="036"/>
      <field id="22" value="012"/>
      <field id="25" value="00"/>
      <field id="41" value="00000001"/>
      <field id="42" value="000000000000001"/>
    </isomsg>

Warning related to ISODate pattern YYYY

_/2019-12-31 taken from jPOS' Slack/

_from @Gandhi

Hi there!

We found ourselves in a weird situation with ISODate.formatDate.

After 2019-12-29 (this weekend), some problems with ICC validation rised from nothing (no changes made in the prod environment).

It happens that dates formatted with pattern YYMMdd were presented as 201229.

Don't know if you were aware of that kind of stuff or if it's documented somewhere, but it would easily corrected using another pattern. (all code comments uses "YY" as example)

It would help too, if ISODate has in it's comments and docs what patterns should be better to be used. I know that's more a missuse case, but if our mistakes can help someone to prevent theirs... it's a good thing right?Here's a code as example/demonstration:

import java.util.Date;
import org.jpos.iso.ISODate;

public class TesteISODate {	
  public static void main(String[] args) {
	  String[] ptt = {"YYMMdd", "yyMMdd"};
	  String[] strdate = {"20191228000000", "20191229000000"};		
    for (String dtStr : strdate) {
	    for (String pttStr : ptt) {
	  			System.out.println(TesteISODate.getOutStr(pttStr, dtStr));
  		}
  	}	
  }	
                           
  private static String getOutStr(String ptt, String strdate) {
		Date dtin = ISODate.parseISODate(strdate);
    return "Using pattern: " + ptt + " and Date for " + strdate + ": "
      + ISODate.formatDate(dtin, ptt);
	}
}

Then Relevant tweet provided by @Soares

PSA: THIS IS THE SEASON TO CHECK YOUR FORMATTERS, PEOPLE TIL that Java's DateTimeFormatter pattern "YYYY" gives you the week-based-year, (by default, ISO-8601 standard) the year of the Thursday of that week.12/29/2019 formats to 2019 12/30/2019 formats to 2020

QBean File Names

00_logger.xml is the only special file that gets read ahead of anything else; then you can use whatever numbering scheme you want. We tend to use:

  • 10_* for channels
  • 20_* for MUXes
  • 30_* for TM
  • 50_* for Servers
  • 90_* for scripts
  • 99_sysmon.xml for the System Monitor

But that’s totally arbitrary.

Logon Manager

Many hosts require a sign-on network management message once a connection is established, regular echo messages during the lifetime of the connection, and a sign-off message once the connection is going down.

You can go low level and implement it as part of your channel implementation. Alternative, you can adopt the jPOS way of using a more decoupled Space based approach.

Here is how such approach would look like:

When a ChannelAdaptor connects to a remote host, it places a ready-indicator in the Space. That's the same ready-indicator used by the MUX in order to tell if the channel is connected or not. So if you have a channel called foo, once connected, it will place an object (actually an instance of java.util.Date, but don't rely on it as it could be something else in the future) called foo.ready in the space.

That means that you can have a service waiting for foo.ready to be present, and firing a 0800 message right after.

The configuration would look like this (i.e. deploy/30_logonmanager.xml)

<logonmanager class="org.jpos.jpts.LogonManager" logger="Q2">
    <property name="persistent.space"  value="je:logonmgr" />
    <property name="timeout"           value="15000" />
    <property name="echo.interval"     value="60000" />
    <property name="mux"               value="mymux" />
</logonmanager>
package org.jpos.jpts;

import java.util.Date;
import java.io.OutputStreamWriter;
import java.io.ByteArrayOutputStream;

import org.jdom2.Element;
import org.jdom2.output.XMLOutputter;
import org.jpos.q2.QBeanSupport;
import org.jpos.core.Configuration;
import org.jpos.q2.iso.QMUX;
import org.jpos.space.Space;
import org.jpos.space.SpaceUtil;
import org.jpos.space.SpaceFactory;
import org.jpos.core.ConfigurationException;
import org.jpos.iso.ISOUtil;
import org.jpos.iso.ISOMsg;
import org.jpos.iso.packager.XMLPackager;
import org.jpos.iso.MUX;
import org.jpos.iso.ISODate;
import org.jpos.iso.ISOException;
import org.jpos.util.NameRegistrar;

@SuppressWarnings("unused unchecked")
public class LogonManager extends QBeanSupport {
    private Space sp;
    private Space psp;
    private long timeout;
    private long echoInterval;
    private long logonInterval;
    private long initialDelay;
    private ISOMsg logonMsg;
    private ISOMsg logoffMsg;
    private ISOMsg echoMsg;

    private static final String TRACE = "JC_TRACE";
    private static final String LOGON = "JC_LOGON.";
    private static final String ECHO  = "JC_ECHO.";

    public void initService () throws ConfigurationException {

        Configuration cfg = getConfiguration();
        sp = SpaceFactory.getSpace(cfg.get("space", ""));
        psp = SpaceFactory.getSpace(cfg.get("persistent-space", ""));
        timeout = cfg.getLong("timeout", 30000);
        echoInterval = cfg.getLong("echo-interval", 30000);
        logonInterval = cfg.getLong("logon-interval", 86400000L);
        initialDelay = cfg.getLong("initial-delay", 1000L);
        Element config = getPersist();
        logonMsg = getMsg("logon", config);
        logoffMsg = getMsg("logoff", config);
        echoMsg = getMsg("echo", config);
    }
    public void startService () {

        for (String mux : cfg.getAll("mux")) {
            new Thread(new Runner(mux), getName() + "-" + mux).start();
        }
    }

    public class Runner implements Runnable {
        String name;
        MUX mux;
        String readyKey;

        public Runner(String name) {
            this.name = name;

            try {
                mux = NameRegistrar.get("mux." + name);
                String[] readyIndicators = ((QMUX) mux).getReadyIndicatorNames();
                if (readyIndicators != null && readyIndicators.length > 0)
                    readyKey = readyIndicators[0];
                else
                    getLog().error("Ready indicator for MUX " + name + " not configured.");
            }
            catch (NameRegistrar.NotFoundException e) {
                getLog().warn(e);
            }
        }
        public void run () {
            while (running() && readyKey != null) {
                Object sessionId = sp.rd(readyKey, 60000);
                if (sessionId == null) {
                    getLog().info("Channel " + readyKey + " not ready");
                    continue;
                }

                try {
                    if (!sessionId.equals(sp.rdp(LOGON + readyKey))) {
                        doLogon(sessionId);
                        Thread.sleep(initialDelay);
                    }
                    else if (sp.rdp(ECHO + readyKey) == null) {
                        doEcho();
                    }
                }
                catch (Throwable t) {
                    getLog().warn(t);
                }
                ISOUtil.sleep(1000);
            }
            stopService();
        }

        public void stopService() {
            try {
                doLogoff();
            }
            catch (Throwable t) {
                getLog().warn(t);
            }
        }

        private void doLogon(Object sessionId) throws ISOException {
            ISOMsg resp = mux.request(createMsg("001", logonMsg), timeout);
            if (resp != null && "0000".equals(resp.getString(39))) {
                SpaceUtil.wipe(sp, LOGON + readyKey);
                sp.out(LOGON + readyKey, sessionId, logonInterval);
                getLog().info("Logon successful (session ID " + sessionId.toString() + ")");
            }
        }

        private void doLogoff () throws ISOException {
            SpaceUtil.wipe (sp, LOGON+readyKey);
            mux.request(createMsg("301", logoffMsg), 1000); // do not logoff
        }

        private void doEcho () throws ISOException {
            ISOMsg resp = mux.request(createMsg("301", echoMsg), timeout);
            if (resp != null) {
                sp.out(ECHO + readyKey, new Object(), echoInterval);
            }
        }

        private ISOMsg createMsg (String msgType, ISOMsg merge) throws ISOException {
            long traceNumber = SpaceUtil.nextLong(psp, TRACE) % 1000000;
            ISOMsg m = new ISOMsg ("2800");
            m.set(7, ISODate.getDateTime(new Date()));
            m.set(11, ISOUtil.zeropad(Long.toString(traceNumber), 6));
            m.set(70, msgType);
            if (merge != null)
                m.merge (merge);

            return m;
        }
    }

    private ISOMsg getMsg(String name, Element config) throws ConfigurationException {
        ISOMsg m = new ISOMsg();
        Element e = config.getChild(name);

        if (e != null)
            e = e.getChild("isomsg");

        if (e != null) {
            try {
                XMLPackager p = new XMLPackager();
                p.setLogger(getLog().getLogger(), getLog().getRealm()
                        + "-config-" + name);
                m.setPackager(p);
                ByteArrayOutputStream os = new ByteArrayOutputStream();
                OutputStreamWriter writer = new OutputStreamWriter(os);
                XMLOutputter out = new XMLOutputter();
                out.output(e, writer);
                writer.close();
                m.unpack(os.toByteArray());
            }
            catch (Exception ex) {
                throw new ConfigurationException(ex);
            }
        }
        return m;
    }
}

BONUS 0: multiple MUXes

The above example works with a single configuration file for multiple MUXes. You just need to repeat the mux property in the configuration and the QBean will handle as many connections as you want.

BONUS 1: message template

That private getMsg(...) method is used to tweak the different type of messages (logon, logoff, echo) so that you can have a configuration like this:

<logonmanager class="org.jpos.jpts.LogonManager" logger="Q2">
    <property name="persistent.space"  value="je:logonmgr" />
    <property name="timeout"           value="15000" />
    <property name="echo.interval"     value="60000" />
    <property name="mux"               value="mymux-0" />
    <property name="mux"               value="mymux-1" />
    <property name="mux"               value="mymux-2" />
    <logon>
      <isomsg>
        <field id="70" value="001" />
      </isomsg>
    </logon>
    <echo>
      <isomsg>
        <field id="70" value="301" />
      </isomsg>
    </echo>
    <logoff>
      <isomsg>
        <field id="70" value="002" />
      </isomsg>
    </logoff>
</logonmanager>

You can of course override other fields, which sometimes require arbitrary data (such as an acquirer ID in field 32).