Tuesday, September 6, 2016

Cloud to Cloud Integration with WSO2 ESB Connectors

This is the continuation of my article "Cloud to Cloud Integration with WSO2 ESB Connectors."

Sequences

A sequence is a logical arrangement of a set of mediators where each mediator is a functional unit and performs a predefined task on a given message. In the zuoraNetsuiteIntegration sequence, we use the query operation from zuorasoap connector to fetch the newly created invoices from Zuora. Since our requirement is only to get the newly created invoices, we use the property mediator to read the last runtime from the registry, construct the query with it and pass the query through the queryString parameter to Zuora. Then we use the script mediator to build the current runtime in the required format. Thereafter, we use the sequence mediator to refer the generateNetsuiteObjects sequence, which is used to process the invoice records. Once the records have been processed, we update the registry with the current runtime by using the property mediator with the registry scope.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?xml version="1.0" encoding="UTF-8"?>
<sequence xmlns="http://ws.apache.org/ns/synapse" name="zuoraNetsuiteIntegration" trace="disable">
   <property xmlns:ns="http://org.apache.synapse/xsd" expression="get-property('registry', 'netsuite/dateTime')" name="dateTime" scope="default" type="STRING" />
   <property xmlns:ns="http://org.apache.synapse/xsd" expression="get-property('registry', 'netsuite/subsidiaryId')" name="subsidiaryId" scope="default" type="STRING" />
   <property xmlns:ns="http://org.apache.synapse/xsd" expression="get-property('registry', 'netsuite/itemId')" name="itemId" scope="default" type="STRING" />
   <property xmlns:ns="http://org.apache.synapse/xsd" expression="fn:concat(&quot;Select Id, AccountId, InvoiceNumber, InvoiceDate, Amount From Invoice WHERE CreatedDate &gt;= '&quot;,get-property('dateTime'),&quot;'&quot;)" name="query" scope="default" type="STRING" />
   <script language="js"><![CDATA[var d = new Date();                         
        var dateTime = d.getFullYear()+ "-"+ ("0" + (d.getMonth()+1)).slice(-2)  + "-"  +("0" + d.getDate()).slice(-2)
                 + "T" + ("0" + d.getHours()).slice(-2)+ ":" + ("0"+d.getMinutes()).slice(-2) + ":" + ("0"+d.getSeconds()).slice(-2)+".000";
            mc.setProperty("dateTime", dateTime.toString());]]></script>
   <property xmlns:ns="http://org.apache.synapse/xsd" expression="get-property('dateTime')" name="netsuite/dateTime" scope="registry" type="STRING" />
   <zuorasoap.query configKey="zuoraSoapConfig">
      <queryString>{$ctx:query}</queryString>
      <batchSize>2000</batchSize>
   </zuorasoap.query>
   <property xmlns:ns="http://org.apache.synapse/xsd" xmlns:urn="http://api.zuora.com/" expression="//urn:size/text()" name="records" scope="default" type="STRING" />
   <filter xmlns:ns="http://org.apache.synapse/xsd" xpath="get-property('records') &gt;'0'">
      <then>
         <sequence key="generateNetsuiteObjects" />
         <iterate xmlns:zuora="http://wso2.org/zuorasoap/adaptor" continueParent="true" expression="//zuora:iterator">
            <target>
               <sequence>
                  <zuorasoap.queryMore>
                     <batchSize>2000</batchSize>
                  </zuorasoap.queryMore>
                  <sequence key="generateNetsuiteObjects" />
               </sequence>
            </target>
         </iterate>
      </then>
      <else />
   </filter>
   <property xmlns:ns="http://org.apache.synapse/xsd" expression="get-property('dateTime')" name="netsuite/dateTime" scope="registry" type="STRING" />
</sequence>

In the generateNetsuiteOjects sequence, we use operations from zuora and zuorasoap connectors to get the relevant data from Zuora and check whether the particular customer is already created or not in the NetSuite using the search operation from the netsuite connector. If the customer is already existing, we create the invoice using the createInvoice sequence. Else we create the customer using the createCustomer sequence and create the invoice using the createInvoice sequence.


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
<?xml version="1.0" encoding="UTF-8"?>
<sequence xmlns="http://ws.apache.org/ns/synapse" name="generateNetsuiteObjects" trace="disable">
   <iterate xmlns:ns="http://org.apache.synapse/xsd" xmlns:ns1="http://api.zuora.com/" continueParent="true" expression="//ns1:records" id="invoicesIterator" preservePayload="true" sequential="true">
      <target>
         <sequence>
            <property xmlns:urn="http://object.api.zuora.com/" expression="//urn:Id/text()" name="invoiceId" scope="default" type="STRING" />
            <property xmlns:urn="http://object.api.zuora.com/" expression="//urn:InvoiceNumber/text()" name="invoiceNumber" scope="default" type="STRING" />
            <property xmlns:urn="http://object.api.zuora.com/" expression="//urn:InvoiceDate/text()" name="invoiceDate" scope="default" type="STRING" />
            <property xmlns:urn="http://object.api.zuora.com/" expression="//urn:AccountId/text()" name="accountId" scope="default" type="STRING" />
            <property xmlns:urn="http://object.api.zuora.com/" expression="//urn:Amount/text()" name="amount" scope="default" type="STRING" />
            <property expression="fn:concat(&quot;select FirstName,WorkEmail,LastName, WorkPhone, State, PostalCode, PersonalEmail, Country, City, Address2, Address1 from Contact WHERE AccountId = '&quot;,get-property('accountId'),&quot;'&quot;)" name="query" scope="default" type="STRING" />
            <zuorasoap.query configKey="zuoraSoapConfig">
               <queryString>{get-property('query')}</queryString>
               <batchSize>200</batchSize>
            </zuorasoap.query>
            <property xmlns:urn="http://object.api.zuora.com/" expression="(//urn:LastName)[1]/text()" name="lastName" scope="default" type="STRING" />
            <property xmlns:urn="http://object.api.zuora.com/" expression="(//urn:FirstName)[1]/text()" name="firstName" scope="default" type="STRING" />
            <property xmlns:urn="http://object.api.zuora.com/" expression="(//urn:WorkEmail)[1]/text()" name="workEmail" scope="default" type="STRING" />
            <property xmlns:urn="http://object.api.zuora.com/" expression="(//urn:Address1)[1]/text()" name="address1" scope="default" type="STRING" />
            <property xmlns:urn="http://object.api.zuora.com/" expression="(//urn:Address2)[1]/text()" name="address2" scope="default" type="STRING" />
            <property xmlns:urn="http://object.api.zuora.com/" expression="(//urn:City)[1]/text()" name="city" scope="default" type="STRING" />
            <property xmlns:urn="http://object.api.zuora.com/" expression="(//urn:Country)[1]/text()" name="country" scope="default" type="STRING" />
            <property expression="fn:concat(&quot;select Name from Account WHERE Id = '&quot;,get-property('accountId'),&quot;'&quot;)" name="accountQuery" scope="default" type="STRING" />
            <zuorasoap.query configKey="zuoraSoapConfig">
               <queryString>{get-property('accountQuery')}</queryString>
               <batchSize>200</batchSize>
            </zuorasoap.query>
            <property xmlns:urn="http://object.api.zuora.com/" expression="//urn:Name/text()" name="accountName" scope="default" type="STRING" />
            <zuora.getInvoices configKey="zuoraRestConfig">
               <accountKey>{get-property('accountId')}</accountKey>
            </zuora.getInvoices>
            <property expression="fn:concat(&quot;//invoices[id='&quot;,get-property('invoiceId'),&quot;']/invoiceItems[1]/serviceStartDate/text()&quot; )" name="exprStartDate" scope="default" type="STRING" />
            <property expression="fn:concat(&quot;//invoices[id='&quot;,get-property('invoiceId'),&quot;']/invoiceItems[1]/serviceEndDate/text()&quot; )" name="exprEndDate" scope="default" type="STRING" />
            <property expression="fn:concat(&quot;//invoices[id='&quot;,get-property('invoiceId'),&quot;']/invoiceItems[1]/chargeName/text()&quot; )" name="exprChargeName" scope="default" type="STRING" />
            <property expression="evaluate($ctx:exprStartDate)" name="serviceStartDate" scope="default" type="STRING" />
            <property expression="evaluate($ctx:exprEndDate)" name="serviceEndDate" scope="default" type="STRING" />
            <property expression="evaluate($ctx:exprChargeName)" name="chargeName" scope="default" type="STRING" />
            <payloadFactory media-type="xml">
               <format>
                  <urn:searchRecord xmlns:urn="wso2.connector.netsuite">
                     <platformMsgs:searchRecord xmlns:platformMsgs="urn:messages_2014_1.platform.webservices.netsuite.com" xmlns:ns1="urn:relationships_2014_1.lists.webservices.netsuite.com" xmlns:platformCore="urn:core_2014_1.platform.webservices.netsuite.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ns1:CustomerSearch">
                        <ns1:basic>
                           <ns2:email xmlns:ns2="urn:common_2014_1.platform.webservices.netsuite.com" operator="is" xsi:type="platformCore:SearchStringField">
                              <ns3:searchValue xmlns:ns3="urn:core_2014_1.platform.webservices.netsuite.com">$1</ns3:searchValue>
                           </ns2:email>
                        </ns1:basic>
                     </platformMsgs:searchRecord>
                  </urn:searchRecord>
               </format>
               <args>
                  <arg evaluator="xml" expression="get-property('workEmail')" />
               </args>
            </payloadFactory>
            <netsuite.search configKey="netsuiteConfig">
               <searchRecord xmlns:urn="wso2.connector.netsuite">{//urn:searchRecord/*}</searchRecord>
            </netsuite.search>
            <property xmlns:urn="urn:core_2014_1.platform.webservices.netsuite.com" expression="//urn:status/@isSuccess" name="status" scope="default" type="STRING" />
            <filter xpath="get-property('status') = 'true'">
               <then>
                  <property xmlns:urn="urn:core_2014_1.platform.webservices.netsuite.com" expression="//urn:totalRecords/text()" name="totalRecords" scope="default" type="STRING" />
                  <filter xpath="get-property('totalRecords') ='1'">
                     <then>
                        <property xmlns:urn="urn:core_2014_1.platform.webservices.netsuite.com" expression="//urn:record/@internalId" name="customerId" scope="default" type="STRING" />
                        <sequence key="createInvoice" />
                        <property xmlns:urn="urn:core_2014_1.platform.webservices.netsuite.com" expression="//urn:status/@isSuccess" name="status" scope="default" type="STRING" />
                        <filter xpath="get-property('status')= 'true'">
                           <then>
                              <log level="custom">
                                 <property expression="fn:concat('Invoice created in Netsuite ',get-property('invoiceNumber'))" name="status" />
                              </log>
                           </then>
                           <else>
                              <log level="custom">
                                 <property expression="fn:concat('Unable to create the invoice in Netsuite for ',get-property('invoiceNumber'))" name="status" />
                              </log>
                           </else>
                        </filter>
                     </then>
                     <else>
                        <sequence key="createCustomer" />
                        <property xmlns:urn="urn:core_2014_1.platform.webservices.netsuite.com" expression="//urn:status/@isSuccess" name="status" scope="default" type="STRING" />
                        <filter xpath="get-property('status')= 'true'">
                           <then>
                              <property xmlns:urn="urn:messages_2014_1.platform.webservices.netsuite.com" expression="//urn:baseRef/@internalId" name="customerId" scope="default" type="STRING" />
                              <sequence key="createInvoice" />
                              <property xmlns:urn="urn:core_2014_1.platform.webservices.netsuite.com" expression="//urn:status/@isSuccess" name="status" scope="default" type="STRING" />
                              <filter xpath="get-property('status')= 'true'">
                                 <then>
                                    <log level="custom">
                                       <property expression="fn:concat('Invoice created in Netsuite ',get-property('invoiceNumber'))" name="status" />
                                    </log>
                                 </then>
                                 <else>
                                    <log level="custom">
                                       <property expression="fn:concat('Unable to create invoice in Netsuite for ',get-property('invoiceNumber'))" name="status" />
                                    </log>
                                 </else>
                              </filter>
                           </then>
                           <else>
                              <log level="custom">
                                 <property expression="fn:concat('Unable to create customer and invoice in Netsuite for invoice ',get-property('invoiceNumber'))" name="status" />
                              </log>
                           </else>
                        </filter>
                     </else>
                  </filter>
               </then>
               <else>
                  <log level="custom">
                     <property expression="fn:concat('Unable to create Netsuite objects for invoice ',get-property('invoiceNumber'))" name="status" />
                  </log>
               </else>
            </filter>
         </sequence>
      </target>
   </iterate>
</sequence>

In the createCustomer sequence, we generate the desired form of the message payload using the PayloadFactory mediator and insert the payload to the addList operation in the netsuite connector.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?xml version="1.0" encoding="UTF-8"?>
<sequence xmlns="http://ws.apache.org/ns/synapse" name="createCustomer" trace="disable">
   <payloadFactory media-type="xml">
      <format>
         <urn:records xmlns:urn="wso2.connector.netsuite">
            <platformMsgs:record xmlns:platformMsgs="urn:messages_2014_1.platform.webservices.netsuite.com" xmlns:RecordRef="urn:core_2014_1.platform.webservices.netsuite.com" xmlns:listRel="urn:relationships_2014_1.lists.webservices.netsuite.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="listRel:Customer">
               <listRel:entityId>$1</listRel:entityId>
               <listRel:email>$2</listRel:email>
               <listRel:companyName>$1</listRel:companyName>
               <listRel:addressbookList replaceAll="true">
                  <listRel:addressbook>
                     <listRel:addr1>$3</listRel:addr1>
                     <listRel:addr2>$4</listRel:addr2>
                     <listRel:city>$5</listRel:city>
                  </listRel:addressbook>
               </listRel:addressbookList>
               <listRel:subsidiary>
                  <RecordRef:internalId>$6</RecordRef:internalId>
                  <RecordRef:type>subsidiary</RecordRef:type>
               </listRel:subsidiary>
            </platformMsgs:record>
         </urn:records>
      </format>
      <args>
         <arg xmlns:ns="http://org.apache.synapse/xsd" xmlns:ns3="http://org.apache.synapse/xsd" xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope" evaluator="xml" expression="get-property('accountName')" />
         <arg xmlns:ns="http://org.apache.synapse/xsd" xmlns:ns3="http://org.apache.synapse/xsd" xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope" evaluator="xml" expression="get-property('workEmail')" />
         <arg xmlns:ns="http://org.apache.synapse/xsd" xmlns:ns3="http://org.apache.synapse/xsd" xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope" evaluator="xml" expression="get-property('address1')" />
         <arg xmlns:ns="http://org.apache.synapse/xsd" xmlns:ns3="http://org.apache.synapse/xsd" xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope" evaluator="xml" expression="get-property('address2')" />
         <arg xmlns:ns="http://org.apache.synapse/xsd" xmlns:ns3="http://org.apache.synapse/xsd" xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope" evaluator="xml" expression="get-property('city')" />
         <arg xmlns:ns="http://org.apache.synapse/xsd" xmlns:ns3="http://org.apache.synapse/xsd" xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope" evaluator="xml" expression="get-property('subsidiaryId')" />
      </args>
   </payloadFactory>
   <netsuite.addList configKey="netsuiteConfig">
      <records xmlns:ns3="http://org.apache.synapse/xsd" xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope" xmlns:urn="wso2.connector.netsuite">{//urn:records/*}</records>
   </netsuite.addList>
</sequence>

In the createInvoice sequence, we generat the desired form of the message payload using the PayloadFactory mediator and insert the payload to the addList operation in the netsuite connector.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?xml version="1.0" encoding="UTF-8"?>
<sequence xmlns="http://ws.apache.org/ns/synapse" name="createInvoice" trace="disable">
   <payloadFactory media-type="xml">
      <format>
         <urn:records xmlns:urn="wso2.connector.netsuite">
            <platformMsgs:record xmlns:platformMsgs="urn:messages_2014_1.platform.webservices.netsuite.com" xmlns:tranSales="urn:sales_2014_1.transactions.webservices.netsuite.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="tranSales:Invoice">
               <tranSales:entity internalId="$1" type="customer" />
               <tranSales:tranId>$5</tranSales:tranId>
               <tranSales:dueDate>$6</tranSales:dueDate>
               <tranSales:itemList>
                  <tranSales:item>
                     <tranSales:item internalId="$8" />
                     <tranSales:quantity>1</tranSales:quantity>
                     <tranSales:amount>$2</tranSales:amount>
                     <tranSales:revRecStartDate>$3</tranSales:revRecStartDate>
                     <tranSales:revRecEndDate>$4</tranSales:revRecEndDate>
                     <tranSales:description>$7</tranSales:description>
                  </tranSales:item>
               </tranSales:itemList>
            </platformMsgs:record>
         </urn:records>
      </format>
      <args>
         <arg xmlns:ns="http://org.apache.synapse/xsd" evaluator="xml" expression="get-property('customerId')" />
         <arg xmlns:ns="http://org.apache.synapse/xsd" evaluator="xml" expression="get-property('amount')" />
         <arg xmlns:ns="http://org.apache.synapse/xsd" evaluator="xml" expression="fn:concat(get-property('serviceStartDate'),'T00:00:00.000Z')" />
         <arg xmlns:ns="http://org.apache.synapse/xsd" evaluator="xml" expression="fn:concat(get-property('serviceEndDate'),'T00:00:00.000Z')" />
         <arg xmlns:ns="http://org.apache.synapse/xsd" evaluator="xml" expression="get-property('invoiceNumber')" />
         <arg xmlns:ns="http://org.apache.synapse/xsd" evaluator="xml" expression="fn:concat(get-property('invoiceDate'),'T00:00:00.000Z')" />
         <arg xmlns:ns="http://org.apache.synapse/xsd" evaluator="xml" expression="fn:concat(get-property('chargeName'),' from ',get-property('serviceStartDate'),' to ',get-property('serviceEndDate'))" />
         <arg xmlns:ns="http://org.apache.synapse/xsd" evaluator="xml" expression="get-property('itemId')" />
      </args>
   </payloadFactory>
   <netsuite.addList configKey="netsuiteConfig">
      <records xmlns:urn="wso2.connector.netsuite">{//urn:records/*}</records>
   </netsuite.addList>
</sequence>

Local Entries

We use these local entries to specify the init element of connectors. As we mentioned earlier, we use aliases instead of plain text to specify passwords. We set the blocking parameter as true to invoke blocking calls to Zuora and NetSuite.


1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<localEntry xmlns="http://ws.apache.org/ns/synapse" key="zuoraSoapConfig">
   <zuorasoap.init>
      <apiUrl>https://www.zuora.com/apps/services/a/77.0</apiUrl>
      <username>johndoe@gmail.com</username>
      <password>{wso2:vault-lookup('zuora.password')}</password>
      <blocking>true</blocking>
   </zuorasoap.init>
</localEntry>


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?xml version="1.0" encoding="UTF-8"?>
<localEntry xmlns="http://ws.apache.org/ns/synapse" key="zuoraRestConfig">
   <zuora.init>
      <apiUrl>https://api.zuora.com/rest</apiUrl>
      <apiVersion>v1</apiVersion>
      <apiAccessKeyId>johndoe@gmail.com</apiAccessKeyId>
      <apiSecretAccessKey>{wso2:vault-lookup('zuora.password')}</apiSecretAccessKey>
      <blocking>true</blocking>
   </zuora.init>
</localEntry>


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<localEntry xmlns="http://ws.apache.org/ns/synapse" key="netsuiteConfig">
   <netsuite.init>
      <apiUrl>https://webservices.na1.netsuite.com/services/NetSuitePort_2014_1</apiUrl>
      <warningAsError>{$ctx:warningAsError}</warningAsError>
      <disableSystemNotesForCustomFields>{$ctx:disableSystemNotesForCustomFields}</disableSystemNotesForCustomFields>
      <ignoreReadOnlyFields>{$ctx:ignoreReadOnlyFields}</ignoreReadOnlyFields>
      <disableMandatoryCustomFieldValidation>{$ctx:disableMandatoryCustomFieldValidation}</disableMandatoryCustomFieldValidation>
      <roleInternalId>{$ctx:roleInternalId}</roleInternalId>
      <partnerId>{$ctx:partnerId}</partnerId>
      <applicationId>{$ctx:applicationId}</applicationId>
      <roleName>{$ctx:roleName}</roleName>
      <password>{wso2:vault-lookup('netsuite.password')}</password>
      <roleExternalId>{$ctx:roleExternalId}</roleExternalId>
      <email>johndoe@wso2.com</email>
      <account>TSTDRV1473412</account>
      <roleType>{$ctx:roleType}</roleType>
      <blocking>true</blocking>
   </netsuite.init>
</localEntry>


Scheduled Task 

Scheduled tasks allow us to run scheduled jobs at given time intervals. Here we specify to inject a message to the zuoraNetsuiteIntegration sequence and use cron-style to schedule the task to run every day. Since it is mandatory to provide a value for the message property, we can provide an empty payload as the value.


1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<task xmlns="http://ws.apache.org/ns/synapse" class="org.apache.synapse.startup.tasks.MessageInjector" group="synapse.simple.quartz" name="invoiceSync">
   <trigger cron="0 0 15 * * ?" />
   <property xmlns:task="http://www.wso2.org/products/wso2commons/tasks" name="injectTo" value="sequence" />
   <property xmlns:task="http://www.wso2.org/products/wso2commons/tasks" name="message">
      <request xmlns="" />
   </property>
   <property xmlns:task="http://www.wso2.org/products/wso2commons/tasks" name="sequenceName" value="zuoraNetsuiteIntegration" />
</task>


Running scenario 

Once the configuration is completed, you need to create a Connector Exporter Project and add the above three connectors. Then create a C-App project and build a CAR file out of that project and deploy it in WSO2 ESB.

No comments:

Post a Comment