Sunday, September 25, 2016

How to Configure Transport Layer Security (TLS) in WSO2 ESB

The SaaS applications like Salesforce, Zuora and Stripe announced that they would begin to disable the TLS 1.0/TLS 1.1 encryption protocols in a phased approach.

If you use Java 7, when using the Salesforce Connector with WSO2 ESB you may receive the following error when trying to connect to Salesforce API.

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:sf="urn:fault.partner.soap.sforce.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <soapenv:Body>
      <soapenv:Fault>
         <faultcode>sf:UNSUPPORTED_CLIENT</faultcode>
         <faultstring>UNSUPPORTED_CLIENT: TLS 1.0 has been disabled in this organization. Please use TLS 1.1 or higher when connecting to Salesforce using https.</faultstring>
         <detail>
            <sf:UnexpectedErrorFault xsi:type="sf:UnexpectedErrorFault">
               <sf:exceptionCode>UNSUPPORTED_CLIENT</sf:exceptionCode>
               <sf:exceptionMessage>TLS 1.0 has been disabled in this organization. Please use TLS 1.1 or higher when connecting to Salesforce using https.</sf:exceptionMessage>
               <sf:upgradeURL>https://cs27.salesforce.com/secur/weakhttps.jsp?l=1</sf:upgradeURL>
               <sf:upgradeMessage>Stronger security is required</sf:upgradeMessage>
            </sf:UnexpectedErrorFault>
         </detail>
      </soapenv:Fault>
   </soapenv:Body>
</soapenv:Envelope>

You are getting this error because TLS v1.0 is enabled by default in Java 7. Let's see how to configure WSO2 ESB to TLSv1.1/ 1.2 being used by the Salesforce Connector.

Open the <ESB_HOME>/repository/conf/axis2/axis2.xml and add the <parameter name="HttpsProtocols">TLSv1.1,TLSv1.2</parameter>entry inside the PassThroughHttpSSLListener and PassThroughHttpSSLSender elements.

<transportReceiver name="https" class="org.apache.synapse.transport.passthru.PassThroughHttpSSLListener">
    .....
    <parameter name="HttpsProtocols">TLSv1.1,TLSv1.2</parameter>
    .....
</transportReceiver>


<transportSender name="https" class="org.apache.synapse.transport.passthru.PassThroughHttpSSLSender">
    .....
    <parameter name="HttpsProtocols">TLSv1.1,TLSv1.2</parameter>
    .....
</transportSender>

To make sure that the configurations are all set correctly, build the TestSSLServer.jar from GitHub and run it.

$ java -jar TestSSLServer.jar <server_name_or_ip> <port>

e.g.:
$ java -jar TestSSLServer.jar localhost 8243

For Java 8 TLSv1.2 is the default, so if you use Java 8, you don't need to configure the above parameter.

References

  1. https://docs.wso2.com/display/ESB490/Configuring+Transport+Level+Security
  2. https://blogs.oracle.com/java-platform-group/entry/diagnosing_tls_ssl_and_https
  3. https://help.salesforce.com/apex/HTViewSolution?id=000221207
  4. http://community.zuora.com/t5/Zuora-Announcements/Action-Required-Zuora-is-Disabling-TLS-1-0/ba-p/2177
  5. https://stripe.com/blog/upgrading-tls
  6. https://blogs.oracle.com/java-platform-group/entry/java_8_will_use_tls
  7. http://www.bolet.org/TestSSLServer/

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.

WSO2 NetSuite Connector

In this blog post, I am going to explain how to configure the WSO2 NetSuite Connector.

NetSuite is cloud-based Enterprise Resource Planning (ERP) solution. It provides powerful financial/accounting capabilities, which gives the real-time visibility to make better, faster decisions. NetSuite exposes its functionality via SuiteTalk web services to makes easy for customers and developers to integrate NetSuite with a variety of applications.

WSO2 NetSuite connector allows you to interact with the functionality and data of the NetSuite from the WSO2 ESB message flow and makes the integrations faster. This connector uses authentication with the request-level credentials, which are sent in the SOAP header. When you use this approach, the request-level preferences override any system-wide preferences that might have been set.

These sections provide step-by-step instructions on how to set up your web services environment in NetSuite and start using the connector to interface with SuiteTalk.

Requirements 

  • WSO2 ESB
  • Netsuite connector
  • Netsuite account


Enable the NetSuite Web Services Feature

  • Go to setup > company > enable features
  • Select the SuiteCloud tab.
  • Check the Web Services check box.
  • Select Save.



Get the account Id

  • Go to Setup > Integration > Web Services Preferences
  • Copy the Account ID



If you use SuiteTalk API version 2015.2 in which NetSuite introduced a new concept called "Integration Record." So if you upgrade to the 2015.2 or a later endpoint, all web services requests must be associated with an integration record. That is, each request must include the application ID. If you uses the 2015.1 endpoint or earlier, an application ID is optional.

To manually create an integration record

  • Go to Setup > Integration > Manage Integrations > New.
  • Fill the required fields. Click Save.



The system saves the new record and updates the page to show the record’s application ID.


Setting the Internal ID Preference


You can configure NetSuite to display internal ID values in the UI. This behavior can be useful during development of your web services integration. The display of internal IDs lets you verify that the values submitted in web services requests match the intended records.
  • Go to Home > Set Preferences. The General subtab is displayed by default.
  • In the Defaults section, click Show Internal IDs.

Using the Web Services Usage Log


Web Services Usage Log lists web services calls. The request and response are captured in NetSuite under: Setup > Integration > Web Services Usage Log


Setup the WSO2 ESB with NetSuite Connector


  • Download the ESB from here and start the server.
  • Download the NetSuite connector from WSO2 Store.
  • Add and enable the connector via ESB Management Console.
  • Create Proxy service to search customer info from NetSuite and invoke the proxy with the following request.
Proxy Service

 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
<?xml version="1.0" encoding="UTF-8"?>
<proxy xmlns="http://ws.apache.org/ns/synapse"
       name="searchCustomer"
       startOnLoad="true"
       statistics="disable"
       trace="disable"
       transports="https,http">
   <target>
      <inSequence>
         <property xmlns:ns="wso2.connector.netsuite"
                   expression="//ns:apiUrl/text()"
                   name="apiUrl"/>
         <property xmlns:ns="wso2.connector.netsuite"
                   expression="//ns:applicationId/text()"
                   name="applicationId"/>
         <property xmlns:ns="wso2.connector.netsuite"
                   expression="//ns:email/text()"
                   name="email"/>
         <property xmlns:ns="wso2.connector.netsuite"
                   expression="//ns:password/text()"
                   name="password"/>
         <property xmlns:ns="wso2.connector.netsuite"
                   expression="//ns:account/text()"
                   name="account"/>
         <property xmlns:ns="wso2.connector.netsuite"
                   expression="//ns:searchRecord/*"
                   name="searchRecord"/>
         <netsuite.init>
            <apiUrl>{$ctx:apiUrl}</apiUrl>
            <applicationId>{$ctx:applicationId}</applicationId>
            <password>{$ctx:password}</password>
            <email>{$ctx:email}</email>
            <account>{$ctx:account}</account>
         </netsuite.init>
         <netsuite.search>
            <searchRecord>{$ctx:searchRecord}</searchRecord>
         </netsuite.search>
         <respond/>
      </inSequence>
      <outSequence>
         <send/>
      </outSequence>
   </target>
   <description/>
</proxy>

Request

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="wso2.connector.netsuite">
   <soapenv:Header />
   <soapenv:Body>
      <urn:apiUrl>https://webservices.na1.netsuite.com/services/NetSuitePort_2015_2</urn:apiUrl>
      <urn:applicationId>32XXX75-CXXE-47X7-BX7-A3EXXXX9D</urn:applicationId>
      <urn:email>johndoe@abc.com</urn:email>
      <urn:password>john4doe</urn:password>
      <urn:account>TSTXXXXXX12</urn:account>
      <urn:searchRecord>
         <platformMsgs:searchRecord xmlns:platformMsgs="urn:messages_2015_2.platform.webservices.netsuite.com" xmlns:ns1="urn:relationships_2015_2.lists.webservices.netsuite.com" xmlns:platformCore="urn:core_2015_2.platform.webservices.netsuite.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ns1:CustomerSearch">
            <basic>
               <email operator="contains" xsi:type="platformCore:SearchStringField">                  <searchValue>john@smith.com</searchValue>
               </email>
            </basic>
         </platformMsgs:searchRecord>
      </urn:searchRecord>
   </soapenv:Body>
</soapenv:Envelope>