Monday, August 7, 2017

While loop in WSO2 ESB

How to invoke an endpoint many number of times.
There are two situations

  1. The number of invocation is defined. i.e we know how many time we are going to invoke the endpoint.
    In such a situation, we can construct a mock payload with that number of elements and by iterating it we can invoke the endpoint
  2. The number of invocation is not defined. i.e we don't know how many time we need to invoke the endpoint. i.e the response of previous invocation determine the number of calls
This post gives a sample ESB configuration to invoke an endpoint any number of time. Like a while loop (until a condition is satisfied)

IMPORTANT: I do NOT recommend the following configuration in a production environment. If you come across a similar situation, I recommend to revisit your use case and come up with the better way.

Anyway the following sample is really fun and shows you how powerful WSO2 ESB is. 

The idea behind creating a loop is, I am using filter mediator and based on a condition I decide whether to continue the flow or terminate. When I say continuing the flow, I will be invoking the endpoint using send mediator and dispatching the response to the same sequence which does the filter so there will be loop until it satisfies the terminating condition.

In this sample I will be using a simple data base to be updated. But I will be querying only one entry at a time and will be looping until all the entries are updated.

Setting up the sample

I am using WSO2 Enterprise Integrator 6.1.1. and MySQL as my database.


Creating the database and table


CREATE DATABASE SimpleSchool;
USE SimpleSchool;

CREATE TABLE `Student` (`Id` int(11) DEFAULT NULL, `Name` varchar(200) DEFAULT NULL, `State` varchar(4) DEFAULT NULL); 


Data Service Configuration

Remember to add the mysql driver jar into EI_HOME/lib

SimpSchool.dbs
<data name="SimpSchool" transports="http https local">
   <config enableOData="false" id="SimplSchool">
      <property name="driverClassName">com.mysql.jdbc.Driver</property>
      <property name="url">jdbc:mysql://localhost:3306/SimpleSchool</property>
      <property name="username">USERNAME</property>
      <property name="password">PASSWORD</property>
   </config>
   <query id="insertStudent" useConfig="SimplSchool">
      <sql>insert into Student (Id, Name, State) values (? ,? ,?);</sql>
      <param name="Id" sqlType="STRING"/>
      <param name="Name" sqlType="STRING"/>
      <param name="State" sqlType="STRING"/>
   </query>
   <query id="getCount" useConfig="SimplSchool">
      <sql>select count(*) as count from Student where State = 'New';</sql>
      <result element="Result" rowName="">
         <element column="count" name="count" xsdType="string"/>
      </result>
   </query>
   <query id="UpdateState" returnUpdatedRowCount="true" useConfig="SimplSchool">
      <sql>update Student set state='Done' where Id=?;</sql>
      <result element="UpdatedRowCount" rowName="" useColumnNumbers="true">
         <element column="1" name="Value" xsdType="integer"/>
      </result>
      <param name="Id" sqlType="STRING"/>
   </query>
   <query id="selectStudent" useConfig="SimplSchool">
      <sql>select Id, Name from Student where state='New' limit 1;</sql>
      <result element="Entries" rowName="Entry">
         <element column="Id" name="Id" xsdType="string"/>
         <element column="Name" name="Name" xsdType="string"/>
      </result>
   </query>
   <resource method="POST" path="insert">
      <call-query href="insertStudent">
         <with-param name="Id" query-param="Id"/>
         <with-param name="Name" query-param="Name"/>
         <with-param name="State" query-param="State"/>
      </call-query>
   </resource>
   <resource method="POST" path="getCount">
      <call-query href="getCount"/>
   </resource>
   <resource method="POST" path="select">
      <call-query href="selectStudent"/>
   </resource>
   <resource method="POST" path="update">
      <call-query href="UpdateState">
         <with-param name="Id" query-param="Id"/>
      </call-query>
   </resource>
</data>

ESB Configuration

I will be using an API to initiate the call, the API will invoke the loop_getCountAndCheck sequence which queries the database and retrieve the count, the result is being filtered and if the count is zero we call the loop_done sequence. Else we call the loop_update_logic sequence. Inside the loop_update_logic we update the data base and let the response to be processed inside the loop_getCountAndCheck sequence.


loop_getCountAndCheck 
<?xml version="1.0" encoding="UTF-8"?>
<sequence name="loop_getCountAndCheck" xmlns="http://ws.apache.org/ns/synapse">
    <payloadFactory media-type="xml">
        <format>
            <dat:_postgetcount xmlns:dat="http://ws.wso2.org/dataservice"/>
        </format>
    </payloadFactory>
    <call>
        <endpoint>
            <http method="POST" uri-template="http://localhost:8280/services/SimpSchool/getCount"/>
        </endpoint>
    </call>
    <log level="custom">
        <property expression="//n1:Result/n1:count"
            name="called get count. Remaining are:"
            xmlns:n1="http://ws.wso2.org/dataservice" xmlns:ns="http://org.apache.synapse/xsd"/>
    </log>
    <filter regex="0" source="//n1:count"
        xmlns:n1="http://ws.wso2.org/dataservice" xmlns:ns="http://org.apache.synapse/xsd">
        <then>
            <log level="custom">
                <property name="remaining are 0" value="calling done sequence"/>
            </log>
            <sequence key="loop_done"/>
        </then>
        <else>
            <log level="custom">
                <property name="remaining are not zero" value="calling update_logic sequence"/>
            </log>
            <sequence key="loop_update_logic"/>
        </else>
    </filter>
</sequence>

loop_done sequence
<?xml version="1.0" encoding="UTF-8"?>
<sequence name="loop_done" xmlns="http://ws.apache.org/ns/synapse">
    <log level="custom">
        <property name="this is done sequence" value="Responding to client"/>
    </log>
    <payloadFactory media-type="xml">
        <format>
            <done xmlns="">updating all</done>
        </format>
    </payloadFactory>
    <respond/>
</sequence>

loop_update_logic
<?xml version="1.0" encoding="UTF-8"?>
<sequence name="loop_update_logic" xmlns="http://ws.apache.org/ns/synapse">
    <log level="custom">
        <property name="this is logic sequence" value="selecting entries"/>
    </log>
    <payloadFactory media-type="xml">
        <format>
            <dat:_postselect xmlns:dat="http://ws.wso2.org/dataservice"/>
        </format>
    </payloadFactory>
    <call>
        <endpoint>
            <http method="POST" uri-template="http://localhost:8280/services/SimpSchool/select"/>
        </endpoint>
    </call>
    <log level="custom">
        <property name="data queried" value="for updating"/>
    </log>
    <payloadFactory media-type="xml">
        <format>
            <dat:_postupdate xmlns:dat="http://ws.wso2.org/dataservice">
                <dat:Id>$1</dat:Id>
            </dat:_postupdate>
        </format>
        <args>
            <arg evaluator="xml" expression="//n1:Id" literal="false"
                xmlns:n1="http://ws.wso2.org/dataservice" xmlns:ns="http://org.apache.synapse/xsd"/>
        </args>
    </payloadFactory>
    <log level="custom">
        <property name="data constructed" value="for updating"/>
    </log>
    <send receive="loop_getCountAndCheck">
        <endpoint>
            <http method="POST" uri-template="http://localhost:8280/services/SimpSchool/update"/>
        </endpoint>
    </send>
</sequence>

API configuration
<api xmlns="http://ws.apache.org/ns/synapse" name="UpdateInLoop" context="/loop">
   <resource methods="GET">
      <inSequence>
         <sequence key="loop_getCountAndCheck"/>
      </inSequence>
   </resource>
</api>


Sample data to fill the Student table
INSERT INTO Student (Id, Name, State) values (1, 'AAAA' , 'New'), (2, 'BBBB', 'New'), (3, 'CCCC' , 'New'), (4, 'DDDD' , 'New');


Sample curl request
curl -v http://localhost:8280/loop


Console output on a happy path
[2017-08-07 16:58:52,474] [EI-Core]  INFO - LogMediator called get count. Remaining are: = 4
[2017-08-07 16:58:52,474] [EI-Core]  INFO - LogMediator remaining are not zero = calling update_logic sequence
[2017-08-07 16:58:52,475] [EI-Core]  INFO - LogMediator this is logic sequence = selecting entries
[2017-08-07 16:58:52,488] [EI-Core]  INFO - LogMediator data queried = for updating
[2017-08-07 16:58:52,489] [EI-Core]  INFO - LogMediator data constructed = for updating
[2017-08-07 16:58:52,517] [EI-Core]  INFO - LogMediator called get count. Remaining are: = 3
[2017-08-07 16:58:52,518] [EI-Core]  INFO - LogMediator remaining are not zero = calling update_logic sequence
[2017-08-07 16:58:52,518] [EI-Core]  INFO - LogMediator this is logic sequence = selecting entries
[2017-08-07 16:58:52,524] [EI-Core]  INFO - LogMediator data queried = for updating
[2017-08-07 16:58:52,524] [EI-Core]  INFO - LogMediator data constructed = for updating
[2017-08-07 16:58:52,553] [EI-Core]  INFO - LogMediator called get count. Remaining are: = 2
[2017-08-07 16:58:52,553] [EI-Core]  INFO - LogMediator remaining are not zero = calling update_logic sequence
[2017-08-07 16:58:52,553] [EI-Core]  INFO - LogMediator this is logic sequence = selecting entries
[2017-08-07 16:58:52,559] [EI-Core]  INFO - LogMediator data queried = for updating
[2017-08-07 16:58:52,559] [EI-Core]  INFO - LogMediator data constructed = for updating
[2017-08-07 16:58:52,586] [EI-Core]  INFO - LogMediator called get count. Remaining are: = 1
[2017-08-07 16:58:52,586] [EI-Core]  INFO - LogMediator remaining are not zero = calling update_logic sequence
[2017-08-07 16:58:52,587] [EI-Core]  INFO - LogMediator this is logic sequence = selecting entries
[2017-08-07 16:58:52,597] [EI-Core]  INFO - LogMediator data queried = for updating
[2017-08-07 16:58:52,598] [EI-Core]  INFO - LogMediator data constructed = for updating
[2017-08-07 16:58:52,619] [EI-Core]  INFO - LogMediator called get count. Remaining are: = 0
[2017-08-07 16:58:52,620] [EI-Core]  INFO - LogMediator remaining are 0 = calling done sequence
[2017-08-07 16:58:52,620] [EI-Core]  INFO - LogMediator this is done sequence = Responding to client


The whole flow is working on the same message context. I haven't done any error handling here. We can introduce a property and by incrementing it in each iteration, we can further control the loop.

Cheers !

1 comment:

  1. Hello,
    Thanks for shared experience!

    What are the problems to implement that in production environment? I don't see another solution for looping between sequences and without repeating elements in the XML request.
    Thanks!

    ReplyDelete