Request Listener(s)
A QServer
can have one or more ISORequestListener
instances, which are configured as follows:
<qserver logger="Q2" name="qserver">
...
<request-listener class="com.your.ISORequestListenerImplementation" />
<request-listener class="com.your.AnotherISORequestListenerImplementation" />
...
</qserver>
The ISORequestListener
interface is straightforward:
public interface ISORequestListener {
boolean process(ISOSource source, ISOMsg m);
}
Its contract is simple: the first listener that returns true
—indicating it has handled the request—stops the server from invoking additional listeners down the chain.
Imagine you want to handle 2800
network management messages and reply to them immediately, without any further processing.
You can create a participant, such as org.jpos.tutorial.Handle2800
, which might look like this:
package org.jpos.tutorial;
import org.jpos.iso.*;
import org.jpos.util.*;
public class Handle2800 implements ISORequestListener extends Log {
@Override
public boolean process(ISOSource source, ISOMsg m) {
try {
if ("2800".equals(m.getMTI())) {
ISOMsg r = (ISOMsg) m.clone();
r.setResponseMTI();
r.set(39, "0000");
source.send(r);
return true;
}
} catch (ISOException | IOException e) {
warn(e);
}
return false;
}
}
Save this snippet as src/main/java/org/jpos/tutorial/Handle2800.java
, and then modify src/dist/deploy/50_qserver.xml
to include the following configuration:
<qserver logger="Q2" name="qserver">
...
...
<request-listener class="org.jpos.tutorial.Handle2800" logger="Q2" />
...
...
</qserver>
Finally, run the following commands to deploy and start the server:
gradle installApp && q2
Once the server is running, send a 2800
network management message. You should receive a 2810
network management response in return.
While extending org.jpos.util.Log
is not strictly required, it can be useful in simple classes like this one that do not already extend another class. By inheriting from Log
, you gain access to the logger subsystem without needing to implement LogSource
. This allows you to use convenient methods like info
, warn
, error
, and trace
directly. For example, in the catch
clause above, we use warn(e)
to log exceptions.
Synchronous call
The QServer
invokes the configured request listeners one by one, in a synchronous manner, until the first listener returns true
, as described above. The fact that this is a synchronous call is very important because, for every accepted socket, QServer
runs a single VirtualThread, and that thread is the one calling each request listener.
This means the request listener’s process
method is expected to return quickly.
Providing a fast response, like in the example above for handling network management messages, is fine. Updating internal metrics or performing quick computations to enhance or modify the message is also acceptable (though there are ISOFilters
better suited for such tasks). However, a request listener should never block the calling thread for a significant period of time.
In systems like jCard and jPTS, we configure most servers using the IncomingListener
, which has a very simple process
implementation:
public boolean process (ISOSource src, ISOMsg m) {
final Context ctx = new Context ();
ctx.put (timestamp, new Date(), remote);
ctx.put (source, src, remote);
ctx.put (request, m, remote);
if (additionalContextEntries != null) {
additionalContextEntries.entrySet().forEach(
e -> ctx.put(e.getKey(), e.getValue(), remote)
);
}
sp.out(queue, ctx, timeout);
return true;
}
This simple request listener implementation creates a new transaction Context
, populates it with the ISOSource
and ISOMsg
request parameters, and sends it to the TransactionManager
for further processing. The TransactionManager
then handles sessions, timeouts, and, because it has access to the underlying ISOChannel
(via the ISOSource
parameter), it can send a response. Later in this documentation, you’ll see that we provide standard transaction participants, such as the SendResponse
participant, specifically designed to handle responses.
QServer
is not the only component that accepts ISORequestListeners
. QMUX
(the multiplexer) also uses them to handle unexpected transactions, such as late responses or requests originating from a link that acts as a server from a TCP/IP perspective.
As with all components, the request listeners can be Configurable, XMLConfigurable, and accept properties as most Q2 components. A common (sample) configuration would be:
<request-listener class="org.jpos.iso.IncomingListener" logger="Q2">
<property name="queue" value="TXNMGR" />
<property name="ctx.DESTINATION" value="upstream-acquirer" />
</request-listener>
In this particular case, IncomingListener
adds all properties with the prefix ctx.
to the Context
, allowing them to be handled by custom transaction participants. For example, the property ctx.DESTINATION
will be added to the newly created Context
under the key DESTINATION
.