Search code examples
jpos

JPOS ISORequestListener: How to set custom fields for a specific transaction response, all while auto-responding to others?


Experimenting with converting my JPOS project from just vanilla JPOS code, to using Q2 instead.

One thing I'm having trouble implementing is a custom response for certain transactions of the user's choice in ISORequestListener. Currently, my tool receives a JSON message specifying what values to send in the Acquirer request message, as well as what values the issuer is to respond with.

{
"messages": [
    {
    "msgName": "Transaction - 01",
    "acquirer": {
        "delay": 5,
        "out": { //send out these fields in the outgoing request
            "0": "0100",
            "2": "9999999999999999",
            "3": "000000",
            "4": "000000045000",
            "6": "000000045000",
            ...
            ...
            ...
            
        },
        "in": {}
    },
    "issuer": {
        "in": {},
        "out": { //Issuer responds with these custom fields for this specific txn, other txns 
                 //auto-respond
            "0": "0110",
            "39": "05"
        }
    }
}
]}

Here is my Q2 setup for the issuer:

<channel-adaptor name="iss-channel" class="org.jpos.q2.iso.ChannelAdaptor" logger="Q2">
<channel class="org.jpos.iso.channel.CSChannel"
         packager="org.jpos.iso.packager.GenericPackager" logger="Q2">
    <property name="packager-config"
        value="src/main/resources/iso4.xml" />
    <property name="host" value="10.xxx.xxx.xx" />
    <property name="port" value="9999" />
    <property name="timeout" value="300000" />
</channel>
<in>iss-channel-send</in>
<out>iss-channel-receive</out>
<reconnect-delay>10000</reconnect-delay>
</channel-adaptor>

<mux class="org.jpos.q2.iso.QMUX" logger="Q2" name="iss-mux" realm="acq-realm">
<in>iss-channel-receive</in>
<out>iss-channel-send</out>
<ready>iss-channel.ready</ready>
<request-listener class="AutoResponder"></request-listener>
</mux>

And the AutoResponder ISORequestLister class:

    import org.jpos.core.Configurable;
import org.jpos.core.Configuration;
import org.jpos.core.ConfigurationException;
import org.jpos.iso.ISOMsg;
import org.jpos.iso.ISORequestListener;
import org.jpos.iso.ISOSource;

public class AutoResponder implements ISORequestListener, Configurable {
    private Configuration cfg;

    @Override
    public void setConfiguration(Configuration cfg) throws ConfigurationException {
        this.cfg = cfg;
    }

    @Override
    public boolean process(ISOSource source, ISOMsg m) {
        try {
            ISOMsg r = (ISOMsg) m.clone();
            r.setResponseMTI();
            r.set(39, cfg.get("rc", "00"));
            source.send(r);

        } catch (Exception e) {
            throw new RuntimeException("Could not process message");
        }
        return true;
    }
}

This current setup is fantastic for autoresponding to all transactions, but I'd like to be able to modify some of the issuer responses according to what the user would like to respond with (This is for negative tests in QA environment)

How can I tell the ISORequestListener that I would like to send a custom field for a specific transaction, all while auto-responding to all other transactions?

In my current tool's code, I have two threads (one for Acq, one for Iss) that build each request/response and then send it using BaseChannel.send(). But I'm unsure on how this can be achieved using Q2's ISORequestListener


Solution

  • Let autoresponder only respond to some message types

    For the first part of the question, you can add properties to your ISORequestListener saying which MITs you want to autorespond, or if the logic is more complex, you can add it to the AutoResponder.process method.

    Here is a simple example, based on your code:

        @Override
        public boolean process(ISOSource source, ISOMsg m) {
            try {
                if (cfg.get("handled-mtis").indexOf(m.getMTI()) {
                    ISOMsg r = (ISOMsg) m.clone();
                    r.setResponseMTI();
                    r.set(39, cfg.get("rc", "00"));
                    source.send(r);
                    return true;
                } else {
                    return false;
                }
    
            } catch (Exception e) {
                throw new RuntimeException("Could not process message");
            }
            return true;
        }
    

    This would work with a configuration like this:

    <request-listener class="AutoResponder">
      <property name="handled-mtis" value="0800"/>
    </request-listener>
    

    If you rather want to specify the not handled list, just negate the if condition, and name the property accordingly.

    This works because the mux will try every request listener until one returns true. So if the auto responder returns false because it doesn't process the message's MTI, the next, in which you'll add the logic to handle the others.

    Adding custom logic to jPOS for setting other fields

    Regarding the second part of your question, I'd suggest following the first 2 tutorials in the jPOS tutorials page. They are written for a server receiving the requests, but essentially you just need to replace that server by your mux and channel.

    You just need to add the tutorial's server request-listener to your mux configuration after the other. It would be something like this:

    <mux class="org.jpos.q2.iso.QMUX" logger="Q2" name="iss-mux" realm="acq-realm">
    <in>iss-channel-receive</in>
    <out>iss-channel-send</out>
    <ready>iss-channel.ready</ready>
    <request-listener class="AutoResponder">
      <property name="handled-mtis" value="0800"/>
    </request-listener>
    <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>
    </mux>
    

    You could also implement the autoresponder logic as a transaction participant of the transaction manager. You can have an idea of how, from the SelectDestination participant of the second tutorial.

    Dynamically change response values

    If you want response values for fields set in runtime. You could create a service that take those values from the json, and put them in a space. Then, the request listener can take those values from the space like this in the process method:

    ISOMsg responseValues = space.rdp(cfg.get("response-values-key", "response-values"); <1>
    if (responseValues != null) r.merge(responseValues);
    

    <1> If you don't configure the response-values-key in the xml response-values is used as default.

    You would also need to configure the space in the request listener:

    Space space;
    void setConfiguration(Configuration cfg) {
      ...
      space = SpaceFactory.getSpace(cfg.get("space"));
    }
    

    Then in the component setting the values:

    ISOMsg m = new ISOMsg();
    // set desired values on message
    Space space = SpaceFactory.getSpace(""); //<1>
    space.out("response-values", m); //<2> 
    

    <1> If you pass empty string here and don't configure it in the request listener, both are going to be using the same default transient space.

    <2> You can make this key (response-values) configurable, it should be the same as the key used to retrieve it in the request listener process method.