Skip to main content

skip to main content

developerWorks  >  Grid computing | SOA and Web services  >

Using WS-Notification

developerWorks
Document options

Document options requiring JavaScript are not displayed

Sample code


Rate this page

Help us improve this content


Level: Intermediate

Mike Weaver (mike@mike-weaver.com), Freelance Writer and Consultant

12 Apr 2005

Learn how to set up a simple system for subscribing to and receiving notifications on a particular topic using the message patterns described in the WS-Notification family of specifications. You can also learn to create the appropriate SOAP messages, send them back and forth between Java servlet-based Web services, and analyze their content.

As systems become increasingly loosely coupled, developers are starting to run into a paradox of sorts. Systems that could benefit from event-driven programming, such as Web services applications, are not necessarily built in such a way that this becomes easy to do. The WS-Notification specifications attempt to solve that problem by providing a standard way for Web services to signal their desire to receive notifications about a particular topic. This article shows a simple implementation of a NotificationConsumer, a NotificationProducer, a subscription, and notifications. It also provides a brief look at additional areas covered by the specification.

Note: This article uses Java™ servlets to demonstrate the steps necessary in implementing a WS-Notification solution, but the concepts are the same for any programming environment. To follow along with the code, you'll need to be familiar with Java programming. Experience with Java servlets will also be useful. XML experience is helpful, but not required.

What is WS-Notification?

WS-Notification (WSN) is a family of specifications that standardize the process of creating an event-based system in a Web services environment. They were developed in conjunction with the Web Services Resource Framework (WSRF), which provides a way to use state in a stateless environment, but they can also be utilized by plain Web services.

WSN consists of three specifications:

  • WS-BaseNotification -- This document lays out the base functionality, defining NotificationProducers, NotificationConsumers, notifications, and the subscriptions that hold them all together. It also describes the process for pausing and resuming subscriptions, as well as controlling the length of a subscription.
  • WS-Topics -- When a user subscribes to a NotificationProducer, that subscription is associated with a particular topic or topics. This document explains the structure by which multiple topics can be defined and created.
  • WS-BrokeredNotification -- In some cases, the entity that creates the notification can't manage the various subscriptions. This document defines the process of creating a publisher that simply creates messages and distributes them through a separate NotificationBroker.

This article focuses on the basics of the WS-BaseNotification specification.



Back to top


What is a WS-Resource?

We should take a moment to discuss a topic that springs out of the WSRF specifications, but is crucial to WS-Notification: WS-Resources.

Web services are an inherently stateless medium. The only information available at the time of a request is the information included with that request. That makes it difficult to create applications in which information has to persist between interactions. For example, in a few moments, we're going to use a Web service to create a subscription to a particular topic. If that subscription only exists during the request that creates it, it won't be very useful.

Instead, we need the subscription to be stateful. A stateful resource exists between interactions, and has, as the name suggests, state. That state is defined by a group of properties. Change a property and the state changes, and vice-versa.

A WS-Resource is the combination of a stateful resource and a Web service that interacts with it. In this article, we'll create several WS-Resources.



Back to top


What we're going to accomplish

To demonstrate the use of WS-Notification, we will create a simple system that tracks No. 1 positions in a search engine, such as Google. A NotificationConsumer signs up for a subscription to a particular keyword, and when the site that occupies the No. 1 position for that keyword changes, the NotificationProducer sends a notification detailing the change. (Actually checking the search engine is beyond the scope of this article. Instead, we'll concentrate on the notification process. If you're curious about it, however, see Resources for a tutorial on using Google's Web services API.)

We'll start with a simple HTML form into which users can enter their e-mail addresses and keywords they want to watch. That form submits to the consumer Web service, which saves each user as a WS-Resource. The consumer service calls the producer Web service, which creates a subscription WS-Resource.

When the producer sees a change for a keyword, it pulls the information for each subscription watching that keyword and sends a notification message to the consumer Web service, which passes it along to the appropriate WS-Resource.



Back to top


Getting ready

To make this work, we'll need to create one Web service for the consumer and one for the producer. To simplify this process, we'll create each one as a servlet that receives POST requests. We've used the Tomcat V5 servlet container from the Apache Jakarta Project for our examples, but any server that supports servlets can be used. This setup requires the following:

  1. Download and install Tomcat.
  2. Download and extract the Axis package.
  3. You can create your own Web application, if desired, or use one of the one that comes with your server.
  4. Copy the files from the <AXIS_HOME>/lib directory to the lib directory of your Web application.
  5. Create two servlets and add them to the web.xml file for your application. Call them ConsumerServiceServlet.java and ProducerServiceServlet.java.


Back to top


The NotificationConsumer

Before we actually do any coding, let's take a look at the structure of the consumer.

A NotificationConsumer can be a plain Web service or a WS-Resource. In either case, we use the Web Services Description Language (WSDL) to describe them. In the case of the ConsumerServiceServlet, we have the following WSDL file:


Listing 1. The NotificationConsumer WSDL file
				<?xml version="1.0" encoding="UTF-8"?>
<definitions name="Search"
    targetNamespace="http://example.com/search"
    xmlns="http://schemas.xmlsoap.org/wsdl/"
    xmlns:tns="http://example.com/search"
    xmlns:wsa=
          "http://schemas.xmlsoap.org/ws/2004/03/addressing"
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
    xmlns:wsrp="http://docs.oasis-open.org/wsrf/2004/06/wsr
f-WS-ResourceProperties-1.2-draft-01.xsd"
    xmlns:wsrpwsdl="http://docs.oasis-open.org/wsrf/2004/0
6/wsrf-WS-ResourceProperties-1.2-draft-01.wsdl"
    xmlns:wsnt="http://docs.oasis-open.org/wsn/2004/06/wsn-W
S-BaseNotification-1.2-draft-01.xsd"
    xmlns:wsntw="http://docs.oasis-open.org/wsn/2004/06/ws
n-WS-BaseNotification-1.2-draft-01.wsdl"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
    
    <wsdl:import namespace="http://docs.oasis-open.org/ws
rf/2004/06/wsrf-WS-ResourceProperties-1.2-draft-01.wsdl"
              location="WS-ResourceProperties.wsdl" />
    <wsdl:import namespace="http://docs.oasis-open.org/ws
n/2004/06/wsn-WS-BaseNotification-1.2-draft-01.wsdl"
        location="WS-BaseN.wsdl" />
    
    <types>
        <xsd:schema 
              targetNamespace="http://example.com/search"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema">
                        
            <xsd:element name="email" 
                                     type="xsd:string" />
            <xsd:element name="keyword" 
                                     type="xsd:string" />
            
            <xsd:element 
                       name="SearchSubscriberProperties">
                <xsd:complexType>
                    <xsd:sequence>
                        <xsd:element ref="email" 
                            minOccurs="1" maxOccurs="1"/>
                        <xsd:element ref="keyword" 
                            minOccurs="1" maxOccurs="1"/>
                        <xsd:any/>
                    </xsd:sequence>
                </xsd:complexType>
            </xsd:element>            
            
        </xsd:schema>
        
    </types>

    <portType name="ConsumerPortType" 
wsrp:ResourceProperties="tns:SearchSubscriberProperties">

        <operation name="GetResourceProperty">
            <input message=
                       "wsrpwsdl:GetResourcePropertyRequest"
                 wsa:Action="http://docs.oasis-open.org/wsr
f/2004/06/WS-ResourceProperties/GetResourceProperty"/>
            <output message=
                      "wsrpwsdl:GetResourcePropertyResponse" 
             wsa:Action="http://docs.oasis-open.org/wsrf/200
4/06/WS-ResourceProperties/GetResourcePropertyResponse"/>
        </operation>

        <wsdl:operation name="Notify">
            <wsdl:input message="wsntw:Notify" 
                wsa:Action="http://docs.oasis-open.org/wsn/2
004/06/wsn-WS-BaseNotification/Notify"/>
        </wsdl:operation>
        
    </portType>
    
    <binding name="ConsumerSoapBinding" 
                            type="tns:/ConsumerPortType">
        <soap:binding style="document" transport=
                 "http://schemas.xmlsoap.org/soap/http"/>
        <operation name="GetResourceProperty">
            <input>
                <soap:body use="literal"/>
            </input>
            <output>
                <soap:body use="literal"/>
            </output>
        </operation>
        <operation name="Notify">
            <input>
                <soap:body use="literal"/>
            </input>
        </operation>
    </binding>
    
    <service name="ConsumerService">
        <port name="ConsumerPort" 
                       binding="tns:ConsumerSoapBinding">
            <soap:address 
                location="http://example.com/search"/>
        </port>
    </service>

</definitions>

Let's take this in steps. First, we're importing the WS-ResourceProperties and WS-BaseNotification WSDL files. These files define the types and messages we'll need, so including them enables us to simply reference them.

Next, we're defining the resource properties document for each subscriber using the SearchSubscriberProperties type. We won't be manipulating these properties using the Web service in this article, but it's important to understand the structure of how this all fits together.

Then we're creating the portType, which acts as a sort of interface for the service. Notice the additional attribute: wsrp:ResourceProperties. This value points back to the resource property document type and indicates the resource to which the included operations apply.

In this case, we have two operations. The first, GetResourceProperty, is required for all WS-Resources. As the name suggests, it enables a client to get the value of a WS-Resource's property. The second is the Notify operation, which the NotificationProducer uses to actually send the notification. These operations use messages defined in their respective included WSDL files.

From there, it's a standard matter of specifying a binding for each operation and applying that binding to a service.

Here, we'll concentrate on the Notify operation, since that's required for all NotificationConsumers, whether they're a plain Web service or a WS-Resource.



Back to top


The basic consumer servlet

The first step in this process is to gather the e-mail address and keyword for each subscriber. To do that, create a simple HTML form shown in Listing 2.


Listing 2. The subscription form
				<html>
<head>
     <title>Create a subscription</title>
</head>
<body>
   <form method="GET"
      action="http://localhost:8080/servlets-examples/servle
t/ConsumerServiceServlet">

      <h2>Create a subscription</h2>

      Email <input type="text" name="email" />
      <br />
      Topic <input type="text" name="topic" />
      <br />
      <input type="submit">

   </form>
</body>
</html>

Note that my consumer servlet resides at: http://localhost:8080/servlets-examples/servlet/ConsumerServiceServlet.

Make sure to change the action parameter to match your own system. This page creates a form into which we can enter the basic subscriber information.

Now let's look at the basic servlet. Retrieving those values is a simple matter of accessing the request parameters.


Listing 3. The basic servlet
				import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;

public class ConsumerServiceServlet extends HttpServlet 
                                        implements Servlet {

   public ConsumerServiceServlet() {
       super();
   }

   protected void doGet(HttpServletRequest req, 
                        HttpServletResponse resp) 
                      throws ServletException, IOException {

       String email = req.getParameter("email");
       resp.getWriter().println("Email: "+email);
      
       String topic = req.getParameter("topic");
       resp.getWriter().println("Topic: "+topic);   
       
   }

   protected void doPost(HttpServletRequest req, 
                         HttpServletResponse resp) 
                      throws ServletException, IOException {

   }
}

The servlet can receive information from the client (in this case, the browser) as a GET message or a POST message. Normally, we'd submit the form as a POST message, but later, we'll use the doPost() method to receive notifications, so for now, we'll receive the form using doGet(), which simply retrieves the values and outputs them to the browser.

Compile the servlet and deploy it to the server. (The server may need restarting for changes to take effect.) Enter an e-mail address and keyword, then submit the form. The information should now be repeated in the browser.



Back to top


Create a subscriber

The purpose of the consumer service is to take the information and use it to create a subscription for that user. When the service receives a notification for that user, it'll need to pass it on. For that to happen, we'll need a way for the service to keep track of each user.

In an actual application, we'd use some sort of persistence, such as saving the information to a file or database. In this case, to keep it simple, the SubscriptionConsumer class is created.


Listing 4. The basic consumer class
				public class SubscriptionConsumer {

   private String email;
   private String topic;
       
   public SubscriptionConsumer(String newEmail, 
                               String newTopic){
      setEmail(newEmail);
      setTopic(newTopic);
   }
   
   public void setEmail (String newEmail){
      email = newEmail;
   }
   public void setTopic (String newTopic){
      topic = newTopic;
   }

   public String getEmail(){
      return email;
   }
   public String getTopic(){
      return topic;
   }
}

For each new user, we'll create a SubscriptionConsumer object and add it to a static array within the servlet, as shown in Listing 5. (Yes, this means we'll lose all of our information when we restart the server. That's the price of simplicity.)


Listing 5. Saving the subscriber information
				import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;

public class ConsumerServiceServlet extends HttpServlet 
                                        implements Servlet {

   static SubscriptionConsumer[] consumers = 
                             new SubscriptionConsumer[1000];
   static int nextId;   
   
   public ConsumerServiceServlet() {
      super();
      nextId = 1;
   }

   protected void doGet(HttpServletRequest req, 
                        HttpServletResponse resp) 
                      throws ServletException, IOException {

       String email = req.getParameter("email");
       resp.getWriter().println("Email: "+email);
      
       String topic = req.getParameter("topic");
       resp.getWriter().println("Topic: "+topic);
       
       SubscriptionConsumer thisConsumer = 
                     new SubscriptionConsumer(email, topic);
       int thisConsumerId = nextId++;
       consumers[thisConsumerId] = thisConsumer;
       
       resp.getWriter().println("Added consumer number "+
                                        thisConsumerId);
       
   }

   protected void doPost(HttpServletRequest req, 
                         HttpServletResponse resp) 
                      throws ServletException, IOException {

   }
}

The first time the servlet runs, it initializes the array and the nextId value. Because the array and the nextId value are static, they're shared between servlet instances. Again, this wouldn't hold up in a production environment, but we're trying to get to the actual notifications, not build a database.

Each time a user submits the form, we create a new SubscriptionConsumer object and add it to the array. One handy thing about the array is that it gives us an easy way to retrieve a particular subscriber. As long as we know the ID, retrieving the actual object is straightforward. This capability will come in handy when the service receives a notification for a particular consumer and needs to pass it on.



Back to top


Subscribe to a topic

Even though we haven't actually created the NotificationProducer yet, because we're dealing with a Web service, we know exactly what data we're going to need to send. According to the WS-BaseNotification specification, we need to send a Simple Object Access Protocol (SOAP) message that looks like the one shown in Listing 6.


Listing 6. The Subscribe message
				<soapenv:Envelope 
   xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
       xmlns:sat="http://example.org/searchSystem"
       xmlns:wsa="http://www.w3.org/2005/02/addressing"
       xmlns:wsnt="http://docs.oasis-open.org/wsn/2004/06/ws
n-WS-BaseNotification-1.2-draft-01.xsd">
    <soapenv:Header>
       <wsa:Action>
          http://docs.oasis-open.org/wsn/2004/06/WS-BaseNoti
fication/Subscribe
       </wsa:Action>
       <wsa:To soapenv:mustUnderstand="1">
http://localhost:8080/servlets-examples/servlet/ProducerServ
iceServlet
       </wsa:To>
     </soapenv:Header>
    <soapenv:Body>
       <wsnt:Subscribe>
          <wsnt:ConsumerReference>
              <wsa:Address>
http://localhost:8080/servlets-examples/servlet/ConsumerServ
iceServlet
              </wsa:Address>
              <wsa:ReferenceProperties>
                 <search:subId 
          xmlns:search="http://www.example.com/searcher">
                       23
                 </search:subId>
              </ReferenceProperties>
          </wsnt:ConsumerReference>
          <wsnt:TopicExpression Dialect="http://docs.oasi
s-open.org/wsn/2004/06/TopicExpression/Simple">
               cheese
          </wsnt:TopicExpression>
       </wsnt:Subscribe>
    </soapenv:Body>
</soapenv:Envelope>

The SOAP header contains two pieces of information: what we're trying to do (the Action element) and where we want to do it (the To element). In this case, the To element refers to the NotificationProducer service we'll build in a moment.

The actual Subscribe message contains two important pieces of information. The first, the ConsumerReference, is an endpoint reference that points to the actual WS-Resource. This structure, defined in the WS-Addressing specification, contains the address for the Web service and one or more ReferenceProperties, which we'll use to identify the individual subscriber. In this case, the ReferenceProperty refers to the index within the array, but the receiving application shouldn't try to interpret the endpoint reference. It should be regarded as a single unit of information. The Subscribe message also includes the topic to which the subscription applies.

Now let's build the message and prepare to send it.


Listing 7. Building the Subscribe message
				import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import javax.xml.soap.*;

public class ConsumerServiceServlet extends HttpServlet 
                                        implements Servlet {

   static SubscriptionConsumer[] consumers = 
                             new SubscriptionConsumer[1000];
   static int nextId;
   
   SOAPFactory soapFactory;
   
   public ConsumerServiceServlet() {
      super();
      nextId = 1;
      try {
         soapFactory = SOAPFactory.newInstance();
      } catch (Exception e){
         e.printStackTrace();
      }
   }

   protected void doGet(HttpServletRequest req,
                        HttpServletResponse resp) 
                      throws ServletException, IOException {

       String email = req.getParameter("email");
       String topic = req.getParameter("topic");
       
       SubscriptionConsumer thisConsumer = 
                     new SubscriptionConsumer(email, topic);
       int thisConsumerId = nextId++;
       consumers[thisConsumerId] = thisConsumer;
       
       try {
      
          MessageFactory messageFactory = 
                               MessageFactory.newInstance();
          SOAPMessage message = 
                             messageFactory.createMessage();
     
          SOAPHeader header = message.getSOAPHeader();
          SOAPHeaderElement actionElement = 
               header.addHeaderElement(
                     soapFactory.createName("Action", "wsa", 
                   "http://www.w3.org/2005/02/addressing"));
          actionElement.addTextNode(
"http://docs.oasis-open.org/wsn/2004/06/WS-BaseNotificatio
n/Subscribe");
          SOAPHeaderElement toElement = 
             header.addHeaderElement(
                         soapFactory.createName("To", "wsa", 
                   "http://www.w3.org/2005/02/addressing"));
          toElement.addTextNode(
"http://localhost:8080/servlets-examples/servlet/ProducerSer
viceServlet");
          toElement.setMustUnderstand(true);

          SOAPBody body = message.getSOAPBody();
          SOAPElement bodyElement = 
              body.addChildElement(soapFactory.createName(
                                        "Subscribe", "wsnt",                  
"http://docs.oasis-open.org/wsn/2004/06/wsn-WS-BaseNotificat
ion-1.2-draft-01.xsd"));

          SOAPElement subEndpoint=soapFactory.createElement(
                                "ConsumerReference", "wsnt", 
"http://docs.oasis-open.org/wsn/2004/06/wsn-WS-BaseNotificat
ion-1.2-draft-01.xsd");
          
          SOAPElement address = soapFactory.createElement(
                                           "Address", "wsa", 
                    "http://www.w3.org/2005/02/addressing");
          address.addTextNode(
"http://localhost:8080/servlets-examples/servlet/ConsumerSer
viceServlet");
          subEndpoint.appendChild(address);
          
          SOAPElement referenceProps = 
            soapFactory.createElement("ReferenceProperties", 
             "wsa", "http://www.w3.org/2005/02/addressing");
          SOAPElement subscriberId = 
                soapFactory.createElement("subID", "search", 
                         "http://www.example.com/searcher");
          subscriberId.addTextNode(
                            String.valueOf(thisConsumerId));
          referenceProps.appendChild(subscriberId);
          subEndpoint.appendChild(referenceProps);

          bodyElement.appendChild(subEndpoint);
              
          SOAPElement topicExpression = 
             bodyElement.addChildElement("TopicExpression");
          topicExpression.addAttribute(
                       soapFactory.createName("Dialect", "", 
"http://docs.oasis-open.org/wsn/2004/06/wsn-WS-BaseNotificat
ion-1.2-draft-01.xsd"), 
"http://docs.oasis-open.org/wsn/2004/06/TopicExpression/Simp
le");
          topicExpression.addTextNode(topic);
              
          bodyElement.appendChild(topicExpression);     

          message.saveChanges();
          
          message.writeTo(resp.getOutputStream());
          
       } catch (Exception e){
              e.printStackTrace(resp.getWriter());
       }
   }

   protected void doPost(HttpServletRequest req, 
                         HttpServletResponse resp) 
                      throws ServletException, IOException {
   }
}

Let's take this one step at a time. First of all, we're building the SOAP message using classes from the SOAP with Attachments API for Java (SAAJ), so the first thing we'll do is import the ones we'll be be using. In most cases, we'll create objects using the SOAPFactory. Because we'll ultimately be using it in various methods, we'll create one global instance and intitialize it when we create the servlet.

Next, we'll create the actual message using the MessageFactory. From there, we can get a reference to the SOAP header and add the appropriate elements, including their namespaces. Note that the To element has to carry a value of 1 for the soapenv:mustUnderstand attribute, so we'll have to set that, as well.

After retrieving a reference to the Body element, we'll construct the elements that go inside it, starting with the Subscribe element and working our way down through the ConsumerReference and TopicExpression elements. In each case, we create the element (using the proper namespace), add any necessary text, and add it to its parent element.

Finally, we save the message and output it to the browser. Submitting the form again gives us a response like the one shown in Listing 8.


Listing 8. The generated message
				
<soapenv:Envelope 
   xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <soapenv:Header>
      <wsa:Action soapenv:actor=
                "http://schemas.xmlsoap.org/soap/actor/next" 
        soapenv:mustUnderstand="0" 
        xmlns:wsa="http://www.w3.org/2005/02/addressing">
http://docs.oasis-open.org/wsn/2004/06/WS-BaseNotification/S
ubscribe
      </wsa:Action>
      <wsa:To soapenv:actor=
                "http://schemas.xmlsoap.org/soap/actor/next" 
        soapenv:mustUnderstand="1" 
        xmlns:wsa="http://www.w3.org/2005/02/addressing">
http://localhost:8080/servlets-examples/servlet/ProducerServ
iceServlet
      </wsa:To>
   </soapenv:Header>
   <soapenv:Body>
      <wsnt:Subscribe xmlns:wsnt=
"http://docs.oasis-open.org/wsn/2004/06/wsn-WS-BaseNotificat
ion-1.2-draft-01.xsd">
        <wsnt:ConsumerReference>
           <wsa:Address xmlns:wsa=
                  "http://www.w3.org/2005/02/addressing">
http://localhost:8080/servlets-examples/servlet/ConsumerServ
iceServlet
           </wsa:Address>
           <wsa:ReferenceProperties xmlns:wsa=
                  "http://www.w3.org/2005/02/addressing">
               <search:subID xmlns:search=
                       "http://www.example.com/searcher">
                   7
               </search:subID>
           </wsa:ReferenceProperties>
        </wsnt:ConsumerReference>
        <wsnt:TopicExpression Dialect=
"http://docs.oasis-open.org/wsn/2004/06/TopicExpression/Simp
le">
            cheese
        </wsnt:TopicExpression>
      </wsnt:Subscribe>
   </soapenv:Body>
</soapenv:Envelope>

Now we're ready to create the NotificationProducer service, which will receive this message.



Back to top


Create the NotificationProducer

A NotificationProducer is also a WS-Resource, which means we can describe it using a WSDL file. In the interest of space, we won't include it here; you can download it from Resources, but in form, it's similar to the WSDL file for the NotificationConsumer.

As far as requirements, a NotificationProducer must be able to fulfill the following requests:

  • GetResourceProperty -- Returns a resource property, such as the topics the NotificationProducer supports
  • Subscribe -- Creates a new subscription
  • GetCurrentMessage -- Returns the last message sent for a particular topic, if there was one

Once the NotificationProducer creates the subscription, the SubscriptionManager must also be able to perform the PauseSubscription and ResumeSubscription actions.

In this article, we'll focus on the Subscribe operation. Let's start with the basic servlet.



Back to top


The NotificationProducer servlet

Now we're ready to create the basic ProducerServiceServlet. At this point, let's just put in the framework and receive the actual message:


Listing 9. Receiving the message
				import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.xml.soap.*;

public class ProducerServiceServlet extends HttpServlet 
                                        implements Servlet {

   static SOAPFactory soapFactory;
   static Subscriber[] subscribers = new Subscriber[1000];
   static int nextId;
   
   public ProducerServiceServlet() {
      super();
      nextId = 1; 
      
      try {
         soapFactory = SOAPFactory.newInstance();
      } catch (Exception e){
         e.printStackTrace();
      }
   }

   protected void doGet(HttpServletRequest req, 
                        HttpServletResponse resp) 
                      throws ServletException, IOException {

   }

   protected void doPost(HttpServletRequest req, 
                         HttpServletResponse resp) 
                      throws ServletException, IOException {
       try {

          InputStream toSOAP = req.getInputStream();

          MessageFactory messageFactory = 
                               MessageFactory.newInstance();
          SOAPMessage message = 
                 messageFactory.createMessage(null, toSOAP);

          SOAPMessage reply = message;
               
          reply.writeTo(resp.getOutputStream());
               
       } catch (Exception e){
          e.printStackTrace(resp.getWriter());
       }

   }

}

Starting at the top, this service, like the consumer service, will ultimately have to save resources, so we've added the Subscriber array as a static variable and initialized the counter. (We'll deal more with the actual class in a moment.) Also like the consumer service, we'll create a lot of SOAP objects, so we've gone ahead and created the SOAPFactory.

In this case, we're receiving a SOAP message, so we need to use the doPost() method to retrieve its contents. Fortunately, SAAJ makes that easy by providing a createMessage() method that takes an InputStream. By feeding it the InputStream that represents the request, we can easily get the original SOAP message.

Ultimately, we're going to reply to this message, so we've created a second SOAP message to use as the reply, which we'll then write to the OutputStream as a response to the original request. For convenience, we can set that reply equal to the original message so we can make sure everything's working. But first we have to actually send the request.



Back to top


Sending the request

SAAJ makes it fairly simple to send a SOAP message and receive a response.


Listing 10. Send the SOAP message
				...
          message.saveChanges();
          
          message.writeTo(resp.getOutputStream());
          
          String destination = 
"http://localhost:8080/servlets-examples/servlet/ProducerSer
viceServlet";

          SOAPConnectionFactory soapConnFactory = 
                        SOAPConnectionFactory.newInstance();
          SOAPConnection connection = 
                         soapConnFactory.createConnection();
          SOAPMessage reply = connection.call(message, 
                                               destination);
          
          reply.writeTo(resp.getOutputStream());
          
       } catch (Exception e){
              e.printStackTrace(resp.getWriter());
       }
   }
...

The destination is simply the URL for the producer servlet. Using that information, we can create a new SOAPConnection object and use it to send the SOAPMessage we created. The result is a SOAPMessage response, which we can then output to the browser.

To see it work, simply submit the original HTML form. The consumer service takes the information, uses it to create a SOAP message, and sends it to the producer service, which, at the moment, simply echoes it back. The consumer service then outputs the resulting reply.

So for now, you should simply see two copies of the original SOAP message in your browser.



Back to top


Analyzing the request

Now that we have the request, we need to analyze it to determine what to do. First, because this service can be used for multiple request types, we'll need to determine what action to take.


Listing 11. Determine the action
				import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.xml.soap.*;

public class ProducerServiceServlet extends HttpServlet 
                                        implements Servlet {

  static SOAPFactory soapFactory;
  static Subscriber[] subscribers = new Subscriber[1000];
  static int nextId;
   
  public ProducerServiceServlet() {
     super();
     nextId = 1; 
      
     try {
        soapFactory = SOAPFactory.newInstance();
     } catch (Exception e){
        e.printStackTrace();
     }
  }

  protected void doGet(HttpServletRequest req, 
                       HttpServletResponse resp) 
                      throws ServletException, IOException {

  }
  
  private String getAction(SOAPMessage message){
     String action;
     try {
         SOAPElement request = (SOAPElement)message
                   .getSOAPBody().getChildElements().next();
         action = request.getLocalName();
     } catch (Exception e){
         action = e.getMessage();
     }
     return action;
      
  }

  private SOAPMessage Subscribe(SOAPMessage message){
     SOAPMessage reply = null;
     try {
        MessageFactory messageFactory =
                               MessageFactory.newInstance();
        reply = messageFactory.createMessage();
        reply.getSOAPBody().addBodyElement(
               soapFactory.createName("SubscribeResponse"));
     } catch (Exception e){
        e.printStackTrace();
     }
     return reply;
  }
   
...

  protected void doPost(HttpServletRequest req, 
                        HttpServletResponse resp) 
                      throws ServletException, IOException {
     try {

       InputStream toSOAP = req.getInputStream();

       MessageFactory messageFactory = 
                               MessageFactory.newInstance();
       SOAPMessage message = 
                 messageFactory.createMessage(null, toSOAP);

       SOAPMessage reply;
          
       String action = getAction(message);
       if (action.equals("Subscribe")){
          reply = Subscribe(message);
       } else if (action.equals("GetCurrentMessage")) {
          reply = GetCurrentMessage(message);
       } else if (action.equals("GetResourceProperty")){
          reply = GetResourceProperty(message);
       } else {
          reply = UnknownActionFault(message);
       }
               
       reply.writeTo(resp.getOutputStream());
               
    } catch (Exception e){
...

The getAction() method analyzes the message to determine its content. In this case, we're relying solely on payload, or the content of the Body element, but you can also look at the Action element in the header. From there, we're determining what method to execute.

In each case, we'll return the reply message. Here, we're creating a simple response that shows what method has been executed; similar methods have been omitted for the other actions for the sake of space. Now we can create the actual subscription.



Back to top


Save the subscription

The next step is to create the actual subscription. As in the case of the ConsumerSubscribers, we'll create a simple class.


Listing 12. The Subscriber class
				import javax.xml.soap.SOAPElement;

public class Subscriber {

    private SOAPElement consumerEndpoint;
    private String topicExpression;
    private String useNotify;
       
   public Subscriber(SOAPElement consumer, String topic, 
                                             String notify){
        setConsumerEndpoint(consumer);
        setTopicExpression(topic);
        setUseNotify(notify);
   }
   
   public void setConsumerEndpoint(SOAPElement consumer){
      consumerEndpoint = consumer;
   }
   public void setTopicExpression (String topic){
      topicExpression = topic;
   }
   public void setUseNotify (String notify){
      useNotify = notify;
   }

    public SOAPElement getConsumerEndpoint(){
       return consumerEndpoint;
    }
    public String getTopicExpression(){
       return topicExpression;
    }
    public String getUseNotify(){
       return useNotify;
    }
}

The Subscriber class stores three pieces of information for each subscriber: the addresses/endpoints, the topics for which they want to receive notifications, and their format preference. WS-Notification allows users to specify whether they want to receive a full Notify-format message (as we'll see here) or a simplified version. We're assuming all of our subscribers want the Notify format, since we didn't include it in the Subscribe request, but technically, the NotificationProducer must be able to satisfy both options on a subscriber-by-subscriber basis.

The next step is to analyze the Subscribe message to extract the information for the subscription.


Listing 13. Analyzing the Subscribe message
				...
   private SOAPMessage Subscribe(SOAPMessage message){
      SOAPMessage reply = null;
      try {
         
        Name subscribe = soapFactory.createName("Subscribe",
                                                "wsnt", 
"http://docs.oasis-open.org/wsn/2004/06/wsn-WS-BaseNotificat
ion-1.2-draft-01.xsd");
        Name consumerReference = soapFactory.createName(
                                "ConsumerReference", "wsnt", 
"http://docs.oasis-open.org/wsn/2004/06/wsn-WS-BaseNotificat
ion-1.2-draft-01.xsd");
        Name topicExpression = soapFactory.createName(
                                  "TopicExpression", "wsnt", 
"http://docs.oasis-open.org/wsn/2004/06/wsn-WS-BaseNotificat
ion-1.2-draft-01.xsd");
             
        SOAPElement bodyElement = 
             (SOAPElement)message.getSOAPBody()
                        .getChildElements(subscribe).next();
         
        SOAPElement consumerElement = 
             (SOAPElement)bodyElement
                .getChildElements(consumerReference).next();
        SOAPElement topicElement = 
             (SOAPElement)bodyElement
                  .getChildElements(topicExpression).next();
        String topicString = 
                         topicElement.getValue().toString();

        Subscriber newSubscriber = 
                           new Subscriber(consumerElement, 
                                       topicString, "true");
        int thisId = nextId++;
        subscribers[thisId] = newSubscriber;
         
         
        MessageFactory messageFactory = 
                               MessageFactory.newInstance();
        reply = messageFactory.createMessage();
        SOAPElement sub = reply.getSOAPBody().
           addBodyElement(
               soapFactory.createName("SubscribeResponse"));

        sub.addChildElement(consumerElement);
        sub.addChildElement(topicElement);

      } catch (Exception e){
        e.printStackTrace();
      }
      return reply;
   }
...

Rather than stepping through the SOAPMessage looking for individual elements, we can create Name objects that point directly at the elements we want to retrieve, then use the getChildElements() method to retrieve them.

In this case, we're storing the endpoint for the consumer directly in the object -- we'll need it intact later -- but we're extracting the content of the topicExpression. We can then create a new Subscriber object and add it to the Subscribers array.

Finally, we can make sure we're pulling the data we want by temporarily adding the extracted elements to the reply message.



Back to top


Send the SubscribeResponse

Once we create the subscription, we need to create an endpoint reference for it and send that back to the consumer. This way, the consumer can use the endpoint to make changes to the subscription, such as pausing, resuming, or canceling it.

Let's start by creating the endpoint and adding it to the message.


Listing 14. Add the endpoint
				...
     Subscriber newSubscriber = 
                   new Subscriber(consumerElement, 
                                       topicString, "true");
     int thisId = nextId++;
     subscribers[thisId] = newSubscriber;
        
     MessageFactory messageFactory = 
                               MessageFactory.newInstance();
     reply = messageFactory.createMessage();
     SOAPElement sub = reply.getSOAPBody()
           .addBodyElement(
                 soapFactory.createName("SubscribeResponse",
                                        "wsnt", 
"http://docs.oasis-open.org/wsn/2004/06/wsn-WS-BaseNotificat
ion-1.2-draft-01.xsd"));

     SOAPElement subEndpoint = 
          soapFactory.createElement("SubscriptionReference",
                                    "wsnt",
"http://docs.oasis-open.org/wsn/2004/06/wsn-WS-BaseNotificat
ion-1.2-draft-01.xsd");
          
     SOAPElement address = 
                 soapFactory.createElement("Address", "wsa", 
                    "http://www.w3.org/2005/02/addressing");
     address.addTextNode(
"http://localhost:8080/servlets-examples/servlet/ProducerSer
viceServlet");
     subEndpoint.appendChild(address);
          
     SOAPElement referenceProps = 
            soapFactory.createElement("ReferenceProperties", 
                                      "wsa", 
                    "http://www.w3.org/2005/02/addressing");
     SOAPElement subId = 
          soapFactory.createElement("subscriptionID", "sub", 
                    "http://www.example.com/searchService");
     subId.addTextNode(String.valueOf(thisId));
     referenceProps.appendChild(subId);
     subEndpoint.appendChild(referenceProps);
          
     sub.appendChild(subEndpoint);
         
     reply = populateHeader(reply, 
                      newSubscriber.getConsumerEndpoint(), 
"http://docs.oasis-open.org/wsn/2004/06/WS-BaseNotificatio
n/SubscribeResponse");
         
   } catch (Exception e){
     e.printStackTrace();
   }
   return reply;
 }
...

The process is the same as it was in the consumer: create the various elements and build them up into a hierarchy, then add them to the message.

In this case, however, we have an additional step. When we were subscribing, we knew the address of the NotificationProducer, so we could create the SOAP header directly. In this case, however, the header is based on the information in the consumer's endpoint reference, so we need to build the header based on that information.


Listing 15. Build the SOAP header
				
   private SOAPMessage populateHeader(SOAPMessage message, 
                                      SOAPElement endpoint, 
                                      String action){
     try {
        SOAPHeader header =  message.getSOAPHeader();

        SOAPHeaderElement actionElement = 
             header.addHeaderElement(
                soapFactory.createName("Action", "wsa", 
                   "http://www.w3.org/2005/02/addressing"));
        actionElement.addTextNode(action);
           
        Name address = soapFactory.createName("Address", 
             "wsa", "http://www.w3.org/2005/02/addressing");
        SOAPElement addressElement = (SOAPElement)endpoint
                          .getChildElements(address).next();
        String toAddr = addressElement.getFirstChild()
                                            .getNodeValue();

        SOAPHeaderElement toElement = 
           header.addHeaderElement(
              soapFactory.createName("To", "wsa", 
                   "http://www.w3.org/2005/02/addressing"));
        toElement.addTextNode(toAddr);

        Name referenceProperties = 
           soapFactory.createName("ReferenceProperties",
             "wsa", "http://www.w3.org/2005/02/addressing");
        SOAPElement refPropsElement =(SOAPElement)endpoint
              .getChildElements(referenceProperties).next();
           
        java.util.Iterator refProps = 
                         refPropsElement.getChildElements();
           
        while (refProps.hasNext()){
           Object thisPropertyObj = refProps.next();
           SOAPElement thisProperty = 
                               (SOAPElement)thisPropertyObj;
           header.addHeaderElement(soapFactory
                    .createName(thisProperty.getLocalName(), 
                       thisProperty.getPrefix(), 
                       thisProperty.getNamespaceURI()))
                          .addTextNode(thisProperty
                           .getFirstChild().getNodeValue());
           }           
        } catch (Exception e){
           try {
           SOAPBody replySoapBody = message.getSOAPBody();
           replySoapBody.addBodyElement(
                        soapFactory.createName("Response"));
                                                             .addTextNode(e.toString());
           } catch (Exception e2){}
        }
      return message;      
   }
...

Some information, such as the Action and To elements, are straightforward. We pass the value for the Action element into the method, and the value of the To element comes from the endpoint's Address element.

The ReferenceProperties element, however, is a bit more of a problem. According to WS-Addressing, the content of the ReferenceProperties element must be added to the SOAP header, so in making the (dubious) assumption that all of our ReferenceProperties will be elements without attributes or children, we're simply copying them into the header. (A production solution would, of course, be more robust.)

The result is the SubscribeResponse, which gets sent back to the consumer.


Listing 16. The SubscribeResponse
				<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv=
                 "http://schemas.xmlsoap.org/soap/envelope/" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <soapenv:Header>
  <wsa:Action 
        xmlns:wsa="http://www.w3.org/2005/02/addressing">
http://docs.oasis-open.org/wsn/2004/06/WS-BaseNotification/S
ubscribeResponse
  </wsa:Action>
  <wsa:To soapenv:mustUnderstand="1" 
        xmlns:wsa="http://www.w3.org/2005/02/addressing">
http://localhost:8080/servlets-examples/servlet/ConsumerServ
iceServlet
  </wsa:To>
  <search:subID 
          xmlns:search="http://www.example.com/searcher">
      1
  </search:subID>
 </soapenv:Header>
 <soapenv:Body>
  <wsnt:SubscribeResponse xmlns:wsnt=
"http://docs.oasis-open.org/wsn/2004/06/wsn-WS-BaseNotificat
ion-1.2-draft-01.xsd">
   <wsnt:SubscriptionReference>
    <wsa:Address 
        xmlns:wsa="http://www.w3.org/2005/02/addressing">
http://localhost:8080/servlets-examples/servlet/ProducerServ
iceServlet
    </wsa:Address>
    <wsa:ReferenceProperties 
        xmlns:wsa="http://www.w3.org/2005/02/addressing">
      <sub:subscriptionID 
        xmlns:sub="http://www.example.com/searchService">
         1
      </sub:subscriptionID>
    </wsa:ReferenceProperties>
   </wsnt:SubscriptionReference>
  </wsnt:SubscribeResponse>
 </soapenv:Body>
</soapenv:Envelope>



Back to top


Receiving the response

Finally, to complete the circle, the consumer needs to accept the response and save the endpoint with the appropriate subscriber.


Listing 17. Accepting the response
				...
          SOAPConnection connection = 
                         soapConnFactory.createConnection();
          SOAPMessage reply = connection.call(message, 
                                               destination);
          
          SOAPBody replyBody = reply.getSOAPBody();
          
          SOAPElement replyBodyElement = 
           (SOAPElement)replyBody.getChildElements().next();
          String responseName = 
                            replyBodyElement.getLocalName();
          
          if (responseName.equals("SubscribeResponse")){
             SOAPElement responseElement = 
    (SOAPElement)replyBodyElement.getChildElements().next();
             consumers[thisConsumerId]
                  .setSubscriptionEndpoint(responseElement);
             resp.getWriter()
                           .print("Added new subscription");
          } else {
             resp.getWriter().print("Improper Response: "
                                             +responseName);
          }
       } catch (Exception e){
              e.printStackTrace(resp.getWriter());
       }
   }

   protected void doPost(HttpServletRequest req, 
                         HttpServletResponse resp) 
                      throws ServletException, IOException {
...

First, we check to make sure that the response is actually a SubscribeResponse. If the NotificationProducer has a problem, it produces a type of Fault message, instead. (See Resources for more information on notification faults.) If it is, we're simply grabbing its first child and adding it to the SubscriptionConsumer object.

In this case, we didn't have to interpret the SOAP header because we were dealing with a synchronous request, so we already knew which SubscriptionConsumer we were dealing with. That won't be the case when we receive a notification.



Back to top


Send a notification

At this point, we've created the subscription, so we know where to send any notifications, but we don't know how. To start with, let's look at an actual notification message.


Listing 18. The notification message
				<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
        xmlns:sat="http://example.org/searchSystem"
        xmlns:wsa="http://www.w3.org/2005/02/addressing"
        xmlns:wsnt=
"http://docs.oasis-open.org/wsn/2004/06/wsn-WS-BaseN-1.2-dra
ft-01.xsd">
    <soapenv:Header>
       <wsa:Action>
http://docs.oasis-open.org/wsn/2004/06/WS-BaseNotification/N
otify
       </wsa:Action>
       <wsa:To soapenv:mustUnderstand="1" 
        xmlns:wsa="http://www.w3.org/2005/02/addressing">
http://localhost:8080/servlets-examples/servlet/ConsumerServ
iceServlet
       </wsa:To>
       <search:subID 
          xmlns:search="http://www.example.com/searcher">
           1
       </search:subID>
    </soapenv:Header>
    <soapenv:Body>
       <wsnt:Notify>
          <wsnt:NotificationMessage>
              <wsnt:Topic Dialect="http://docs.oasis-ope
n.org/wsn/2004/06/TopicExpression/Simple">
                 cheese
              </wsnt:Topic>
              <wsnt:Message>
                <search:NewSearchResult>
                    The site ranked number one for this 
                    topic has changed.
                </search:NewSearchResult>
              </wsnt:Message>
          </wsnt:NotificationMessage>
       </wsnt:Notify>
    </soapenv:Body>
</soapenv:Envelope>

As in the SubscribeResponse, the header carries information about the consumer. The body of the message contains a Notify element, containing the topic and the actual message. The Notify element can also include the endpoint reference of the NotificationProducer, but we've left that out here.

(If the consumer had specified UseNotify as false, we'd simply send the search:NewSearchResult element, rather than the Notify element.)

The producer service generates this message for each consumer registered for the appropriate target.


Listing 19. Creating the notification message
				...
   public ProducerServiceServlet() {
      super();
      nextId = 1; 
      
      try {
         soapFactory = SOAPFactory.newInstance();
      } catch (Exception e){
         e.printStackTrace();
      }
   }

    private SOAPMessage makeNotification(int i){
       SOAPMessage message = null;
       try{
          MessageFactory messageFactory = 
                               MessageFactory.newInstance();
          message = messageFactory.createMessage();
              
          message = populateHeader(message, 
                       subscribers[i].getConsumerEndpoint(), 
  "http://docs.oasis-open.org/wsn/2004/06/WS-BaseNotificatio
n/Notify");

          SOAPBody body = message.getSOAPBody();
   
          SOAPElement bodyElement = 
              body.addChildElement(soapFactory.createName(
                                           "Notify", "wsnt", 
"http://docs.oasis-open.org/wsn/2004/06/wsn-WS-BaseNotificat
ion-1.2-draft-01.xsd"));

          SOAPElement notificationMessage = 
                 bodyElement.addChildElement(
                      "NotificationMessage", "wsnt", 
"http://docs.oasis-open.org/wsn/2004/06/wsn-WS-BaseNotificat
ion-1.2-draft-01.xsd");
               
          SOAPElement topicExpression = 
               notificationMessage.addChildElement("Topic");
          topicExpression.addAttribute(
                       soapFactory.createName("Dialect", "", 
"http://docs.oasis-open.org/wsn/2004/06/wsn-WS-BaseNotificat
ion-1.2-draft-01.xsd"), 
"http://docs.oasis-open.org/wsn/2004/06/TopicExpression/Simp
le");
          topicExpression.addTextNode(
                       subscribers[i].getTopicExpression());
              
          SOAPElement messageElement = 
              notificationMessage.addChildElement(
                 "Message", "wsnt", 
"http://docs.oasis-open.org/wsn/2004/06/wsn-WS-BaseNotificat
ion-1.2-draft-01.xsd");
          messageElement.addChildElement("NewSearchResult", 
               "search", "http://www.example.com/searches")
                 .addTextNode("The site ranked number one "+
                             "for this topic has changed.");
              
          message.saveChanges();
              
       } catch (Exception e){
             e.printStackTrace();
       }
       return message;
    }

   protected void doGet(HttpServletRequest req, 
                        HttpServletResponse resp) 
                      throws ServletException, IOException {

       String topic = req.getParameter("topic");
       for (int i=1; i < nextId; i++){
       
         if (topic.equals(subscribers[i]
                                    .getTopicExpression())){
            SOAPMessage message = makeNotification(i);
               
            try {
               message.writeTo(resp.getOutputStream());
            } catch (Exception e){
               resp.getWriter().print(e.getMessage());      
            }    
               
         } 
      }
  }
...

In the real world, the fact that a notification is necessary will likely come from a process, such as, in this case, one that monitors a search engine. To make things simple, however, we'll pull the appropriate topic in using the doGet() method. (You can create a form that calls the servlet or you can simply add "topic=mytopic" to the end of your URL.)

Once we have the topic, we can look at each of our subscribers to see whether they've subscribed to it. If they have, we'll use the makeNotification() method to create the appropriate SOAP message using their information. The process is virtually identical to the one we used to create the SubscribeResponse message, except, of course, that the payload is different.

Now we're ready to actually send the message. While it's tempting to simply use the SOAPConnection.call() method, as we did when subscribing, we can't actually do that here because notifications are one-way messages, meaning that the consumer doesn't actually return a response. Because the call() method waits for a response, that won't do.

So, in our case, we'll simply create the HTTP connection directly and write the message to it.


Listing 20. Writing to the HTTP connection
				...
       for (int i=1; i < nextId; i++){
       
          if (topic.equals(subscribers[i]
                                    .getTopicExpression())){
             SOAPMessage message=makeNotification(i);
             String destination=getDestination(message);
             resp.getWriter().println("Sending to "
                                              +destination);
             try{               

               URL url = new URL(destination);
               HttpURLConnection connection = 
                    (HttpURLConnection)url.openConnection();
               connection.setRequestMethod("POST");
               message.writeTo(
                              connection.getOutputStream());
               InputStream in = 
                           connection.getInputStream();//));
               in.close(); 
                
             } catch (Exception e){
               resp.getWriter().print(e.getMessage());      
             }    
               
          } 
       }
...
    private String getDestination(SOAPMessage message){
        try {
        
           Name address=soapFactory.createName("To", "wsa", 
                    "http://www.w3.org/2005/02/addressing");
           SOAPElement addressElement = 
              (SOAPElement)message.getSOAPHeader()
                          .getChildElements(address).next();
           
           return addressElement.getValue();
        
        } catch (Exception e){
           e.printStackTrace();
           return e.toString();
        }
 
    }
...

Before we can send anything, we need to know where it's going. So, first, we'll use the getDestination() method to analyze the actual message and return the value of the To element. Once we have that, we can open a URLConnection (or more specifically, HttpURLConnection, so we can set the request method to POST) and write the message to it. Note that just writing the message isn't enough; you must open and close the InputStream, even though there's no actual message to receive, or the message won't be sent.



Back to top


Receive the notification

We're almost there. Now we just have to set up the consumer to actually receive the notification, and we'll be finished.

We used the doGet() method for the initial form, but the notifications come as POST requests, so we'll use doPost() to handle the incoming message:


Listing 21. Handling the notification
				...
   private void Notify(String message){
      try {
        File file = new File("/home/myself/"+
         String.valueOf(System.currentTimeMillis())+".txt");
        file.createNewFile();
        FileWriter fwrite = new FileWriter(file);
        fwrite.write(message);
        fwrite.flush();
        fwrite.close();

      } catch (IOException e) {
        e.printStackTrace();
      }      
   }
   
   private int getIntendedSubscriber(SOAPMessage message){
      try {
        
        Name subId=soapFactory.createName("subID", "search", 
                         "http://www.example.com/searcher");
        SOAPElement subIdElement = 
              (SOAPElement)message.getSOAPHeader()
                            .getChildElements(subId).next();
           
        String subIdStr = subIdElement.getValue();
        return Integer.parseInt(subIdStr);

      } catch (Exception e){
        e.printStackTrace();
        return -1;
     }
   }
   
   protected void doPost(HttpServletRequest req, 
                         HttpServletResponse resp) 
                      throws ServletException, IOException {
        
       try {
             
          InputStream toSOAP = req.getInputStream();

          MessageFactory messageFactory = 
                               MessageFactory.newInstance();
          SOAPMessage message = 
                 messageFactory.createMessage(null, toSOAP);
            
          if (getAction(message).equals("Notify")){
             int subscriberId = 
                             getIntendedSubscriber(message);
             Notify(consumers[subscriberId].getEmail());
          }
            
       } catch (Exception e){
          e.printStackTrace(resp.getWriter());
       }
   }
...

Just as we did when we sent the original Subscribe message, we'll use the request to populate the SOAP message and check to make sure it's a type we're expecting. If it is, we'll use the getIntendedSubscriber() method to extract the subId value from the SOAP header. That gives us the index for the appropriate SubscriptionConsumer object, which gives us the appropriate e-mail.

In an application like this, we'd normally take that e-mail address and send a message, but we certainly don't have the space to go into that. Instead, a simple Notify() method has been provided so we can see that it's working and that every subscription to a particular topic created gives us a separate notification.



Back to top


Other aspects of WS-Notification

In this article, we set up a simple system for subscribing to and receiving notifications on a particular topic using the message patterns described in the WS-Notification family of specifications. We created the appropriate SOAP messages, sent them back and forth between Java servlet-based Web services, and analyzed their content.

But even after all that we've done here, we've still only scratched the surface of creating a system that takes advantage of WS-Notification. Here are some features to keep in mind:

  • Once the consumer has the endpoint of a subscription -- or more properly, a subscription manager -- he has the option to pause or resume it. He can also stop the subscription by destroying the WS-Resource that represents it or extend its lifetime by changing its TerminationTime resource property.
  • Topics don't always have to be arbitrary. WSRF specifies that resource properties should be specified as topics to which consumers can subscribe in order to receive notification of changes.
  • We've created a (very) rudimentary implementation here for the sole purpose of illustrating what actually goes on in the world of WS-Notification, but it isn't necessary to cobble together something more complete for our applications. The Globus Toolkit V4 Java Core includes a (nearly) complete implementation of WS-Notification, along with an implementation of WSRF. (The Globus Toolkit does not currently support brokered notifications, in which the publisher and the NotificationProducer are not the same entity.)

To get the most out of WS-Notification, it helps to understand not only the specifications but also the concepts behind WSRF. Check out Resources for developerWorks tutorials and other information that will help.




Back to top


Download

DescriptionNameSizeDownload method
Source codeproducer.wsdl6.17 KBHTTP
Information about download methods


Resources

The main location for WSN and WSRF documentation is at The Globus Alliance, but the latest specifications for WS-Notification can be found at Oasis. Documentation includes:

You can also get a good grounding in these specifications by reading the Understanding WSRF tutorial series:

  • Understanding WSRF, Part 1: Using WS-ResourceProperties

  • Part 2: Working with WS-Resources: WS-ResourceLifetime, WS-ServiceGroup and WS-BaseFaults

  • Part 3: Publish-subscribe with WS-Notification

  • Part 4: Using the WS Java Core classes

The actual recommendations for SOAP and WSDL are maintained by the World Wide Web Consortium, and IBM is one of the companies that proposed WS-Addressing. You can also find useful information here on developerWorks.

Get a look at using the Google Web services API with Building Web service applications with the Google API.

For a more complete and robust implementation of the WS-Notification family of specifications, download the Java WS Core.



About the author

Mike Weaver is a freelance writer and consultant with a background in distributed computing and application development for next-generation Internet and Web services. His background spans mechanical engineering and computer science. He enjoys spending time with his wife and two small children. He welcomes all feedback and can be reached at mike@mike-weaver.com.




Rate this page


Please take a moment to complete this form to help us better serve you.



 


 


Not
useful
Extremely
useful
 


Share this....

digg Digg this story del.icio.us del.icio.us Slashdot Slashdot it!



Back to top