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.