Implementing an SAAJ-based Web Service

To see SAAJ in action, you will now examine a service example that interacts with the agency EJBs you saw earlier in the case study to list the jobs available at a particular location. The server consists of a servlet that uses SAAJ to receive and unpack the SOAP messages containing the location name and the request type. In response, the server will retrieve the job information and package it up as an XML response for the client.

Processing a SOAP Message Using SAAJ

The implementation for an SAAJ-based Web Service takes the form of an ordinary Java servlet. The servlet runs inside a servlet container and processes the SOAP message using the SAAJ API. HTTP-based SOAP messages are delivered to the doPost method of a servlet. For the service example, this method will need to

  • Retrieve the MIME headers from the HTTP request. These are needed to help generate the SAAJ representation of the SOAP message.

  • Extract the SOAP message from the HTTP request.

  • Convert the XML-encoded request type and location into a Java representation.

  • Locate the jobs through the agency EJBs.

  • Convert the Java representation of the jobs into XML.

  • Send the XML-encoded job list back top the caller.

There are two main parts to this process—manipulating the message and its contents and marshaling in to and out of XML. The service example contains two classes split along these lines. The LocationInformationService class is the servlet-based service class that manipulates the message while the ServerMarshaler takes care of converting between the Java and XML representations of the data passed between client and server.

Receiving and Accessing a SOAP Message Using SAAJ

To process a SOAP message containing a location at which to look for jobs, your servlet must first override the doPost method:

public class LocationInformationService extends HttpServlet
{
    public void doPost(HttpServletRequest request, HttpServletResponse response)
                                                 throws ServletException, IOException
    {
        ...
    }
    ...
}

The servlet request contains the SOAP message from the client. However, you don't want to have to process it all yourself, so at this point you can start to use the SAAJ APIs. The first thing to do is to obtain information about the MIME format of the request. This information allows SAAJ to determine the different segments of the message shown previously in Figure 21.2 and to handle any attachments that may form part of the message. To retrieve this information, you create an instance of javax.xml.soap.MimeHeaders from the HTTP headers contained in the HttpServletRequest object by retrieving the headers from the request and adding them to the MimeHeaders. Listing 21.1 shows a helper method—getMimeHeaders—that does this.

Listing 21.1. Retrieving the MIME Headers
public class LocationInformationService extends HttpServlet
{
    public void doPost(HttpServletRequest request, HttpServletResponse response)
                                                 throws ServletException, IOException
   {
      MimeHeaders mimeHeaders = getMimeHeaders(request);
      ...
   }
   ...

   private MimeHeaders getMimeHeaders(HttpServletRequest request)
							{
							Enumeration httpHeaders = request.getHeaderNames();
							MimeHeaders mimeHeaders = new MimeHeaders();
							while (httpHeaders.hasMoreElements())
							{
							String httpHeaderName = (String)httpHeaders.nextElement();
							String httpHeaderValue = request.getHeader(httpHeaderName);
							StringTokenizer httpHeaderValues =
							new StringTokenizer(httpHeaderValue, ",");
							while (httpHeaderValues.hasMoreTokens())
							{
							String headerValue = httpHeaderValues.nextToken().trim();
							mimeHeaders.addHeader(httpHeaderName, headerValue);
							}
							}
							return mimeHeaders;
							}
}

The MIME header information can be used by an instance of javax.xml.soap.MessageFactory to extract the SOAP message from the requests input stream. You pass the input stream from the request, together with the MIME headers, to the createMessage method on the MessageFactory instance. This method processes the stream and creates an instance of SOAPMessage. Listing 21.2 shows another helper method for your servlet—extractSoapMessage—that retrieves the SOAPMessage from the request.

Listing 21.2. Retrieving the SOAP Message Sent by the Client
public class LocationInformationService extends HttpServlet
{
    public void doPost(HttpServletRequest request, HttpServletResponse response)
                                                 throws ServletException, IOException
    {
        ...
        SOAPMessage soapRequest = extractSoapMessage(mimeHeaders, request);
        ...
    }
    ...
    private SOAPMessage extractSoapMessage(MimeHeaders mimeHeaders, HttpServletRequest
 request)
							throws SOAPException,
 IOException
							{
							InputStream inStream = request.getInputStream();
							MessageFactory factory = MessageFactory.newInstance();
							SOAPMessage message = factory.createMessage(mimeHeaders, inStream);
							return message;
							}
}

After you have the SOAPMessage containing the request, you need to unmarshal the XML-encoded request type and location. The ServerMarshaler that forms the other part of the Web Service defines methods to perform this manipulation. Listing 21.3 shows how the ServerMarshaler is used to retrieve the information which is subsequently checked before being passed to an agency EJB.

Listing 21.3. Retrieving the Request Type and Location
public class LocationInformationService extends HttpServlet
{
    public void doPost(HttpServletRequest request, HttpServletResponse response)
                                                 throws ServletException, IOException
    {
        ...
        String requestType =
							ServerMarshaler.getRequestTypeFromMessage(soapRequest);
							String location = ServerMarshaler.getLocationFromMessage(soapRequest);
							if (requestType == null || requestType.length() == 0)
							{
							log("LocationInformationService: no request type provided");
							return;
							}
							if (location == null || location.length() == 0)
							{
							log("LocationInformationService: no location provided");
							return;
        }
        ...
    }
    ...
}

Now that you have the request type and the location, the next step is to generate the response. The response will need to be passed back to the client in another SOAPMessage, so before generating the response you should obtain a message to hold it. Listing 21.4 shows this.

Listing 21.4. Creating a SOAPMessage to Send Back to the Client
public class LocationInformationService extends HttpServlet
{
    public void doPost(HttpServletRequest request, HttpServletResponse response)
                                                 throws ServletException, IOException
    {
        ...
        MessageFactory factory = MessageFactory.newInstance();
							SOAPMessage soapResponse = factory.createMessage();
        ...
    }
    ...
}

As with many other factories under J2EE, the way you obtain a factory depends on your relationship with a container. If you are running outside of a container, you can simply create a new instance of the factory:

MessageFactory messageFactory = MessageFactory.newInstance();

Alternatively, if such a factory is registered with the container, it can be obtained through JNDI. As with all such resources, if your client is long-running, you should close and release the connection when you are no longer using it.

This SOAPMessage can then be filled with the list of jobs. This involves calling the findByLocation() method of the Job entity EJB which takes the name of a location and returns a collection containing the jobs at that location. The ServerMarshaler also takes responsibility for converting the list of jobs into XML-encoded format through its addJobsToMessage() method. The creation of the response message is shown in Listing 21.5.

Listing 21.5. Populating the SOAPMessage with Jobs
public class LocationInformationService extends HttpServlet
{
    static final String jobJNDI = "java:comp/env/ejb/JobLocal";

    public void doPost(HttpServletRequest request, HttpServletResponse response)
                                                 throws ServletException, IOException
    {
        ...
        InitialContext ic = new InitialContext();
							if (requestType.equalsIgnoreCase("jobsAtLocation"))
							{
							String[] jobs = null;
							Object lookup = ic.lookup(jobJNDI);
							JobLocalHome jobHome = (JobLocalHome)PortableRemoteObject.narrow(lookup,
 JobLocalHome.class);
							Collection col = jobHome.findByLocation(location);
							Iterator it = col.iterator();
							jobs = new String[col.size()];
							for (int i = 0; i < col.size(); ++i)
							{
							JobLocal job = (JobLocal)it.next();
							jobs[i] = job.getCustomer() + "/" + job.getRef();
							}
							ServerMarshaler.addJobsToMessage(jobs, soapResponse);
							}
        ...
    }
    ...
}

After you have the SOAPMessage containing the receipt, you then need to send it back to the caller using the HttpServletResponse. Before sending any data, you must set the status of the call to HttpServletResponse.SC_OK and set the "content-type" HTTP header to be 'text/xml; charset="utf-8"' to indicate that the message contains XML. You can then get the output stream from the response and write the message contents using the writeTo method on the SOAPMessage. Flush the output stream after sending the message to ensure that it is fully sent to the client. This final set of message processing steps is shown in Listing 21.6.

Listing 21.6. Returning the SOAPMessage to the Client
public class LocationInformationService extends HttpServlet
{
    public void doPost(HttpServletRequest request, HttpServletResponse response)
                                                 throws ServletException, IOException
    {
        ...
        response.setStatus(HttpServletResponse.SC_OK);
							response.setHeader("content-type", "text/xml; charset="utf-8"");
							OutputStream outStream = response.getOutputStream();
							soapMessage.writeTo(outStream);
							outStream.flush();
        ...
    }
    ...
}

With the addition of suitable exception handling and import statements, this concludes the code for the message handling. This service depends on the use of the ServerMarshaler, so you will examine this next.

Server-side Marshaling Between Java and XML

When using JAX-RPC, the tools generate marshaling classes that marshal data between Java and XML. There is the same need when creating SAAJ messages, but there is no strict framework as there is with JAX-RPC. The SAAJ API provides methods for you to manipulate the contents of the SOAP envelope so that you can add your own XML documents to the SOAP body. However, you are faced with some choices:

  • SAAJ provides a custom API for populating SOAPMessages and accessing their contents. Although this is slightly quirky, it ensures that the contents of the message are correctly scoped to the appropriate namespaces.

  • You can build your message using the standard XML DOM API as the message contents can be treated as DOM Nodes. If you are more familiar with DOM you may find this more straightforward, but there is more scope for errors in namespace manipulation.

  • Another alternative would be to use the Java Architecture for XML Binding to provide automated marshaling between Java and XML. However, JAXB is not included in J2EE 1.4 so, again, this is something that could be used at a subsequent release (or you could use the implementation in the Java Web Services Developer Pack).

For this example, you will use the SAAJ API to create an XML document within the SOAP envelope. This is what the ServerMarshaler class does. The SAAJ API provides access to the different parts of the SOAP envelope, as shown in Figure 21.3. A SOAP message consists of a SOAP part and a set of optional attachments. To populate or process a SOAP message, you need to retrieve the SOAP part represented by the javax.xml.soap.SOAPPart class. You can retrieve the SOAP part through the getSOAPPart method on the SOAPMessage. You can then retrieve the SOAP envelope, represented by the javax.xml.soap.SOAPEnvelope class, from within the SOAP part using the getEnvelope method:

SOAPEnvelope soapEnvelope = soapMessage.getSOAPPart().getEnvelope();

Figure 21.3. The parts of a SOAP message accessible through SAAJ.


From within the SOAPEnvelope, you can retrieve representations of the SOAP header (javax.xml.soap.SOAPHeader) and the SOAP body (javax.xml.soap.SOAPBody). Here's an example:

SOAPBody soapBody = soapEnvelope.getBody();

The next steps depend on whether you are processing a SOAPMessage you have been sent or creating a new one. The first thing the server needs to do is extract information from the SOAPMessage sent by the client. Listing 21.7 shows an example of the message contents sent by a client. The message body contains two elements—one for the request type and another for the location.

Listing 21.7. Request Message Contents
<agency:request xmlns:agency="http://J2EE21/agency">
    <agency:type>jobsAtLocation</agency:type>
    <agency:location>London</agency:location>
</agency:request>

To process this message, you will have to obtain a child of the body element named <request> and then retrieve specific children of that <request> node called <type> and <agency>. However, the SAAJ API provides only the SOAPElement.getChildElements() method that returns an Iterator for the collection of child elements matching the given name. To extract the single element you expect, you simply iterate onto the first element and cast this to be a SOAPElement. The ServerMarshaler class contains a helper method, getChildElementByName(), to perform this common set of steps for you as shown in Listing 21.8.

Listing 21.8. getChildElementByName Method from ServerMarshaler Class
private static SOAPElement getChildElementByName(Name name, SOAPElement element)
                              throws SOAPException
{
  Iterator childIterator = element.getChildElements(name);

  if (childIterator.hasNext())
  {
    SOAPElement childElement = (SOAPElement)childIterator.next();

    return childElement;
  }
  else
  {
    throw new SOAPException("element " + name.getQualifiedName() + " is missing");
  }
}

You may have noticed the use of the javax.xml.soap.Name type as a parameter to the getChildElementByName() method. The SAAJ API uses these specifically qualified name constructs rather than strings to enforce correct namespace usage. Any time an element is created within the SOAPEnvelope, a Name is required. The names for your messages Name need to be defined within a specific namespace, such as "http://J2EE21/agency".

Armed with the getChildElementByName() method and the SAAJ methods you have seen so far, you can now retrieve the request type from the message. The ServerMarshaler method that does this, getRequestTypeFromMessage(), is shown in listing 21.9. This method takes the SOAPMessage retrieved from the message factory and extracts the request type (in this case 'jobsAtLocation'). A Name is first created representing the <request> element using the SOAPEnvelope's createName() method. This name is passed to the getChildElementByName() method along with the SOAPBody. This works since SOAPBody is a subclass of SOAPElement and so it can be processed by getChildElementByName(). This is then repeated to obtain the <type> child element from under the <request>. The text contents of the <type> element can then be retrieved using the SOAPElement.getValue() method. The code to retrieve the <location> is almost identical.

Listing 21.9. getRequestTypeFromMessage() Method from ServerMarshaler Class
  ...
  static final String nsAgency = "http://J2EE21/agency";
  static final String prefixAgency = "agency";

  public static String getRequestTypeFromMessage(SOAPMessage soapMessage) throws SOAPException
  {
    SOAPEnvelope soapEnvelope = soapMessage.getSOAPPart().getEnvelope();
    SOAPBody soapBody = soapEnvelope.getBody();

    // Retrieve the request element from under the body
    Name requestName = soapEnvelope.createName("request", prefixAgency, nsAgency);
    SOAPElement soapRequest = getChildElementByName(requestName, soapBody);
    // Retrieve the type element from under the request
    Name typeName = soapEnvelope.createName("type", prefixAgency, nsAgency);
    SOAPElement soapType = getChildElementByName(typeName, soapRequest);
    String type = soapType.getValue();

    return type;
  }
  ...

The other marshaling required on the server is to convert the array of job strings into XML. Recall from the application code in Listing 21.5 that the addJobsToMessage() method is passed a SOAP message to populate. The code for this method is shown in Listing 21.10. You can retrieve the SOAPEnvelope from the message passed and subsequently the SOAPBody. You can then add a new SOAPBodyElement using the addBodyElement method to represent the top level XML <response> element. You must pass an appropriate Name for the newly created element. This must be obtained from the SOAPEnvelope of the message that will contain the element. The return value from addBodyElement() is a SOAPElement representing the new node.

Listing 21.10. addJobsToMessage() Method from ServerMarshaler Class
  ...
  static final String nsAgency = "http://J2EE21/agency";
  static final String prefixAgency = "agency";

  public static void addJobsToMessage(String[] jobs, SOAPMessage soapMessage) throws
 SOAPException
  {
    SOAPEnvelope soapEnvelope = soapMessage.getSOAPPart().getEnvelope();
    SOAPBody soapBody = soapEnvelope.getBody();

    // Create a response element under the body
    Name responseName = soapEnvelope.createName("response", prefixAgency, nsAgency);
    SOAPBodyElement soapResponse = soapBody.addBodyElement(responseName);

    // Indicate how many job elements there are in the response
    Name numJobsName = soapEnvelope.createName("numJobs", prefixAgency, nsAgency);
    SOAPElement soapNumJobs = soapResponse.addChildElement(numJobsName);
    soapNumJobs.addTextNode("" + jobs.length);
    // Create a set of job elements under the response
    Name jobsName = soapEnvelope.createName("jobs", prefixAgency, nsAgency);
    SOAPElement soapJobs = soapResponse.addChildElement(jobsName);

    Name jobName = soapEnvelope.createName("job", prefixAgency, nsAgency);

    for (int i = 0; i < jobs.length; i++)
    {
      SOAPElement soapJob= soapJobs.addChildElement(jobName);
      soapJob.addTextNode(jobs[i]);
    }
  }
  ...

You create elements to represent the list of jobs (<jobs>) and the number of jobs in that list (<numjobs>) below the <response> element by using the SOAPBodyElement's addChildElement method. These new elements are of type SOAPElement. As you iterate through the list of jobs, you add one <job> element for each job below <jobs>. You add the text for the <numJobs> and <job> elements using the addTextNode method on SOAPElement. Once again, the created elements must be associated with the correct XML namespace by creating a Name for them.

Note that during all of this, javax.xml.soap.SOAPExceptions might be thrown by the various methods and constructors used. Please refer to the SAAJ API documentation for specific details.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.226.120