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.