We will see how we can add notifications to a service so clients can be notified each time a certain RP is modified. As we did in Chapter 3, Writing Your First Stateful Web Service in 5 Simple Steps and Chapter 6, Resource Properties, our example will be based, for simplicity, on the ServiceResourceHome
resource home.
Our portType will need to extend from a standard WS-Notifications portType called NotificationProducer
, which exposes a Subscribe
operation that consumers can use to subscribe themselves to a particular topic. Since this examples takes an existing resource property and exposes it as a topic, no additional WSDL code is required beyond extending the NotificationProducer
portType.
First of all, we need to declare the WS-Notifications namespace, and import its WSDL file.
<?xml version="1.0" encoding="UTF-8"?> <definitions name="MathService" targetNamespace="http://www.globus.org/namespaces/examples/core/MathService_instance_notif" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://www.globus.org/namespaces/examples/core/MathService_instance_notif" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:wsrp="http://docs.oasis-open.org/wsrf/2004/06/wsrf-WS-ResourceProperties-1.2-draft-01.xsd" xmlns:wsrpw="http://docs.oasis-open.org/wsrf/2004/06/wsrf-WS-ResourceProperties-1.2-draft-01.wsdl" xmlns:wsntw="http://docs.oasis-open.org/wsn/2004/06/wsn-WS-BaseNotification-1.2-draft-01.wsdl" xmlns:wsdlpp="http://www.globus.org/namespaces/2004/10/WSDLPreprocessor" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <wsdl:import namespace= "http://docs.oasis-open.org/wsrf/2004/06/wsrf-WS-ResourceProperties-1.2-draft-01.wsdl" location="../../wsrf/properties/WS-ResourceProperties.wsdl" /> <wsdl:import namespace= "http://docs.oasis-open.org/wsn/2004/06/wsn-WS-BaseNotification-1.2-draft-01.wsdl" location="../../wsrf/notification/WS-BaseN.wsdl"/> <!-- ... --> </definitions>
Then, we need to extend from the NotificationProducer
portType.
<portType name="MathPortType"
wsdlpp:extends="wsrpw:GetResourceProperty wsntw:NotificationProducer"
wsrp:ResourceProperties="tns:MathResourceProperties">
<!-- <operation>s -->
</portType>
This is part of file
|
Finally, since these modifications create a new interface, we need to map the new WSDL namespaces to Java packages.
http\://www.globus.org/namespaces/examples/core/MathService_instance_notif= org.globus.examples.stubs.MathService_instance_notif http\://www.globus.org/namespaces/examples/core/MathService_instance_notif/bindings= org.globus.examples.stubs.MathService_instance_notif.bindings http\://www.globus.org/namespaces/examples/core/MathService_instance_notif/service= org.globus.examples.stubs.MathService_instance_notif.service
These three lines must be present in
|
In all the previous chapters, we used a special Globus-supplied class called ReflectionResourceProperty
to implement our RPs. This class had a number of benefits, described in ReflectionResourceProperty, the main one being that it greatly simplified our implementation since we could use the RPs as if they were normal Java variables.
In this chapter, however, we will use a different Globus-supplied class to represent our RPs: SimpleResourceProperty
. Although we could also use ReflectionResourceProperty
, this is a good chance to take a look at SimpleResourceProperty
. Although this class makes the implementation a little bit more complicated, it shouldn't be too hard to understand now that we know what the resource property document is.
Now is a good moment to review Section 6.1, “A closer look at resource properties”. |
First of all, remember how in all the previous examples, our RPs were implemented simply as two attributes in the resource class:
private int value; private String lastOp;
Now, this will be replaced by the following:
/* Resource properties */ private ResourceProperty valueRP; private ResourceProperty lastOpRP;
|
Furthermore, we are not required to implement get/set methods for the RPs, as we did when using ReflectionResourceProperty
. However, as will be explained later on, it will nonetheless be convenient to do so, as it will make working with the RPs easier, specially if we split our implementation into a service, a resource, and a resource home.
Next, we need to initialize the resource properties. In our example, this is done in the constructor:
public MathService() throws RemoteException { this.propSet = new SimpleResourcePropertySet( MathQNames.RESOURCE_PROPERTIES); try { valueRP = new SimpleResourceProperty(MathQNames.RP_VALUE); valueRP.add(new Integer(0)); lastOpRP = new SimpleResourceProperty(MathQNames.RP_LASTOP); lastOpRP.add("NONE"); } catch (Exception e) { throw new RuntimeException(e.getMessage()); } this.propSet.add(valueRP); this.propSet.add(lastOpRP); }
Now that our resource properties are implemented using the SimpleResourceProperty
class, publishing those RPs as topics is very simple. We will use a Globus-supplied class called ResourcePropertyTopic
which is both a resource property and a topic (more precisely, it implements both the ResourceProperty
and Topic interfaces). As we will see, the only thing we need to to is create new ResourcePropertyTopic
objects and "wrap them around" our SimpleResourceProperty
objects. Then, the ResourcePropertyTopic
objects are added both to the resource's list of RPs (the RP set) and the list of topics.
First of all, our resource class must implement the TopicListAccessor
interface, which requires that we implement a getTopicList
method returning a TopicList
. A TopicList
attribute must therefore be added to our resource class to keep track of all the topics published by our resource.
import org.globus.wsrf.TopicListAccessor; public class MathService implements Resource, ResourceProperties, TopicListAccessor { private TopicList topicList; // ... /* Required by interface TopicListAccessor */ public TopicList getTopicList() { return topicList; } }
Next, we initialize the topic list, create the ResourcePropertyTopic
objects, and add the to the RP set and the topic list:
public MathService() throws RemoteException { /* Create RP set */ this.propSet = new SimpleResourcePropertySet( MathQNames.RESOURCE_PROPERTIES); /* Initialize the RP's */ try { valueRP = new SimpleResourceProperty(MathQNames.RP_VALUE); valueRP.add(new Integer(0)); lastOpRP = new SimpleResourceProperty(MathQNames.RP_LASTOP); lastOpRP.add("NONE"); } catch (Exception e) { throw new RuntimeException(e.getMessage()); } this.topicList = new SimpleTopicList(this); valueRP = new ResourcePropertyTopic(valueRP); ((ResourcePropertyTopic) valueRP).setSendOldValue(true); lastOpRP = new ResourcePropertyTopic(lastOpRP); ((ResourcePropertyTopic) lastOpRP).setSendOldValue(true); this.topicList.addTopic((Topic) valueRP); this.topicList.addTopic((Topic) lastOpRP); this.propSet.add(valueRP); this.propSet.add(lastOpRP); }
The code shown above is part of
|
We need to modify the implementation of the add
and subtract
methods because we are now using SimpleResourceProperty
objects to represent our RPs. However, take into account that none of the following changes are directly related to the fact that we're using notifications. When using ResourcePropertyTopic
s, the notification is sent out automatically whenever we modify the value of an RP. We do not need to add any code to trigger the notification.
As mentioned earlier, using SimpleResourceProperty
objects is going to make our interaction with the RP's a bit hairier. When using ReflectionResourceProperty
, our add
method was as simple as this:
public AddResponse add(int a) throws RemoteException { value += a; lastOp = "ADDITION"; return new AddResponse(); }
Now, however, the code will look something like this:
public AddResponse add(int a) throws RemoteException { Integer value = (Integer) valueRP.get(0); value = new Integer(value.intValue()+a); valueRP.set(0, value); lastOpRP.set(0,"ADDITION"); return new AddResponse(); }
The code shown above is part of
|
Accessing the RPs if we split up the implementation | |
---|---|
Remember that, in this example, we are using the
However, this will not be so if we split up the implementation as seen in Chapter 4, Singleton resources and Chapter 5, Multiple resources. In these cases, we will need to use our resource's public AddResponse add(int a) throws RemoteException { MathResource mathResource = getResource(); ResourceProperty valueRP = mathResource.propSet.get(MathQNames.RP_VALUE); Integer value = (Integer) valueRP.get(0); value = new Integer(value.intValue()+a); valueRP.set(0, value); ResourceProperty lastOpRP = mathResource.propSet.get(MathQNames.RP_LASTOP); lastOpRP.set(0,"ADDITION"); return new AddResponse(); } Of course, this doesn't make things any nicer. This is why it is usually a good idea to include get/set methods for our RPs in the resource implementation, even if they are not required by public int getValue() { Integer value_obj = (Integer) valueRP.get(0); return value_obj.intValue(); } public void setValue(int value) { Integer value_obj = new Integer(value); valueRP.set(0, value_obj); } public String getLastOp() { String lastOp_obj = (String) lastOpRP.get(0); return lastOp_obj; } public void setLastOp(String lastOp) { lastOpRP.set(0, lastOp); } With these get/set methods, our public AddResponse add(int a) throws RemoteException { MathResource mathResource = getResource(); mathResource.setValue(mathResource.getValue() + a); mathResource.setLastOp("ADDITION"); return new AddResponse(); } This, of course, looks much nicer. In fact, it is very similar to the way we were able to implement |
To be able to use the WS-Notifications portTypes we need to modify our WSDD file to make sure that our service relies on the Globus-supplied operation providers for those portTypes.
<?xml version="1.0" encoding="UTF-8"?>
<deployment name="defaultServerConfig"
xmlns="http://xml.apache.org/axis/wsdd/"
xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<service name="examples/core/notifications/MathService" provider="Handler" use="literal" style="document">
<parameter name="className" value="org.globus.examples.services.core.notifications.impl.MathService"/>
<wsdlFile>share/schema/examples/MathService_instance_notif/Math_service.wsdl</wsdlFile>
<parameter name="allowedMethods" value="*"/>
<parameter name="handlerClass" value="org.globus.axis.providers.RPCProvider"/>
<parameter name="scope" value="Application"/>
<parameter name="providers" value="GetRPProvider SubscribeProvider GetCurrentMessageProvider"/>
<parameter name="loadOnStartup" value="true"/>
</service>
</deployment>
This file is
|
Let's build the service:
./globus-build-service.sh notifications
And deploy it:
globus-deploy-gar $EXAMPLES_DIR/org_globus_examples_services_core_notifications.gar
To try out this service, we will need two clients. The first client will be in charge of listening for notifications, and includes a lot of new code. The second client is a very simple client that invokes the add
operation. This will allow us to test if a change in the Value
RP (triggered by the add
operation) is indeed notified to the listener client.
This client is composed of two important parts:
Subscription: This block of code will be in charge of setting up the subscription with the Value
RP (which, remember, is also published as a topic). Once the subscription is set up, this block of code simply loops indefinitely.
Delivery: Once the subscription has been set up, and the main thread of the program is looping indefinitely, the delivery code gets invoked any time a notification arrives at the client. In fact, the listener must implement the NotifyCallback
interface, which requires that we implement a deliver
method that will be in charge of handling incoming notifications.
public class ValueListener implements NotifyCallback {
}
First off, let's take a look at the code that sets up the subscription. This code is inside a method called run
that expects the notification producer's URI as its only parameter.
public void run(String serviceURI) { try { NotificationConsumerManager consumer; consumer = NotificationConsumerManager.getInstance(); consumer.startListening(); EndpointReferenceType consumerEPR = consumer .createNotificationConsumer(this); Subscribe request = new Subscribe(); request.setUseNotify(Boolean.TRUE); request.setConsumerReference(consumerEPR); TopicExpressionType topicExpression = new TopicExpressionType(); topicExpression.setDialect(WSNConstants.SIMPLE_TOPIC_DIALECT); topicExpression.setValue(MathQNames.RP_VALUE); request.setTopicExpression(topicExpression); WSBaseNotificationServiceAddressingLocator notifLocator = new WSBaseNotificationServiceAddressingLocator(); EndpointReferenceType endpoint = new EndpointReferenceType(); endpoint.setAddress(new Address(serviceURI)); NotificationProducer producerPort = notifLocator .getNotificationProducerPort(endpoint); producerPort.subscribe(request); System.out.println("Waiting for notification. Ctrl-C to end."); while (true) { try { Thread.sleep(30000); } catch (Exception e) { System.out.println("Interrupted while sleeping."); } } } catch (Exception e) { e.printStackTrace(); } }
Now, let's take a look at the code that handles incoming notifications. Remember that the delivery
is required by the NotifyCallback
interface. If we do not implement it, our client will be unable to receive notifications.
public void deliver(List topicPath, EndpointReferenceType producer, Object message) { ResourcePropertyValueChangeNotificationElementType notif_elem; ResourcePropertyValueChangeNotificationType notif; notif_elem = (ResourcePropertyValueChangeNotificationElementType) message; notif = notif_elem.getResourcePropertyValueChangeNotification(); if (notif != null) { System.out.println("A notification has been delivered"); System.out.print("Old value: "); System.out.println(notif.getOldValue().get_any()[0].getValue()); System.out.print("New value: "); System.out.println(notif.getNewValue().get_any()[0].getValue()); } }
The code shown above is part of
|
The adding client requires no explanation, as it is identical to the ones seen in previous chapters.
The source code for the adding client is
If you're not sure about how the client works, this might be a good time to review Section 5.9.2, “The adding client”. |
First of all, let's compile the listener client:
javac \ -classpath $CLASSPATH:build/stubs/classes/ \ org/globus/examples/clients/MathService_instance_notif/ValueListener.java
Since we are going to use two clients, you should run the listener in a separate console.
java \
-DGLOBUS_LOCATION=$GLOBUS_LOCATION \
-classpath $CLASSPATH:build/stubs/classes/ \
org/globus/examples/clients/MathService_instance_notif/ValueListener \
http://127.0.0.1:8080/wsrf/services/examples/core/notifications/MathService
Notice how we have to define a property called
|
If all goes well, you should see the following:
Waiting for notification. Ctrl-C to end.
Now, let's compile the adder client:
javac \ -classpath $CLASSPATH:build/stubs/classes/ \ org/globus/examples/clients/MathService_instance_notif/ClientAdd.java
And run it:
java \ -classpath $CLASSPATH:build/stubs/classes/ \ org/globus/examples/clients/MathService_instance_notif/ClientAdd \ http://127.0.0.1:8080/wsrf/services/examples/core/notifications/MathService \ 10
If all goes well, you should see the following:
Value RP: 10 LastOp RP: ADDITION
Now, if you check the console where the listener client is running, you should see the following:
A notification has been delivered Old value:0 New value:10
You can try to run the adder client once more:
Value RP: 20 LastOp RP: ADDITION
And the following will be output by the listener.
A notification has been delivered Old value:10 New value:20