8.4. Notifying changes in a resource property

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.

8.4.1. The WSDL file

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>
[Note]

This is part of file $EXAMPLES_DIR/schema/examples/MathService_instance_notif/Math.wsdl.

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
[Note]

These three lines must be present in $EXAMPLES_DIR/namespace2package.mappings.

8.4.2. The resource implementation

SimpleResourceProperty

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.

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;
[Note]

ResourceProperty is a Globus-supplied interface that all resource properties must implement. Both ReflectionResourceProperty and SimpleResourceProperty, for example, implement it.

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); 1

	try {
		valueRP = new SimpleResourceProperty(MathQNames.RP_VALUE); 2
		valueRP.add(new Integer(0)); 3
				
		4
		lastOpRP = new SimpleResourceProperty(MathQNames.RP_LASTOP);
		lastOpRP.add("NONE");
	} catch (Exception e) {
		throw new RuntimeException(e.getMessage());
	}

	5
	this.propSet.add(valueRP);
	this.propSet.add(lastOpRP);
}
1

First, we create the RP set. This, in effect, creates an empty RP document. In our case, this would be something like this:

<MathResourceProperties xmlns:tns="http://www.globus.org/namespaces/examples/core/MathService_instance_notif">
</MathResourceProperties>
2

Next, we create a SimpleResourceProperty. If you compare with the previous examples, you'll notice that the constructor for SimpleResourceProperty only requires the QName of the RP (whereas ReflectionResourceProperty required more parameters).

3

Now, we set the initial value of the RP. When using ReflectionResourceProperty, we simply had to modify the value attribute. Now, however, we have to use SimpleResourceProperty's add method to do this. By adding new Integer(0), we are creating a new Value RP with value 0:

<tns:Value>0</tns:Value>

Note that, if the RP were unbounded, we could keep on invoking valueRP.add to create more Value RPs:

<tns:Value>0</tns:Value>
<tns:Value>0</tns:Value>
<tns:Value>0</tns:Value>

However, we cannot do this because Value is declared to occur once (and only once) in the RP document.

4

We perform the previous two steps again to add a new LastOp RP.

5

Finally, we add the RPs to the RP set. Now, our RP document will look something like this:

<MathResourceProperties xmlns:tns="http://www.globus.org/namespaces/examples/core/MathService_notif">
	<tns:Value>0</tns:Value>
	<tns:LastOp>NONE</tns:LastOp>
</MathResourceProperties>

Publishing our RPs as topics with ResourcePropertyTopic

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());
	}

	1
	this.topicList = new SimpleTopicList(this);

	2
	valueRP = new ResourcePropertyTopic(valueRP);
	((ResourcePropertyTopic) valueRP).setSendOldValue(true);

	lastOpRP = new ResourcePropertyTopic(lastOpRP);
	((ResourcePropertyTopic) lastOpRP).setSendOldValue(true);

	3
	this.topicList.addTopic((Topic) valueRP);
	this.topicList.addTopic((Topic) lastOpRP);
	
	this.propSet.add(valueRP);
	this.propSet.add(lastOpRP);
}
1

We initialize the topic list using the Globus-supplied SimpleTopicList class.

2

We take the previously created SimpleResourceProperty objects and put them "inside" ResourcePropertyTopic objects. Notice how the valueRP and lastOpRP attributes (of type ResourceProperty) are set to the ResourcePropertyTopic objects, not the original SimpleResourceProperty objects.

Next, we will activate a nice feature included in ResourcePropertyTopics. We can ask that the notification include not only the new value (whenever an RP is modified), but also the old value.

3

Finally, we add the ResourcePropertyTopic objects to the topic list.

[Note]

The code shown above is part of $EXAMPLES_DIR/org/globus/examples/services/core/notifications/impl/MathService.java.

8.4.3. The service implementation

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 ResourcePropertyTopics, 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); 1
	value = new Integer(value.intValue()+a); 2
	valueRP.set(0, value); 3
	lastOpRP.set(0,"ADDITION"); 4
	
	return new AddResponse();
}

[Note]

The code shown above is part of $EXAMPLES_DIR/org/globus/examples/services/core/notifications/impl/MathService.java.

1

We retrieve the current value of the Value RP. Since this RP can have one (and only one) value, we have to access the value in position 0 using the get method. Since get returns an Object, we need to cast this into an Integer object.

2

Next, we perform the actual addition.

3

Next, we modify the value of the Value RP using the set method. Again, since this RP can hold a single value, the new value is placed in the first position of the RP (position 0).

4

Finally, we modify the value of the LastOp RP using, once again, the set method.

[Tip]Accessing the RPs if we split up the implementation

Remember that, in this example, we are using the ServiceResourceHome which allows us to implement the service and the resource in the same class. This means that our add and subtract methods have direct access to the ResourceProperty objects representing our RPs.

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 ResourceProperties interface to access the RP set and then the RP's themselves. This means that our code could end up looking something like this:

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 SimpleResourceProperty. In other words, we could add the following to our resource:

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 add method could be implemented like this:

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 add and subtract in Chapter 4, Singleton resources and Chapter 5, Multiple resources.

8.4.4. Deployment Descriptor

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>
[Note]

This file is $EXAMPLES_DIR/org/globus/examples/services/core/notifications/deploy-server.wsdd.

8.4.5. Compile and deploy

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

8.4.6. Client code

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.

Listener client

This client is composed of two important parts:

  1. 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.

  2. 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 {
		1
		NotificationConsumerManager consumer;

		2
		consumer = NotificationConsumerManager.getInstance();
		consumer.startListening();
		EndpointReferenceType consumerEPR = consumer
				.createNotificationConsumer(this);

		3	
		Subscribe request = new Subscribe();
		request.setUseNotify(Boolean.TRUE);
		request.setConsumerReference(consumerEPR);

		4
		TopicExpressionType topicExpression = new TopicExpressionType();
		topicExpression.setDialect(WSNConstants.SIMPLE_TOPIC_DIALECT);
		topicExpression.setValue(MathQNames.RP_VALUE);
		request.setTopicExpression(topicExpression);

			
		5
		WSBaseNotificationServiceAddressingLocator notifLocator = 
			new WSBaseNotificationServiceAddressingLocator();
		EndpointReferenceType endpoint = new EndpointReferenceType();
		endpoint.setAddress(new Address(serviceURI));
		NotificationProducer producerPort = notifLocator
				.getNotificationProducerPort(endpoint);

		6
		producerPort.subscribe(request);

		7
		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();
	}
}
1

Our client is going to act as a notification consumer. This means that our client will have to expose a Notify operation that will be invoked by the notification producer. For this to happen, our client has to act as both a client and a server. Fortunately, thanks to a Globus-supplied class called NotificationConsumerManager, we are shielded from all the potential nastiness involved in doing this is.

2

Once we invoke the startListening method in the NotificationConsumerManager, our client becomes a server hosting a service that implements the standard NotificationConsumer portType. As such, this service will have an endpoint reference. We need to keep track of this EPR, since it will be used by the NotificationProducer to deliver the notifications.

3

We create the request to the remote Subscribe call. There are two properties we must set: whether the producer must use the standard Notify operation to deliver notifications (in general, we will always want this to be true), and the consumer's EPR.

4

Next, we create a TopicExpressionType object representing the topic we want to subscribe to. Notice how we're subscribing to the Value RP.

5

A this point, the Subscribe request is ready to be sent to the notification producer. To do this, we need to obtain a reference to the standard NotificationProducer portType in the remote service.

6

We are finally ready to send the subscription request.

7

Finally, we let out program loop indefinitely. Notice that we instruct the client's thread to sleep during the loop. This doesn't affect the client's ability to receive notifications, as it will be woken up whenever a notification is delivered.

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, 1
		Object message) {
	ResourcePropertyValueChangeNotificationElementType notif_elem;
	ResourcePropertyValueChangeNotificationType notif;

	2
	notif_elem = (ResourcePropertyValueChangeNotificationElementType) message;
	notif = notif_elem.getResourcePropertyValueChangeNotification();

	if (notif != null) {
		System.out.println("A notification has been delivered");
			
		3
		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());
	}
}
1

The deliver method has three parameters:

  1. topicPath: the topic that produced the notification.

  2. producer: the EPR of the notification producer.

  3. message: the actual notification. Notice how it is of type Object, so we will need to cast it to a more useful type.

2

When using ResourcePropertyTopics to notify changes in RPs, the notification message is of type ResourcePropertyValueChangeNotificationElementType. This type, in turn, contains an object of type ResourcePropertyValueChangeNotificationType. This object is the one that contains the new value of the RP. Remember that, in this example, we've also asked that the notification include the old value too.

3

Finally, we print out the old and new values of the RP.

[Note]

The code shown above is part of $EXAMPLES_DIR/org/globus/examples/clients/MathService_instance_notif/ValueListener.java

Adding client

The adding client requires no explanation, as it is identical to the ones seen in previous chapters.

[Note]

The source code for the adding client is $EXAMPLES_DIR/org/globus/examples/clients/MathService_instance_notif/ClientAdd.java

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”.

Compile and run

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
[Note]

Notice how we have to define a property called GLOBUS_LOCATION. This should be set to the directory where GT4 is installed. We need to define this property because, as mentioned earlier, our client is also going to act as a server. Therefore, it needs to know where all the Globus files are located (some of which are necessary for it to work as a server).

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