A notification client

Math Listener

This client is a bit more complex than all the previous clients we've seen. First of all, we can't put all the code in the main method. A notification client (or more correctly: a class which is going to receive notifications) must implement a deliverNotification method, which is the method that the Grid Service will call when a change is produced.

Also, we're actually going to write two clients. The first one is the important one: it is the one that is going to subscribe to MathService and receive notifications. The second one is a very simple one which calls the add method. This way, we can have several 'listener clients' running at the same time, and then see how they are all updated when we run the 'adder client'.

package org.globus.progtutorial.clients.MathService_sd_notif;

import org.globus.ogsa.client.managers.NotificationSinkManager;
import org.globus.ogsa.NotificationSinkCallback;
import org.globus.ogsa.impl.core.service.ServicePropertiesImpl;
import org.globus.ogsa.utils.AnyHelper;
import org.gridforum.ogsi.ExtensibilityType;
import org.gridforum.ogsi.HandleType;
import org.gridforum.ogsi.ServiceDataValuesType;

import org.globus.progtutorial.stubs.MathService_sd_notif.servicedata.MathDataType;

import java.rmi.RemoteException;

public class ClientListener 
    extends ServicePropertiesImpl implements NotificationSinkCallback
{
  public static void main(String[] args)
  {
    try
    {
      // Get command-line arguments
      HandleType GSH = new HandleType(args[0]);
      ClientListener clientListener = new ClientListener(GSH);
    }catch(Exception e)
    {
      System.out.println("ERROR!");
      e.printStackTrace();
    }
  }

  public ClientListener(HandleType GSH) throws Exception
  {
    // Start listening to the MathService
    NotificationSinkManager notifManager = NotificationSinkManager.getManager();
    notifManager.startListening(NotificationSinkManager.MAIN_THREAD);
    String sink  =  notifManager.addListener("MathData", null, GSH, this);
    System.out.println("Listening...");

    // Wait for key press
    System.in.read();

    
    // Stop listening
    notifManager.removeListener(sink);
    notifManager.stopListening();
    System.out.println("Not listening anymore!");
    }

  
  public void deliverNotification(ExtensibilityType any) throws RemoteException
  {
    try
    {
      // Service Data has changed. Show new data.
      ServiceDataValuesType serviceData = AnyHelper.getAsServiceDataValues(any);
      MathDataType mathData = (MathDataType) AnyHelper.getAsSingleObject(serviceData, MathDataType.class);

      // Write service data
      System.out.println("Current value: " + mathData.getValue());
      System.out.println("Previous operation: " + mathData.getLastOp());
      System.out.println("# of operations: " + mathData.getNumOps());
    }catch(Exception exc)
    {
      System.out.println("ERROR!");
      exc.printStackTrace();
    }
  }
}
[Note]

This file is $TUTORIAL_DIR/org/globus/progtutorial/clients/MathService_sd_notif/ClientListener.java

Let's take a close look at the class declaration:

public class MathListener 
    extends ServicePropertiesImpl implements NotificationSinkCallback

Unlike all our previous clients, this one extends from a class and implements an interface.

  • extends ServicePropertiesImpl: This class is the base class of GridServiceImpl. Why would we want our client to extend from a class which is intended for the server-side of our application? Well, consider this: since our client is going to receive calls from the server...our client is also a server! This might sound a bit confusing, but consider the following rule of thumb in distributed systems: "Clients make calls, servers receive them". According to this definition, our "client" is both a client and a server, because it is going to make calls to MathService (to subscribe to the MathData SDE), but also receive them (deliverNotification). So, our client needs a server infrastructure, which the ServicePropertiesImpl class provides.

  • implements NotificationSinkCallback: This interface must be implemented by classes that wish to subscribe to notifications. One of the requirements of this interface is that we have to implement a deliverNotification method.

The main method is very simple. It receives the only argument to the program (MathService's GSH) and then creates a MathListener class. All the 'listening' is done in the constructor of MathListener. This, of course, is not a good design, but it keeps the code simple enough. More elegant solutions should use threads.

The first interesting snippet of code in the constructor is this:

NotificationSinkManager notifManager = NotificationSinkManager.getManager();
notifManager.startListening(NotificationSinkManager.MAIN_THREAD);
String sink  =  notifManager.addListener("MathData", null, GSH, this);

The NotificationSinkManager is a class that takes care of the whole subscription process, which is done in two simple steps: telling the manager to "start listening" and then telling it what it has to listen to. The addListener merits special attention. Let's take a closer look at the parameters it receives:

  • The Service Data Element we want to subscribe to.

  • A timeout (null, in the example; we don't want to stop listening)

  • The GSH of the Grid Service that has the Service Data Element we want to subscribe to

  • The class which will take care of receiving the notifications (in the example, it is this, although we could delegate the notifications to a different class)

Once we're listening, we'll wait for a key press. As soon as you press a key, we'll 'unsubscribe' in two steps: removing the listener we created previously, and then stopping the notification manager listening thread.

notifManager.removeListener(sink);
notifManager.stopListening();

Now, take a look at deliverNotification. It has an argument called ExtensibilityType any. This is the SDE which is sent along with the notification. As we saw in the Service Data section, we first have to cast the ExtensibilityType into a MathDataType using 'helper' classes.

ServiceDataValuesType serviceData = AnyHelper.getAsServiceDataValues(any);
MathDataType mathData = (MathDataType) AnyHelper.getAsSingleObject(serviceData, MathDataType.class);

Now, let's compile and run the client:

javac \
-classpath ./build/classes/:$CLASSPATH \
org/globus/progtutorial/clients/MathService_sd_notif/ClientListener.java

java \
-classpath ./build/classes/:$CLASSPATH \
-Dorg.globus.ogsa.schema.root=http://localhost:8080/ \
org.globus.progtutorial.clients.MathService_sd_notif.ClientListener \
http://127.0.0.1:8080/ogsa/services/progtutorial/core/notifications/MathService

Notice how we have to define a property called org.globus.ogsa.schema.root. This should be set to the base URL of your Grid Services container. If all goes well, you should see a "Listening..." message. Since we're not making any changes to MathService, you're not receiving any notifications yet. Remember, this client is only listening for notifications. Now we have to produce changes using a 'Math Adder' to see how the notifications are delivered. To see how notifications can be delivered to multiple listeners, you can run more than one MathListener at the same time.

Math Adder

The code for the Math Adder is pretty straightforward:

package org.globus.progtutorial.clients.MathService_sd_notif;

import org.globus.progtutorial.stubs.MathService_sd_notif.service.MathServiceGridLocator;
import org.globus.progtutorial.stubs.MathService_sd_notif.MathPortType;
import java.net.URL;

public class ClientAdder
{
  public static void main(String[] args)
  {
    try
    {
      // Get command-line arguments
      URL GSH = new java.net.URL(args[0]);
      int a = Integer.parseInt(args[1]);

      // Get a reference to the Grid Service instance
      MathServiceGridLocator mathServiceLocator = new MathServiceGridLocator();
      MathPortType math = mathServiceLocator.getMathServicePort(GSH);

      // Call remote method 'add'
      math.add(a);
      System.out.println("Added " + a);
    }catch(Exception e)
    {
      System.out.println("ERROR!");
      e.printStackTrace();
    }
    }
}
[Note]

This file is $TUTORIAL_DIR/org/globus/progtutorial/clients/MathService_sd_notif/ClientAdder.java

Compile and run it:

javac \
-classpath ./build/classes/:$CLASSPATH \
org/globus/progtutorial/clients/MathService_sd_notif/ClientAdder.java

java \
-classpath ./build/classes/:$CLASSPATH \
org.globus.progtutorial.clients.MathService_sd_notif.ClientAdder \
http://127.0.0.1:8080/ogsa/services/progtutorial/core/notifications/MathService \
5

If all goes well, you'll see how the listener/s receive/s a notification each time you run MathAdder. The internal value, along with then 'number of operations', should increase with each run of MathAdder. Since we're only adding, the 'previous operation' will always be 'Addition'.