Thursday, August 4, 2016

Guaranteed delivery of emails using WSO2 ESB and WSO2 MB

WSO2 ESB provides guaranteed message delivery through it's message store and message processor. Here, I'm going to talk about a usecase where how to send an email through its recipient even the mail server is unavailable at the time the email is sent. Let's start with the setup.

1. Download a distribution of the WSO2 ESB from here. Extract the zip file. The folder created will be referred as ESB_HOME. Download the message broker. WSO2 MB is used as the message broker in this document, therefore, download the WSO2 MB from the official site, extract the zip file. This folder will be referred as MB_HOME.

2. As we are going to mailto transport to send mails, uncomment the mailto TransportSender and TransportReceiver from axis2.xml in the ESB server
ie. ESB_HOME/conf/axis2.xml

<transportSender name="mailto" class="org.apache.axis2.transport.mail.MailTransportSender">
       <parameter name="mail.smtp.host">smtp.gmail.com</parameter>
       <parameter name="mail.smtp.port">587</parameter>
       <parameter name="mail.smtp.starttls.enable">true</parameter>
       <parameter name="mail.smtp.auth">true</parameter>
       <parameter name="mail.smtp.user">email.user</parameter>
       <parameter name="mail.smtp.password">email.user.password</parameter>
       <parameter name="mail.smtp.from">email.user@gmail.com</parameter>
</transportSender>

<transportReceiver name="mailto" class="org.apache.axis2.transport.mail.MailTransportListener">
</transportReceiver>

You have to give the senders credentials and the mail servers details under transportSender section

3. Copy the following JAR files from the <MB_HOME>/clent-lib directory to the <ESB_HOME>/repository/components/lib directory
  • andes-client-3.0.1.jar
  • geronimo-jms_1.1_spec-1.1.0.wso2v1.jar

4. Open <ESB_HOME>/repository/conf/jndi.properties file and make a reference to the running Message Broker as specified below:
  • Use carbon as the virtual host.
  • Define a queue named JMSMS
  • Comment out the topic, since it is not required in this scenario. However, in order to avoid getting the javax.naming.NameNotFoundException:TopicConnectionFactory exception during server startup, make a reference to the Message Broker from the TopicConnectionFactory as well.

5. Now the ESB server is configured. Then we have to configure MB server. Uncomment the jms transport receiver and sender for WSO2 MB in axis2.xml

<transportReceiver name="jms" class="org.apache.axis2.transport.jms.JMSListener">
     <parameter name="myTopicConnectionFactory" locked="false">
        <parameter name="java.naming.factory.initial" locked="false">org.wso2.andes.jndi.PropertiesFileInitialContextFactory</parameter>
         <parameter name="java.naming.provider.url" locked="false">repository/conf/jndi.properties</parameter>
         <parameter name="transport.jms.ConnectionFactoryJNDIName" locked="false">TopicConnectionFactory</parameter>
         <parameter name="transport.jms.ConnectionFactoryType" locked="false">topic</parameter>
     </parameter>
     <parameter name="myQueueConnectionFactory" locked="false">
         <parameter name="java.naming.factory.initial" locked="false">org.wso2.andes.jndi.PropertiesFileInitialContextFactory</parameter>
         <parameter name="java.naming.provider.url" locked="false">repository/conf/jndi.properties</parameter>
         <parameter name="transport.jms.ConnectionFactoryJNDIName" locked="false">QueueConnectionFactory</parameter>
        <parameter name="transport.jms.ConnectionFactoryType" locked="false">queue</parameter>
     </parameter>
     <parameter name="default" locked="false">
         <parameter name="java.naming.factory.initial" locked="false">org.wso2.andes.jndi.PropertiesFileInitialContextFactory</parameter>
         <parameter name="java.naming.provider.url" locked="false">repository/conf/jndi.properties</parameter>
         <parameter name="transport.jms.ConnectionFactoryJNDIName" locked="false">QueueConnectionFactory</parameter>
         <parameter name="transport.jms.ConnectionFactoryType" locked="false">queue</parameter>
     </parameter>
 </transportReceiver>

<transportSender name="jms" class="org.apache.axis2.transport.jms.JMSSender"/>

6. As we are running two carbon servers on the same machine, apply a port offset in the  <MB_HOME>/repository/conf/carbon.xml  file by changing the offset value to 1. For example,
<Ports>
  <!-- Ports offset. This entry will set the value of the ports defined below to
the define value + Offset.
e.g. Offset=2 and HTTPS port=9443 will set the effective HTTPS port to 9445
-->
  <Offset>1</Offset>

7. Start the ESB server and MB server

8. On ESB server Create the failover message store
In this example usecase an in-memory message store is used to create the failover message store.

<messageStore name="Failover"/>

failover.png
9. Create the original message store as illustrated in the diagram. I have used JMS message store for this purpose.  When creating the original message store, you need to enable guaranteed delivery on the producer side. A sample config would be as below,

<messageStore name="Original" class="org.apache.synapse.message.store.impl.jms.JmsStore" xmlns="http://ws.apache.org/ns/synapse">
  <parameter name="java.naming.factory.initial">org.wso2.andes.jndi.PropertiesFileInitialContextFactory</parameter>
  <parameter name="java.naming.provider.url">repository/conf/jndi.properties</parameter>
  <parameter name="store.jms.JMSSpecVersion">1.1</parameter>
  <parameter name="store.producer.guaranteed.delivery.enable">true</parameter>
  <parameter name="store.failover.message.store.name">Failover</parameter>
</messageStore>

original_message_store.png
10. Create a proxy service to send the email to designation. This proxy is treated as the backend service which calls the mail server and delivers the emails.
<proxy xmlns="http://ws.apache.org/ns/synapse"
      name="MailToProxy"
      transports="https,http"
      statistics="disable"
      trace="disable"
      startOnLoad="true">
  <target>
     <inSequence>
        <property name="senderAddress" value="chanika1118@gmail.com"/>
        <log level="full">
           <property name="Sender Address" expression="get-property('senderAddress')"/>
        </log>
        <property name="FORCE_SC_ACCEPTED" value="true" scope="axis2"/>
        <property name="OUT_ONLY" value="true"/>
        <property name="Subject" value="This is a test" scope="transport"/>
        <property name="messageType" value="text/plain" scope="axis2-client"/>
        <script language="js">mc.setPayloadXML(&lt;ns:text xmlns:ns="http://ws.apache.org/commons/ns/payload"&gt;Plain text received!&lt;/ns:text&gt;);</script>
        <header name="To"
                expression="fn:concat('mailto:', get-property('senderAddress'))"/>
        <log level="full">
           <property name="message" value="Response message"/>
           <property name="Sender Address" expression="get-property('senderAddress')"/>
        </log>
        <send/>
     </inSequence>
     <outSequence>
        <send/>
     </outSequence>
     <faultSequence>
        <property name="FORCE_SC_ACCEPTED" value="true" scope="axis2"/>
        <property name="OUT_ONLY" value="true"/>
        <log level="full"/>
        <store messageStore="Failover"/>
     </faultSequence>
  </target>
  <description/>
</proxy>

As you can see, if there is an error occurred when sending the email, it goes to the fault sequence and within the fault sequence it calls the ‘Failover’ message store in order to make sure that the message will be delivered when the connectivity is back.

11. Add Endpoint to call the proxy service

<endpoint xmlns="http://ws.apache.org/ns/synapse" name="MailDeliveryService">
  <address uri="http://localhost:8280/services/MailToProxy">
     <suspendOnFailure>
        <progressionFactor>1.0</progressionFactor>
     </suspendOnFailure>
     <markForSuspension>
        <retriesBeforeSuspension>0</retriesBeforeSuspension>
        <retryDelay>0</retryDelay>
     </markForSuspension>
  </address>
</endpoint>

endpoint.png
12. Add a proxy service to invoke the mail delivery endpoint.

<proxy xmlns="http://ws.apache.org/ns/synapse"
      name="SampleProxy"
      transports="https,http"
      statistics="disable"
      trace="disable"
      startOnLoad="true">
  <target>
     <inSequence>
        <send>
           <endpoint key="MailDeliveryService"/>
        </send>
     </inSequence>
  </target>
  <description/>
</proxy>

13. Add Scheduled Message Forwarding Processor to forward the request to the defined endpoint.

<messageProcessor name="ForwardMessageProcessor" class="org.apache.synapse.message.processor.impl.forwarder.ScheduledMessageForwardingProcessor" targetEndpoint="MailDeliveryService" messageStore="Original" xmlns="http://ws.apache.org/ns/synapse">
  <parameter name="interval">1000</parameter>
  <parameter name="client.retry.interval">1000</parameter>
  <parameter name="max.delivery.attempts">4</parameter>
  <parameter name="is.active">true</parameter>
  <parameter name="max.delivery.drop">Disabled</parameter>
  <parameter name="member.count">1</parameter>
</messageProcessor>

forward.png
14. Add Scheduled Failover Message Forwarding Processor to send messages from the failover store to the original store when it is available in the failover scenario. In there you have to define the source message store (which is failover store in our scenario) and the target message store (which is original message store in our scenario)
<messageProcessor name="FailoverMessageProcessor" class="org.apache.synapse.message.processor.impl.failover.FailoverScheduledMessageForwardingProcessor" messageStore="Failover" xmlns="http://ws.apache.org/ns/synapse">
  <parameter name="interval">1000</parameter>
  <parameter name="client.retry.interval">60000</parameter>
  <parameter name="max.delivery.attempts">1000</parameter>
  <parameter name="is.active">true</parameter>
  <parameter name="max.delivery.drop">Disabled</parameter>
  <parameter name="member.count">1</parameter>
  <parameter name="message.target.store.name">Orginal</parameter>
</messageProcessor>

failoverprocessor.png
Now everything is setup. It is time to test the scenario. You can invoke the SampleProxy and see how the mails are received to the recipient defined in the proxy service. 
The failover scenario can be tested by dropping the connection between the ESB server and the mail server or shutdown the mail server. Then you can send some messages to the ESB SampleProxy service. If you check the logs you will see that the failover message processor periodically sends messages to the original message store and eventually tries to send messages to the mail server continuously through the scheduled message forwarding processor. Moreover, the emails are not sent to the receiver address since there is a connectivity issue. The message count of the failover message store is changing over time with the number of messages sent to the proxy service and the number of messages pulled out from the failover message store in order to send to the original message store.


Once you enable connectivity between the ESB server and the mail server, the messages pulled out from the failover message store are sent to the recipients. 

No comments:

Post a Comment