Showing posts with label Savan. Show all posts
Showing posts with label Savan. Show all posts

Tweaking SAVAN : Persistent subscriptions

WS-Eventing defines a protocol for one Web service (called a "subscriber") to register interest (called a "subscription") with another Web service (called an "event source") in receiving messages about events (called "notifications" or "event messages").

The subscriber may manage the subscription by interacting with a Web service (called the "subscription manager") designated by the event source.

Savan is the WS-Eventing implementation for Apache Axis2.

This post is a continuation of my previous post, which demonstrates Savan with an example.

Before going any further with this, please make sure you have gone through it and have the example running.

The sample application included three components.

1. Event source[axis2-savan-event-source] - a stock quote service publishes stock prices iteratively

2. Event sink[axis2-savan-event-sink] - a stock quote consumer subscribes to the stock quote service to receive events

3. [axis2-savan-client] - initiates subscription for the stock quote consumer

By default Savan manages event subscriptions with an in-memory subscriber store.

That is, in case the server running the event source, got restarted, event sink no more will receive any events.

This post takes you through, explaining how to add a custom subscriber store to manage subscriptions in a persistence manner.

Let's get started with the custom subscriber store code.

package org.apache.ws.axis2;

import java.util.Iterator;

import org.apache.axis2.context.ConfigurationContext;
import org.apache.savan.SavanException;
import org.apache.savan.storage.SubscriberStore;
import org.apache.savan.subscribers.Subscriber;
import org.apache.savan.subscribers.SubscriberGroup;

public class CustomSubscriberStore implements SubscriberStore {

public void init(ConfigurationContext arg0) throws SavanException {
// TODO Initialize the storage
}

public void addSubscriberGroup(String arg0) throws SavanException {
// TODO Add subscriber to a subscriber group
}

public void addSubscriberToGroup(String arg0, Subscriber arg1) throws SavanException {
// TODO Add subscriber to a subscriber group
}

public void delete(String arg0) throws SavanException {
// TODO Delete a previously stored subscriber
}

public SubscriberGroup getSubscriberGroup(String arg0) throws SavanException {
// TODO Retrieve a previously stored subscriber group
}

public Subscriber retrieve(String arg0) throws SavanException {
// TODO Retrieve a previously stored subscriber
}

public Iterator retrieveAllSubscriberGroups() throws SavanException {
// TODO Retrieve a previously stored subscriber groups
}

public Iterator retrieveAllSubscribers() throws SavanException {
// TODO Retrieve previously stored subscribers
}

public void store(Subscriber arg0) throws SavanException {
// TODO Store the subscriber in the persistent data store
}

}
Once completed, add the jar containing the above class to [AXIS2_HOME]\lib.

Now we need to make sure, Savan module loads the above class.

That can be configured in savan-config.xml.

You can find savan-config.xml in side [AXIS2_HOME]\repository\modules\savan-SNAPSHOT.mar. [You may extract savan-SNAPSHOT.mar, do the changes to the configuration file and then zip it again as savan-SNAPSHOT.mar]

<subscriberStores>
<subscriberStore>
<key>default</key>
<class>org.apache.savan.storage.DefaultSubscriberStore</class>
</subscriberStore>
<!-- this is your new subscriber store, with the key 'custom' -->
<subscriberStore>
<key>custom
<class>org.apache.ws.axis2.CustomSubscriberStore
</subscriberStore>
</subscriberStores>
Now, we are almost done. Let's configure the event source[axis2-savan-event-source] to use our custom subscriber store.

You need edit services.xml of StockQuoteService and add a new parameter.

<service name="StockQuoteService" scope="application">

<module ref="savan" />
<module ref="addressing" />

<parameter name="ServiceClass" locked="false">org.apache.ws.axis2.StockQuoteService</parameter>
<!-- Set SubscriberStoreKey to the key you set for you custom subscriber store in savan-config.xml -->
<parameter name="SubscriberStoreKey" locked="false">custom</parameter>

</service>
All set - redeploy the event source service and test with the client.

SAVAN: Behind the scene

It's highly recommended that you read my previous post before going any further with this one.

Let me summarize a bit, what we did there.

We deployed a sample application with Axis2/Savan to demonstrate WS-Eventing.

The sample application had three components.

1. Event source[axis2-savan-event-source] - a stock quote service publishes stock prices iteratively

2. Event sink[axis2-savan-event-sink] - a stock quote consumer subscribes to the stock quote service to receive events

3. [axis2-savan-client] - initiates subscription for the stock quote consumer

Let's get started.

If you go through the services.xml of StockQuoteService, you'll find that we expose no operations there.
<!-- src/META-INF/services.xml -->
<service name="StockQuoteService" scope="application">

<module ref="savan" />
<module ref="addressing" />

<parameter name="ServiceClass" locked="false">
org.apache.ws.axis2.StockQuoteService
</parameter>

</service>
See whether following surprises you. Hit http://localhost:8080/axis2/services/ on your browser and observe the operations available under StockQuoteService.

If it's not the services.xml which added those operations, who else could it be?

It's basically the 'Savan' module does that [Remember!!! services.xml engages savan module to the StockQuoteService].

In otherwords engaging savan module will seemlessly add three more operations to your service.

Let's get into more deeper.

Extract the savan-SNAPSHOT.mar file inside [AXIS2_HOME]\repository\modules [Remember!!! you were asked to read my previous post :).]

You'll find module.xml inside META-INF directory. Lets dig into the module.xml.
<module name="savan" class="org.apache.savan.module.SavanModule">

<operation name="SavanInOutOperation" mep="http://www.w3.org/2004/08/wsdl/in-out">
<messageReceiver class="org.apache.savan.messagereceiver.SavanInOutMessageReceiver"/>
<actionMapping>http://schemas.xmlsoap.org/ws/2004/08/eventing/Subscribe</actionMapping>
<actionMapping>http://schemas.xmlsoap.org/ws/2004/08/eventing/Renew</actionMapping>
<actionMapping>http://schemas.xmlsoap.org/ws/2004/08/eventing/GetStatus</actionMapping>
<actionMapping>http://schemas.xmlsoap.org/ws/2004/08/eventing/Unsubscribe</actionMapping>
</operation>

<operation name="atom" mep="http://www.w3.org/2004/08/wsdl/in-out">
<messageReceiver class="org.apache.savan.atom.AtomMessageReceiver"/>
</operation>

<operation name="publish" mep="http://www.w3.org/2004/08/wsdl/in-out">
<messageReceiver class="org.apache.savan.messagereceiver.PublishingMessageReceiver"/>
<actionMapping>http://ws.apache.org/ws/2007/05/eventing-extended/Publish</actionMapping>
</operation>

</module>
Hold on a second. Lets cover bit of ground on Axis2.

One of the fundamental goals of SOAP processing frameworks is to deliver an incoming message to the targeted application.

The process of finding correct target application is known as 'dispatching'.

Let's have a look at the following section extracted from [AXIS2_HOME]\conf\axis2.xml.
<phase name="Dispatch" class="org.apache.axis2.engine.DispatchPhase">
<handler name="RequestURIBasedDispatcher"
class="org.apache.axis2.dispatchers.RequestURIBasedDispatcher"/>
<handler name="SOAPActionBasedDispatcher"
class="org.apache.axis2.dispatchers.SOAPActionBasedDispatcher"/>
<handler name="RequestURIOperationDispatcher"
class="org.apache.axis2.dispatchers.RequestURIOperationDispatcher"/>
<handler name="SOAPMessageBodyBasedDispatcher"
class="org.apache.axis2.dispatchers.SOAPMessageBodyBasedDispatcher"/>
<handler name="HTTPLocationBasedDispatcher"
class="org.apache.axis2.dispatchers.HTTPLocationBasedDispatcher"/>
<handler name="GenericProviderDispatcher"
class="org.apache.axis2.jaxws.dispatchers.GenericProviderDispatcher"/>
<handler name="MustUnderstandValidationDispatcher"
class="org.apache.axis2.jaxws.dispatchers.MustUnderstandValidationDispatcher"/>
</phase>
You can see a set of handlers being configured to execute under the 'Dispatch' phase.

All the dispatchers first try to find the service with the information it has. They do this if the dispatchers invoked before it had not found a service.

If the service is already found, the findService method of the dispatcher will just pass through. Then it tries to find an operation within the service, if a service is found by then.

Again, the dispatcher will do this only if other dispatchers invoked prior to it had not found an operation. Likewise, all the dispatchers put a collaborative effort in doing this. This will enable one dispatcher to find a service and another dipatcher to later find the operation.

If you'd like to read more on how Axis2 finds the service with the corresponding operation, read this.

Lets configure the TCP monitor to track subscription message. This explains how you can configure TCP monitor.

Once configured and run the Client, you'll see the following SOAP request, for the subscription.
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header xmlns:wsa="http://www.w3.org/2005/08/addressing">
<wsa:To>http://localhost:8080/axis2/services/StockQuoteService</wsa:To>
<wsa:MessageID>urn:uuid:F873BC9C2A4F33B6961225131910519</wsa:MessageID>
<wsa:Action>http://schemas.xmlsoap.org/ws/2004/08/eventing/Subscribe</wsa:Action>
</soapenv:Header>
<soapenv:Body>
<wse:Subscribe xmlns:wse="http://schemas.xmlsoap.org/ws/2004/08/eventing">
<wse:Delivery>
<wse:NotifyTo>
<wsa:Address xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">
http://localhost:8080/axis2/services/StockQuoteConsumer
</wsa:Address>
</wse:NotifyTo>
</wse:Delivery>
</wse:Subscribe>
</soapenv:Body>
</soapenv:Envelope>
Once you look at the SOAP message and the extract from the axis2.xml which displays the set of available handlers for dispatching, you will not find a matching handler.

In this case, dispatching has to happen through WS-Addressing information.

So, once we engaged the 'addressing' module to the service, AddressingBasedDispatcher will be available for it during the "Addressing" phase.

With this, Axis2 will get to know, the message is destined to http://localhost:8080/axis2/services/StockQuoteService.

As I mentioned previously in this post, engaging savan module it self with the StockQuoteService has added three more operations to the service. Those three operations are defined in the savan module.xml - shown above.

You'll see that http://schemas.xmlsoap.org/ws/2004/08/eventing/Subscribe is mapped to the operation "SavanInOutOperation" [in module.xml] - in that case org.apache.savan.messagereceiver.SavanInOutMessageReceiver will be invoked.

By going through the above, you will figure out that all Subscribe/Renew/GetStatus and Unsubscribe operations are handled by the org.apache.savan.messagereceiver.SavanInOutMessageReceiver. In other words, that is exactly what savan module does.

Once a request to Subscribe being received at the 'savan' module, it will create a SubscriberStore for the 'publishing' [event source] service and will add the required information regarding the 'receving'[event sink] service to it.

If you look at the following code from [axis2-savan-event-source]\org.apache.ws.axis2\StockQuoteService, you will notice that Line:1 will get the SubcriberStore for the corresponding service, in this case, for the StockQuoteService, and if it's not null, then will publish events.
store = CommonUtil.getSubscriberStore(serviceContext.getAxisService());

if (store != null)  {
data = getNextPublicationData();
publicationClient = new PublicationClient(serviceContext.getConfigurationContext());
publicationClient.sendPublication(data, serviceContext.getAxisService(),null);
}

WS-Eventing with SAVAN

WS-Eventing defines a protocol for one Web service (called a "subscriber") to register interest (called a "subscription") with another Web service (called an "event source") in receiving messages about events (called "notifications" or "event messages").

The subscriber may manage the subscription by interacting with a Web service (called the "subscription manager") designated by the event source.

Savan is the WS-Eventing implementation for Apache Axis2.

This post guides you through WS-Eventing with an example in Savan/Axis2.

-First download Axis2 1.4.1 and extract it to a local folder [AXIS2_HOME].

-Start Axis2 simple server with [AXIS2_HOME]\bin\axis2server.bat and make sure http://localhost:8080/axis2/services/ lists all avaialble services.

The sample application includes three components.

1. Event source[axis2-savan-event-source] - a stock quote service publishes stock prices iteratively

2. Event sink[axis2-savan-event-sink] - a stock quote consumer subsribes to the stock quote service to receive events

3. [axis2-savan-client] - initiates subscription for the stock quote consumer

You can download Eclipse projects for above three components from here and import those into your Eclipse workspace, say [SAVAN].

Copy all the jar files from [AXIS2_HOME]\lib to [SAVAN]\lib.

Copy [SAVAN]\lib\savan-SNAPSHOT.mar to [AXIS2_HOME]\repository\modules.

Copy [SAVAN]\lib\savan-core-SNAPSHOT.jar to [AXIS2_HOME]\lib.

Share the folder [SAVAN]\lib and map it to the network drive "W".

All set now, refresh your workspace and it should build with no compile errors.

Lets start with, Event source[axis2-savan-event-source].
// org.apache.ws.axis2/StockQuoteService
public OMElement getNextPublicationData() {

OMFactory factory = null;
OMNamespace namespace = null;
OMElement stockElement = null;
OMElement data = null;

// <ns1:publish xmlns:ns1="http://stocktrader.consumer">
//       <ns1:Stock xmlns:ns1="http://stocktrader.consumer">12345
// </ns1:publish>

factory = OMAbstractFactory.getOMFactory();
namespace = factory.createOMNamespace(NAMESPACE, "ns1");
stockElement = factory.createOMElement(STOCK, namespace);

int value = r.nextInt();
stockElement.setText(Integer.toString(value));

data = factory.createOMElement("publish", namespace);
data.addChild(stockElement);

return data;
}

The above code will generate the data to publish - where the subscribers can recieve. At the same time pay your attention to line:20.

Here the subscribed services should have a method called 'publish' [that is, they should match the name used here].
store = CommonUtil.getSubscriberStore(serviceContext.getAxisService());

if (store != null)  {
data = getNextPublicationData();
publicationClient = new PublicationClient(serviceContext.getConfigurationContext());
publicationClient.sendPublication(data, serviceContext.getAxisService(),null);
}
Line:1 will get the SubcriberStore for the corresponding service, in this case, for the StockQuoteService. Savan, module will create the SubcriberStore for the given service upon the first subscription request.

Following, shows the services.xml for StockQuoteService, where you need to engage "addressing" and "savan" modules.
<!-- src/META-INF/services.xml -->
<service name="StockQuoteService" scope="application">

<module ref="savan" />
<module ref="addressing" />

<parameter name="ServiceClass" locked="false">
org.apache.ws.axis2.StockQuoteService
</parameter>

</service>
Now lets deploy the StockQuoteService.

Right click the project axis2-savan-event-source, select 'Export' --> Select 'General/Archive File'. --> Select settings as in the image below.

Lets, move to the Event sink[axis2-savan-event-sink] or the StockQuoteConsumer.

This service has a single method, which simply prints the notifications received from the StockQuoteService.
public void publish(OMElement param) throws Exception {
System.out.println(param);
}
services.xml for the StockQuoteConsumer service will look like, following.
<service name="StockQuoteConsumer">

<parameter name="ServiceClass" locked="false">org.apache.ws.axis2.StockQuoteConsumerImpl</parameter>

<operation name="publish" mep="http://www.w3.org/2004/08/wsdl/in-only">
<messageReceiver class="org.apache.axis2.receivers.RawXMLINOnlyMessageReceiver" />
</operation>

</service>
Note down here that we do not engage 'savan' module with the StockQuoteConsumer.

Now deploy the StockQuoteConsumer service in the same way as we did in StockQuoteService.

Let's focus on the Client code.
final static String SUBSCRIBER_ID = "wso2-svan-subscriber";
final static String EVENT_SINK_EPR = "http://localhost:8080/axis2/services/StockQuoteConsumer";
final static String EVENT_SOURCE_EPR = "http://localhost:8080/axis2/services/StockQuoteService";

ServiceClient client = null;
Options options = null;
ConfigurationContext confContext = null;
EventingClientBean bean = null;
EventingClient eventingClient = null;


confContext = ConfigurationContextFactory.createConfigurationContextFromFileSystem("repo","repo/conf/client.axis2.xml");
client = new ServiceClient(confContext, null);
options = new Options();
options.setTo(new EndpointReference(EVENT_SOURCE_EPR));
client.setOptions(options);
client.engageModule("addressing");  

bean = new EventingClientBean();

// indicates where to send notifications
bean.setDeliveryEPR(new EndpointReference(EVENT_SINK_EPR));

// where to send a SubscriptionEnd message 
// bean.setEndToEPR(new EndpointReference(""));

// indicates the event sink would prefer to have the subscription expire
// bean.setExpirationTime(new Date(""));
// bean.setExpirationDuration(new Duration("10"));

// bean.setFilter(filter)'
// bean.setFilterDialect(filterDialect);

eventingClient = new EventingClient(client);
eventingClient.subscribe(bean, SUBSCRIBER_ID);
All set, start the Axis2 simple server first and then simply run the Client.

You can see the out put on Axis2 simple server console.