Skip to main content

Extending WSO2 ESB with a Custom Transport Implementation - Part II

This blog post is a continuation to my previous blog post where I have described the concepts of WSO2 ESB transports mechanism. Since we have covered the basics, let's start writing some real code. I will be using the ISO8583 standard as my subject to this custom implementation. I will be grabbing some content from this blog post for my reference to ISO8583 java implementation (Business logic). Thanks Manoj Fernando for writing such an informative post.

http://manoj-fernando.blogspot.com/2013/08/iso8583-with-wso2-esb.html

Idea of the custom transport implementation is to provide a mechanism to write your business logic which can plug in to the WSO2 ESB runtime. I am not going to tell more about ISO8583 or it's internal implementations. I will be using already implemented java library jPos for this purpose.  It has the functionality to cover the basic use cases of ISO8583 implementations.

Sample use case

Let’s take the scenario of a certain financial application needing to make a credit transaction by sending an XML message that needs to be converted to an ISO8583 byte stream before passed on to the wire through a TCP channel.



Implementation

First, we need to define our ISO8583 field definition.  This might be a bit confusing to some.  If we are dealing with a specification, why do we need a field definition?  This is because that ISO8583 specification is not hard-binding any data elements and/or field ordering. It is entirely up to the application designer to define which field types/IDs need to be placed for their specific transactional requirements.

At a glance, the field definition file looks like the following.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE isopackager SYSTEM "genericpackager.dtd">
<isopackager>
  <isofield
      id="0"
      length="4"
      name="Message Type Indicator"
      class="org.jpos.iso.IFA_NUMERIC"/>
  <isofield
      id="1"
      length="16"
      name="Bitmap"
      class="org.jpos.iso.IFA_BITMAP"/>
  <isofield
      id="2"
      length="19"
      name="Primary Account number"
      class="org.jpos.iso.IFA_LLNUM"/>
  <isofield
      id="3"
      length="6"
      name="Processing Code"
      class="org.jpos.iso.IFA_NUMERIC"/>

</isopackager>

Please refer to [1] & [2] for a complete reference of ISO8583.   As per now, let me just say that each field should have an ID, a length and type specified in its definition.  I have only listed a snippet of the XML config here, and you may find the full definition jposdef.xml inside the codebase.
I have created a simple maven project to implement this transport.  Make sure that you have included the jPOS dependencies on pom.xml as follows.
              <dependency>
                     <groupId>org.jpos</groupId>
                     <artifactId>jpos</artifactId>
                     <version>1.9.0</version>
              </dependency>
              <dependency>
                     <groupId>com.sleepycat</groupId>
                     <artifactId>je</artifactId>
                     <version>4.0.92</version>
             </dependency>


To implement the transport sender, you need to subclass the AbstractTransportSender and implement its sendMessage method as follows.

public class ISO8583TransportSender extends AbstractTransportSender {

@Override
public void sendMessage(MessageContext msgCtx, String targetEPR,
OutTransportInfo outTransportInfo) throws AxisFault {

try {
URI isoURL = new URI(targetEPR);
ISOPackager packager = new GenericPackager(this.getClass()
.getResourceAsStream("jposdef.xml"));

ASCIIChannel chl = new ASCIIChannel(isoURL.getHost(),
isoURL.getPort(), packager);

                        writeMessageOut(msgCtx, chl);

} catch (Exception e) {
throw new AxisFault(
"An exception occured in sending the ISO message");
}

}


    /**
     * Writting the message to the output channel after applying correct message formatter
     * @param msgContext
     * @param chl
     * @throws org.apache.axis2.AxisFault
     * @throws java.io.IOException
     */
    private void writeMessageOut(MessageContext msgContext,
                                 ASCIIChannel chl) throws AxisFault, IOException {
        ISO8583MessageFormatter messageFormatter = (ISO8583MessageFormatter)BaseUtils.getMessageFormatter(msgContext);
        OMOutputFormat format = BaseUtils.getOMOutputFormat(msgContext);
        messageFormatter.setAsciiChannel(chl);
        messageFormatter.writeTo(msgContext, format, null, true);
    }

Within the TransportSender, we are extracting the URL and then create the relevant entities for the Message Formatter and pass the control to the MessageFormatter. Within the MessageFormatter, we can send the actual message to the back end server.

public class ISO8583MessageFormatter implements MessageFormatter {

    private ASCIIChannel asciiChannel;
    @Override
    public byte[] getBytes(MessageContext messageContext, OMOutputFormat omOutputFormat) throws AxisFault {
        return new byte[0];
    }

    @Override
    public void writeTo(MessageContext messageContext, OMOutputFormat omOutputFormat, OutputStream outputStream, boolean b) throws AxisFault {
        ISOMsg isoMsg = toISO8583(messageContext);
        ASCIIChannel chl = this.asciiChannel;
        try {
            chl.connect();
            chl.send(isoMsg);
            chl.disconnect();
        } catch (Exception ex) {
            throw new AxisFault(
                    "An exception occured in sending the ISO message");
        }
    }

    @Override
    public String getContentType(MessageContext messageContext, OMOutputFormat omOutputFormat, String s) {
        return null;
    }

    @Override
    public URL getTargetAddress(MessageContext messageContext, OMOutputFormat omOutputFormat, URL url) throws AxisFault {
        return null;
    }

    @Override
    public String formatSOAPAction(MessageContext messageContext, OMOutputFormat omOutputFormat, String s) {
        return null;
    }

    public ISOMsg toISO8583(MessageContext messageContext) throws AxisFault {
        SOAPEnvelope soapEnvelope = messageContext.getEnvelope();
        OMElement isoElements = soapEnvelope.getBody().getFirstElement();

        ISOMsg isoMsg = new ISOMsg();

        @SuppressWarnings("unchecked")
        Iterator<OMElement> fieldItr = isoElements.getFirstChildWithName(
                new QName(ISO8583Constant.TAG_DATA)).getChildrenWithLocalName(
                ISO8583Constant.TAG_FIELD);

        String mtiVal = isoElements
                .getFirstChildWithName(new QName(ISO8583Constant.TAG_CONFIG))
                .getFirstChildWithName(new QName(ISO8583Constant.TAG_MTI))
                .getText();

        try {
            isoMsg.setMTI(mtiVal);

            while (fieldItr.hasNext()) {

                OMElement isoElement = (OMElement) fieldItr.next();

                String isoValue = isoElement.getText();

                int isoTypeID = Integer.parseInt(isoElement.getAttribute(
                        new QName("id")).getAttributeValue());

                isoMsg.set(isoTypeID, isoValue);

            }

            return isoMsg;

        } catch (ISOException ex) {
            throw new AxisFault("Error parsing the ISO8583 payload");
        } catch (Exception e) {

            throw new AxisFault("Error processing stream");
        }

    }


    public ASCIIChannel getAsciiChannel() {
        return asciiChannel;
    }

    public void setAsciiChannel(ASCIIChannel asciiChannel) {
        this.asciiChannel = asciiChannel;
    }

}

Here within the formatter, we are transforming the XML message into ISO8583 binary message and send to the back end server.

This is only an example of dividing your message sending logic to message sender and message formatter. You can design your implementation according to your requirement. Sometimes, you may not need specific formatter but you can do the formatting part also in the sender itself. But I have delegated a part of the message handling to message formatter for demonstration purpose.

Likewise, you can write a message receiver and message builder for receiving the messages via iso8583 protocol. I will leave that as an exercise for the reader.

Once we have the message sender and formatter implemented, we need to register them in the axis2.xml file. Let's go to the axis2.xml file and add following 2 entries there.

        <messageFormatter contentType="application/iso8583"
                         class="org.wso2.transport.iso8583.message.ISO8583MessageFormatter"/>

<transportSender name="iso8583" class="org.wso2.transport.iso8583.ISO8583TransportSender"/>

Once you create the jar file from your custom transport implementation code, place it is ESB_HOME/repository/components/lib directory.

If you are done with the above steps, you can start the ESB server.

Let's create a sample API to interact with this custom transport implementation. Here is the API definition.

<api xmlns="http://ws.apache.org/ns/synapse" name="iso8583" context="/iso8583">
   <resource methods="POST GET">
      <inSequence>
         <log level="full"></log>
         <property name="OUT_ONLY" value="true"></property>
         <property name="FORCE_SC_ACCEPTED" value="true" scope="axis2"></property>
         <property name="messageType" value="application/iso8583" scope="axis2"></property>
         <send>
            <endpoint name="isoserver">
               <address uri="iso8583://localhost:5000"></address>
            </endpoint>
         </send>
      </inSequence>
   </resource>
</api>

In the above configuration, I have specified the messageType as application/iso8583 such that it will engage the correct message formatter within the mediation flow. 

Now we need to create a sample TestServer to test the functionality of the ISO8583 back end server. We can create a MockServer using the jpos library itself. Here is the code for the TestServer.

public class TestServer implements ISORequestListener {
    static final String hostname = "localhost";
    static final int portNumber = 5000;


    public static void main(String[] args) throws ISOException {


        ISOPackager packager = new GenericPackager("jposdef.xml");
        ServerChannel channel = new ASCIIChannel(hostname, portNumber, packager);
        ISOServer server = new ISOServer(portNumber, channel, null);

        server.addISORequestListener(new TestServer());

        System.out.println("ISO8583 server started...");
        new Thread(server).start();
    }

    public boolean process(ISOSource isoSrc, ISOMsg isoMsg) {
        try {
            System.out.println("ISO8583 incoming message on host ["
                    + ((BaseChannel) isoSrc).getSocket().getInetAddress()
                    .getHostAddress() + "]");

            if (isoMsg.getMTI().equalsIgnoreCase("1800")) {

                receiveMessage(isoSrc, isoMsg);
                logISOMsg(isoMsg);

            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return true;
    }

    private void receiveMessage(ISOSource isoSrc, ISOMsg isoMsg)
            throws ISOException, IOException {
        System.out.println("ISO8583 Message received...");
        ISOMsg reply = (ISOMsg) isoMsg.clone();
        reply.setMTI("1810");
        reply.set(39, "00");

        isoSrc.send(reply);
    }

    private static void logISOMsg(ISOMsg msg) {
        System.out.println("----ISO MESSAGE-----");
        try {
            System.out.println("  MTI : " + msg.getMTI());
            for (int i = 1; i <= msg.getMaxField(); i++) {
                if (msg.hasField(i)) {
                    System.out.println("    Field-" + i + " : "
                            + msg.getString(i));
                }
            }
        } catch (ISOException e) {
            e.printStackTrace();
        } finally {
            System.out.println("--------------------");
        }

    }

}

You can run the above program to mimic the ISO8583 server and then we can send a message from a client like Advanced REST Client plugin in the chrome browser. Our message payload should be like below.

<iso8583message>
       <config>
              <mti>1800</mti>
       </config>
       <data>
              <field id="3">110</field>
              <field id="5">4200.00</field>
              <field id="48">Simple Credit Transaction</field>
              <field id="6">645.23</field>
              <field id="88">66377125</field>
       </data>
</iso8583message>

When we send this message from client, ESB will accept the message and execute the message sender we have written and then selects the message formatter and send to the mock back end server. You can see the following log printed in the TestServer side if you have done all the things right.

ISO8583 server started...
ISO8583 incoming message on host [127.0.0.1]
ISO8583 Message received...
----ISO MESSAGE-----
  MTI : 1800
    Field-3 : 000110
    Field-5 : 000004200.00
    Field-6 : 000000645.23
    Field-48 : Simple Credit Transaction
    Field-88 : 0000000066377125
--------------------

When you are have configured the ESB, you will get exceptions if you do not copy following jar files to the lib directory alongside with custom transport jar file.

  • jpos-1.9.0.jar
  • jdom-1.1.3.jar
  • commons-cli-1.3.1.jar

Now we have written our message sender and message formatter implementations. Likewise, you can implement the message receiver and message builder code also. I have created an archive with all the relevant artifacts which I have developed for this blog post and uploaded them to github. You can download all the projects and relevant jar files from following location.


Comments

Post a Comment

Popular posts from this blog

How to protect your APIs with self contained access token (JWT) using WSO2 API Manager and WSO2 Identity Server

In a typical enterprise information system, there is a high chance that people will use different types of systems built by different vendors to implement certain types of functionalities. The APIs might be hosted in an API Manager developed by vendor A and the user management can be implemented using a different vendor (vendor B). In this type of a situation, one system will not be able to directly contact the other system but they want to use both systems in tandem. Self-contained access tokens are used in these types of situations where applications can get the token from one system and use that in another system to access protected resources. In this scenario, the second system does not need to make a contact to the first system over the network to validate the user information since the token is self-contained and it has relevant details about the user. This will improve the token processing time significantly since it completely removes the network interaction. The below fig...

WSO2 ESB creating a response for a "GET" request

When you are creating REST APIs with WSO2 ESB, you may need to send some response message back to the user when something goes wrong. In this kind of scenario, you can create a payload inside the ESB and send it back. For doing this, you can use the below configuration. <api xmlns=" http://ws.apache.org/ ns/synapse " name="LoopBackProxy" context="/loopback">    <resource methods="POST GET">       <inSequence>          <property name="NO_ENTITY_BODY" scope="axis2" action="remove"></property>          <log level="full"></log>          <payloadFactory media-type="xml">             <format>                <m:messageBeforeLoopBack xmlns:m=" http://services...

WSO2 ESB usage of get-property function

What are Properties? WSO2 has a huge set of mediators but property mediator is mostly used mediator for writing any proxy service or API. Property mediator is used to store any value or xml fragment temporarily during life cycle of a thread for any service or API. We can compare “Property” mediator with “Variable” in any other traditional programming languages (Like: C, C++, Java, .Net etc). There are few properties those are used/maintained by ESB itself and on the other hand few properties can be defined by users (programmers). In other words, we can say that properties can be define in below 2 categories: ESB Defined Properties User Defined Properties. These properties can be stored/defined in different scopes, like: Transport Synapse or Default Axis2 Registry System Operation Generally, these properties are read by get-properties() function. This function can be invoked with below 2 variations. get-property(String propertyName) get-property(String scop...