Skip to main content

Web services programming tips and tricks: Learn simple, practical Web services design patterns, Part 1

Asynchronous Web services operations using JMS

James Snell (jasnell@us.ibm.com), Software Engineer, IBM
Author photo
James Snell is a member of the IBM Emerging Technologies Toolkit team and has spent the past few years focusing on emerging Web services technologies and standards. He maintains a weblog on developerWorks focused on emerging technologies.

Summary:  Learn how to apply well-defined, proven Web application design strategies to the world of Web services. This first tip in a series shows you how to implement asynchronous query operations using Java Messaging Service (JMS) queues.

Date:  19 Oct 2004
Level:  Intermediate
Activity:  5756 views

When you think about implementing Web services with SOAP, you probably have synchronous request-response-style operations in mind. A true Service-Oriented Architecture (SOA), however, is capable of embracing a much broader range of messaging patterns and design strategies. In this tip I begin a short series that focuses on the application of basic Web application design patterns to Web services applications. The patterns presented are not new -- in fact they've been used for more traditional Web applications for many years -- however, many developers either might not be aware of how to implement such strategies within the Web services realm or might not have fully considered how to apply those strategies. My goal is to present a collection of simple, straightforward design alternatives to the request-response model. In order to follow the example it helps to have at least a basic understanding of how to implement Web services in a J2EE environment.

Asynchronous query pattern

To kick things off, I explore the implementation of asynchronous request and response operations for the purpose of breaking up long-running operations to avoid time-outs or long hang-ups in code execution. The pattern that I implement is a familiar one if you have experience implementing asynchronous queries in traditional HTTP and HTML Web applications.


Figure 1. The asynchronous query pattern
The asynchronous query pattern

The flow of this pattern is simple:

  1. The requester submits a request to the Service Provider, who queues up the message and returns a correlation ID that the Requester can use later to check on the status of the request.
  2. The request processor dequeues the request and processes it. Typically the processing of the request is long-running. Once the processing is complete, the Processor queues a response message.
  3. At some indeterminate point in the future, the Requester asks the Service Provider if a response to the request is ready. If the response has been queued, the Provider returns that response back to the requester. If the response is not available, the Provider reports such to the Requester, who can choose to either cancel the request or continue to wait, polling the Provider at some selected interval until the response is available.

Kyle Brown has written an excellent article (see Resources) on the application of this pattern in J2EE Servlet applications for Java Ranch, a Java development resource Web site. In his article, Kyle adequately discusses the motivations and basic design issues involved in the basic implementation of this pattern. Not surprisingly, the implementation does not change very much in a Web services implementation of the pattern.


Design the service interface

To illustrate the asynchronous query pattern, I implement a simple example Web service that has absolutely no practical value beyond illustrating the concepts of the pattern. All the service does is transform three lowercase String input values to upper case after forcing a 10 second pause in the execution to simulate a long running process.

To implement this service, two Web service operations are exposed: submitRequest and checkResponse. What each of the operations does is self-explanatory. Listing 1 shows the WSDL describing the service interface.


Listing 1. AsyncService.wsdl

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions 
  targetNamespace="http://one.wspattern.developerworks.ibm.com" 
  xmlns:impl="http://one.wspattern.developerworks.ibm.com" 
  xmlns:intf="http://one.wspattern.developerworks.ibm.com" 
  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" 
  xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <wsdl:types>
  <schema 
    targetNamespace="http://one.wspattern.developerworks.ibm.com" 
    xmlns="http://www.w3.org/2001/XMLSchema" 
    xmlns:impl="http://one.wspattern.developerworks.ibm.com" 
    xmlns:intf="http://one.wspattern.developerworks.ibm.com" 
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <complexType name="ResponseCheck">
    <sequence>
     <element name="correlationID" nillable="true" type="xsd:string"/>
    </sequence>
   </complexType>
   <element name="ResponseCheck" nillable="true" type="impl:ResponseCheck"/>
   <complexType name="Response">
    <sequence>
     <element name="type" type="xsd:int"/>
     <element name="correlationID" nillable="true" type="xsd:string"/>
     <element name="refresh" type="xsd:int"/>
     <element name="a" nillable="true" type="xsd:string"/>
     <element name="b" nillable="true" type="xsd:string"/>
     <element name="c" nillable="true" type="xsd:string"/>
    </sequence>
   </complexType>
   <element name="Response" nillable="true" type="impl:Response"/>
   <complexType name="Request">
    <sequence>
     <element name="a" nillable="true" type="xsd:string"/>
     <element name="b" nillable="true" type="xsd:string"/>
     <element name="c" nillable="true" type="xsd:string"/>
    </sequence>
   </complexType>
   <element name="Request" nillable="true" type="impl:Request"/>
  </schema>
 </wsdl:types>

   <wsdl:message name="submitRequestRequest">
      <wsdl:part name="request" type="intf:Request"/>
   </wsdl:message>

   <wsdl:message name="checkResponseResponse">
      <wsdl:part name="checkResponseReturn" type="intf:Response"/>
   </wsdl:message>

   <wsdl:message name="checkResponseRequest">
      <wsdl:part name="check" type="intf:ResponseCheck"/>
   </wsdl:message>

   <wsdl:message name="submitRequestResponse">
      <wsdl:part name="submitRequestReturn" type="intf:Response"/>
   </wsdl:message>

   <wsdl:portType name="AsyncService">
      <wsdl:operation name="checkResponse" parameterOrder="check">
         <wsdl:input 
           message="intf:checkResponseRequest" 
           name="checkResponseRequest"/>
         <wsdl:output 
           message="intf:checkResponseResponse" 
           name="checkResponseResponse"/>
      </wsdl:operation>
      <wsdl:operation name="submitRequest" parameterOrder="request">
         <wsdl:input 
           message="intf:submitRequestRequest" 
           name="submitRequestRequest"/>
         <wsdl:output 
           message="intf:submitRequestResponse" 
           name="submitRequestResponse"/>
      </wsdl:operation>
   </wsdl:portType>

   <wsdl:binding name="AsyncServiceSoapBinding" type="intf:AsyncService">
      <wsdlsoap:binding 
        style="rpc" 
        transport="http://schemas.xmlsoap.org/soap/http"/>
      <wsdl:operation name="checkResponse">
         <wsdlsoap:operation soapAction=""/>
         <wsdl:input name="checkResponseRequest">
            <wsdlsoap:body 
              namespace="http://one.wspattern.developerworks.ibm.com" 
              use="literal"/>
         </wsdl:input>
         <wsdl:output name="checkResponseResponse">
            <wsdlsoap:body 
              namespace="http://one.wspattern.developerworks.ibm.com" 
              use="literal"/>
         </wsdl:output>
      </wsdl:operation>
      <wsdl:operation name="submitRequest">
         <wsdlsoap:operation soapAction=""/>
         <wsdl:input name="submitRequestRequest">
            <wsdlsoap:body 
              namespace="http://one.wspattern.developerworks.ibm.com" 
              use="literal"/>
         </wsdl:input>
         <wsdl:output name="submitRequestResponse">
            <wsdlsoap:body 
              namespace="http://one.wspattern.developerworks.ibm.com" 
              use="literal"/>
         </wsdl:output>
      </wsdl:operation>
   </wsdl:binding>

   <wsdl:service name="AsyncServiceService">
      <wsdl:port binding="intf:AsyncServiceSoapBinding" name="AsyncService">
         <wsdlsoap:address 
           location="http://localhost:9080/WSPattern1/services/AsyncService"/>
      </wsdl:port>
   </wsdl:service>

</wsdl:definitions>

There are a couple of things to note about this interface:

  1. The service uses an RPC/Literal encoding style.
  2. Both the submitRequest and checkResponse operations return an object called Response. There are two flavors of Response, identified by the type property. A type property value of 0 references a Refresh Response, and a type property value of 1 references a Request Response. Refresh Response indicates that a response is not yet available and that the requester should submit a new checkResponse operation no sooner than the value specified by the refresh property (equivalent to the HTTP META Refresh mechanism discussed in Kyle Brown’s article referenced above). A Request Response contains the three upper-cased input strings and represents the completion of the request processing.
  3. The Refresh Response includes a Correlation ID property whose value is used as the input for the checkResponse operation. This identifier is the only means for the client to correlate its initial request with the response. There are other ways to implement this, as I discuss a bit later.

Listing 2 shows a typical message exchange for this service.


Listing 2. AsyncService message exchange

Initial Request

<SOAP-ENV:Envelope 
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <SOAP-ENV:Body>
    <m:submitRequest 
      xmlns:m="http://one.wspattern.developerworks.ibm.com">
      <request>
        <a>String</a>
        <b>String</b>
        <c>String</c>
      </request>
    </m:submitRequest>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>


submitRequest Response

<soapenv:Envelope 
  xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soapenv:Header/>
  <soapenv:Body>
    <p155:submitRequestResponse 
      xmlns:p155="http://one.wspattern.developerworks.ibm.com">
      <submitRequestReturn>
        <type>0</type>
        <correlationID>1097517621904</correlationID>
        <refresh>10000</refresh>
        <a xsi:nil="true"/>
        <b xsi:nil="true"/>
        <c xsi:nil="true"/>
      </submitRequestReturn>
    </p155:submitRequestResponse>
  </soapenv:Body>
</soapenv:Envelope>


Initial checkResponse attempt, no response available, submitted after 10000 miliseconds

<soapenv:Envelope 
  xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soapenv:Header/>
  <soapenv:Body>
    <p155:checkResponseResponse 
      xmlns:p155="http://one.wspattern.developerworks.ibm.com">
      <checkResponseReturn>
        <type>0</type>
        <correlationID>1097517621904</correlationID>
        <refresh>10000</refresh>
        <a xsi:nil="true"/>
        <b xsi:nil="true"/>
        <c xsi:nil="true"/>
      </checkResponseReturn>
    </p155:checkResponseResponse>
  </soapenv:Body>
</soapenv:Envelope>


Second checkResponse attempt, submitted after 10000 miliseconds

<soapenv:Envelope 
  xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soapenv:Header/>
  <soapenv:Body>
    <p155:checkResponseResponse 
      xmlns:p155="http://one.wspattern.developerworks.ibm.com">
      <checkResponseReturn>
        <type>1</type>
        <correlationID xsi:nil="true"/>
        <refresh>0</refresh>
        <a>STRING</a>
        <b>STRING</b>
        <c>STRING</c>
      </checkResponseReturn>
    </p155:checkResponseResponse>
  </soapenv:Body>
</soapenv:Envelope>


Implement the service

Implementing the asynchronous query pattern service is a straightforward application of the Java Messaging Service. For the purposes of this example, I use the OpenJMS open source JMS provider implementation (see Resources) and the IBM® WebSphere® Application Server V5 (Application Server). I use the default OpenJMS configuration and implement a JSR-109-compliant J2EE Web service. I wrote the code for the example using WebSphere Studio Application Developer (Application Developer) V5.1, which you can download from developerWorks (see Resources). I also provide an EAR file (see Resources) in case you don't have access to Application Developer.

There are two server-side components that need to be implemented: the request processor and the Web service implementation. The request processor is tasked with the job of pulling requests off the queue and executing the "long running" 10 second capitalization process. The service implementation is tasked with receiving requests from Web services clients and queuing them up for processing and for delivering responses to clients following a checkResponse operation.

In a typical J2EE application, the request processor would be implemented as a Message Driven Bean according to the JMS specification. In this example, I use a simple HTTP Servlet that implements the JMS MessageListener interface. The servlet is configured to init on server startup, making the listener available for all requests that might come in. As soon as a request is queued, it is delivered to the listening servlet.


Listing 3. JNDIListenerServlet.java

package com.ibm.developerworks.wspattern.one.helper;
import java.util.Enumeration;
import java.util.Hashtable;

import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueReceiver;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.naming.Context;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;

public class JNDIListenerServlet 
  extends HttpServlet 
  implements Servlet, MessageListener {

  private Context context;
  private QueueConnection connection;
  private QueueSession session;
  private Queue queue;
  private QueueReceiver receiver;

  public void init() 
    throws ServletException {

     super.init();
    
     try {
       context = JNDIHelper.getInitialContext();
       connection = JNDIHelper.getConnection(context);
       session = JNDIHelper.getSession(connection);
       queue = JNDIHelper.getQueue(context);
       receiver = JNDIHelper.getQueueReceiver(session, queue);
       receiver.setMessageListener(this);
       System.out.println("Listener servlet is Listening");
     } catch (Exception e) {}
  }

  public void destroy() {
    try {
      connection.close();
    } catch (Exception e) {}
  }

  public void onMessage(Message message) {
    try {
      System.out.println("Processing message " + message.getJMSCorrelationID());
      Thread.sleep(10 * 1000); // sleep for ten seconds
      Queue responseQueue = JNDIHelper.getResponseQueue(context);
      QueueSender sender = JNDIHelper.getQueueSender(session,responseQueue);
      MapMessage request = (MapMessage)message;
      MapMessage response = session.createMapMessage();
      response.setJMSCorrelationID(request.getJMSCorrelationID());
      for (Enumeration e = request.getMapNames(); e.hasMoreElements();) {
        String name = (String) e.nextElement();
        try {
          response.setString(
            name, 
            request.getString(name).toUpperCase());
        } catch (Exception ex) {}
      }
      sender.send(response);
    } catch (Exception e) {
      System.out.println("==================");
      try {
        System.out.println(
          "THERE WAS AN ERROR PROCESSING THE MESSAGE! " + 
          message.getJMSCorrelationID());
      } catch (Exception ex) {}
      e.printStackTrace(System.out);
      System.out.println("==================");
    }
  }

}

The JNDIListenerServlet and the service implementation both use a simple helper class created for this application that hides away the details of initializing the JMS connection and session.


Listing 4. JNDIHelper.java

package com.ibm.developerworks.wspattern.one.helper;

import java.util.Hashtable;

import javax.jms.JMSException;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueReceiver;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;


public class JNDIHelper {

  private static Context context;

  public static Context getInitialContext() 
    throws NamingException {
      if (context == null) {
        Hashtable properties = new Hashtable();
        properties.put(
          Context.INITIAL_CONTEXT_FACTORY, 
          "org.exolab.jms.jndi.InitialContextFactory");
        properties.put(
          Context.PROVIDER_URL,
          "rmi://localhost:1099");
        context = new InitialContext(properties);
      }
      return context;
  }

  public static QueueConnection getConnection(
    Context context) 
      throws NamingException, 
             JMSException {
        QueueConnectionFactory factory = 
          (QueueConnectionFactory) context.lookup(
            "JmsQueueConnectionFactory");
        QueueConnection connection = factory.createQueueConnection();
        connection.start();
        return connection;
  }

  public static QueueSession getSession(QueueConnection connection) 
    throws JMSException {
      QueueSession session = 
        connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
      return session;
  }

  public static Queue getQueue(Context context) 
    throws NamingException {
      Queue queue = (Queue) context.lookup("queue1");
      return queue;
  }  
  
  public static Queue getResponseQueue(Context context) 
    throws NamingException {
      Queue queue = (Queue) context.lookup("queue2");
      return queue;
  }
  
  public static QueueSender getQueueSender(
    QueueSession session, 
    Queue queue) 
      throws JMSException {
        QueueSender sender = session.createSender(queue);
        return sender;
  }
  
  public static QueueReceiver getQueueReceiver(
    QueueSession session,
    Queue queue) 
      throws JMSException {
        QueueReceiver receiver = session.createReceiver(queue);
        return receiver;
  }

  public static QueueReceiver getQueueReceiver(
    QueueSession session,
    Queue queue,
    String selector) 
      throws JMSException {
        QueueReceiver receiver = session.createReceiver(queue, selector);
        return receiver;
  }
    
}

Once the servlet is created, edit the Web application’s web.xml so that the servlet is configured to initialize on server startup. When the servlet initializes, it opens a JMS connection and registers itself as a listener on OpenJMS’s default message queue.

The second step is to create the service implementation. This is where working with Application Developer comes in handy because of the various artifacts that need to be generated in order to get a working JSR-109 Web service. Here I focus strictly on the service implementation class. Please refer to the code that you can download by clicking on the Code icon at the top or bottom of this tip to see the other various Java and XML configuration files that Application Server requires.


Listing 5. AsyncService.java

package com.ibm.developerworks.wspattern.one;

import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueReceiver;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.naming.Context;

import com.ibm.developerworks.wspattern.one.helper.JNDIHelper;

public class AsyncService {

  public Response submitRequest(Request request) {
    Response response = null;
    try {
      Context context = JNDIHelper.getInitialContext();
      QueueConnection connection = JNDIHelper.getConnection(context);
      QueueSession session = JNDIHelper.getSession(connection);
      Queue queue = JNDIHelper.getQueue(context);
      QueueSender sender = JNDIHelper.getQueueSender(session,queue);
      
      MapMessage message = session.createMapMessage();
      String corrID = Long.toString(System.currentTimeMillis());
      message.setJMSCorrelationID(corrID);
      
      message.setString("one", request.getA());
      message.setString("two", request.getB());
      message.setString("three", request.getC());
      sender.send(message);
      
      response = new Response();
      response.setType(Response.TYPE_REFRESH);
      response.setCorrelationID(corrID);
      response.setRefresh(10 * 1000);
      return response;
    } catch (Exception e) {
      response = new Response();
      response.setType(Response.TYPE_RESPONSE);
      response.setA(e.getMessage());
    }
    return response;
  }

  public Response checkResponse(ResponseCheck check) {
    String corrID = check.getCorrelationID();
    Response response = null;
    try {
      Context context = JNDIHelper.getInitialContext();
      QueueConnection connection = JNDIHelper.getConnection(context);
      QueueSession session = JNDIHelper.getSession(connection);
      Queue queue = JNDIHelper.getResponseQueue(context);
      String selector = "JMSCorrelationID = '" + corrID + "'";
      QueueReceiver receiver = JNDIHelper.getQueueReceiver(session, queue, selector);
      Message message = receiver.receiveNoWait();
      if (message == null) {
        response = new Response();
        response.setType(Response.TYPE_REFRESH);
        response.setRefresh(10 * 1000);
        response.setCorrelationID(corrID);
      } else {
        MapMessage resp = (MapMessage)message;
        response = new Response();
        response.setType(Response.TYPE_RESPONSE);
        response.setA(resp.getString("one"));
        response.setB(resp.getString("two"));
        response.setC(resp.getString("three"));
      }
    } catch (Exception e) {}
    return response;
  }
}

There really shouldn’t be any surprises here. The submitRequest method prepares a JMS MapMessage based on the input parameters. This map message consists of three string values. The message is then sent to the queue and a Refresh Response containing the correlation ID is prepared and returned to the client.

The checkResponse operation grabs the correlation ID from the input parameters and opens a connection to the response queue, asking it to deliver any messages with the appropriate correlation ID. If no message exists, the operation does not wait around for one; it simply prepares another Refresh Response with a new refresh interval period and returns that back to the caller. If a message is delivered, the operation prepares and returns an appropriate Request Response.

Deploy the Web service, start your OpenJMS and WebSphere servers, and your asynchronous query pattern Web service will be up and running.


Summary

Central to the success of the asynchronous query pattern is the ability for the Web services client and service provider to correlate requests and responses. In the example presented here, you can invent a simple, yet one-off correlation ID and refresh timer mechanism that is specific to this single example application. It would be possible, however, to use some combination of WS-* specifications to achieve the same result. A WS-Addressing Endpoint Reference or a WS-Transaction Coordination Context could have easily contained the correlation ID and refresh timer values. Regardless, use of this pattern is specific to your particular applications, and regardless of whether or not you use standard SOAP header elements and various WS-* specifications, the behavior that each of your operations implements must be well-defined and well-documented.

Further, the example implemented here uses a traditional SOAP request-response messaging pattern to submit requests and receive responses. Alternatively, it would be possible to use a REST-style model in which requests are HTTP POSTed to a servlet and responses are retrieved using an HTTP GET request. Either approach is equally valid and has its relative strengths and weaknesses compared to one another and should be selected depending on the unique needs of your application. For instance, should your checkResponse operation require the use of WS-Security-based authentication, using a REST-style interaction pattern would not make sense.

Finally, you could easily imagine extending the scope of this example to allow requesters to submit detailed status queries on long-running operations or even to cancel operations that are in progress. Illustrating these various possibilities is beyond the scope of this article so I leave it as an exercise for you to explore on your own.



Downloads

DescriptionNameSizeDownload method
WebSphere deployable EAR filews-tip-altdesign1ear.ear701 KB HTTP
Example program source filesws-tip-altdesign1code.zip719 KB HTTP

Information about download methods


Resources

About the author

Author photo

James Snell is a member of the IBM Emerging Technologies Toolkit team and has spent the past few years focusing on emerging Web services technologies and standards. He maintains a weblog on developerWorks focused on emerging technologies.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=SOA and Web services, Sample IT projects
ArticleID=18051
ArticleTitle=Web services programming tips and tricks: Learn simple, practical Web services design patterns, Part 1
publish-date=10192004
author1-email=jasnell@us.ibm.com
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers