Java Web Programming with Eclipse contents
Last modified February 28, 2011 11:03 am

back next

Web Services (continued)

Video

A Web Service to Publish News Items

In this section we add a servlet to the publisher application that will enable connecting clients to add news items to the publisher application's news feed.

Create a new package in the publisher project called publisher.ws and create a publish news item service servlet in this new package with the following implementation.

package publisher.ws;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;

import publisher.data.NewsItem;
import publisher.data.NewsItemDAO;

public class PublishNewsItemService extends HttpServlet {
   private Logger logger = Logger.getLogger(this.getClass());

   protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
   throws ServletException, IOException {
      logger.debug("doPost()");

      // Read the XML request document.
      BufferedReader br = req.getReader();
      SAXBuilder builder = new SAXBuilder();
      Document requestDocument = null;
      try 
      {
         requestDocument = builder.build(br);
      } catch (JDOMException e) {
         throw new RuntimeException(e);
      }

      // Extract title and link from request.
      Element item = requestDocument.getRootElement();
      Element titleElement = item.getChild("title");
      String title = titleElement.getText();
      Element linkElement = item.getChild("link");
      String link = linkElement.getText();

      // Create a news item from submitted data.
      NewsItem newsItem = new NewsItem();
      newsItem.setTitle(title);
      newsItem.setUrl(link);
      new NewsItemDAO().create(newsItem);

      // Create response document with id of newly 
      // created news item.
      Element idElement = new Element("id");
      idElement.addContent(newsItem.getId().toString());
      Document responseDocument = new Document(idElement);
      StringWriter sw = new StringWriter();
      XMLOutputter outputter = new XMLOutputter();
      outputter.output(responseDocument, sw);
      String responseDocumentString = sw.toString();

      // Return response document.
      byte[] responseBytes = responseDocumentString.getBytes("UTF-8");
      resp.setContentLength(responseBytes.length);
      resp.setContentType("text/xml");
      OutputStream os = resp.getOutputStream();
      os.write(responseBytes);
      os.flush();
   }
}

Observe that the publish servlet implements the doPost method and does not implement the doGet method. This means that clients of the publish web service will submit requests using HTTP POST messages, which means they will submit news item data in the body of the HTTP request messages. We will follow the most common convention, which is to structure the submitted data as an XML document. An example XML document that the client will submit is as follows.

<item>
   <title>Yahoo home page</title>
   <link>http://yahoo.com/</link>
</item>

When the doPost method is called, we extract the XML data with the SAXBuilder of the JDOM library. This is done with the following code.

      // Read the XML request document.
      BufferedReader br = req.getReader();
      SAXBuilder builder = new SAXBuilder();
      Document requestDocument = null;
      try 
      {
         requestDocument = builder.build(br);
      } catch (JDOMException e) {
         throw new RuntimeException(e);
      }

The builder takes as an argument a BufferedReader, which provides access to the character data coming in from the client. The builder creates an object representation of the XML document and returns a reference to this object, which is of type Document. The build method of the builder object extracts the data from the buffered reader passed into it and constructs the document object. This operation relies on extracting data from an underlying communications socket, which may fail if there is a network or other communications error. The build operation may also fail if the incoming characters can not be parsed as an XML document. For these reasons, the build operation throws a checked exception that we handled with a try-catch block.

After the SAX builder builds an object representation of the submitted data in the form of an instance of the document class, we extract the title and link strings for the news item. To do this, we first obtain a reference to the root element of the request document, and then we access the children of the root elememnt, which are the title and link elements.

      // Extract title and link from request.
      Element item = requestDocument.getRootElement();
      Element titleElement = item.getChild("title");
      String title = titleElement.getText();
      Element linkElement = item.getChild("link");
      String link = linkElement.getText();

After we have the title and link for the news item, we use it to create an instance of the NewsItem class. This news item instance is passed into the create method of the newsItemDAO in order to store the news item data in the database.

      // Create a news item from submitted data.
      NewsItem newsItem = new NewsItem();
      newsItem.setTitle(title);
      newsItem.setUrl(link);
      new NewsItemDAO().create(newsItem);

The create method has a side effect of creating and setting the id of the news item. The client needs this id in order to remove the news item from the RSS feed at some point in the future. For this reason, we return the id of the newly created news item to the client.

To return the news item id to the client, we follow the convention that data be exchanged within XML documents. The following XML document is an example of what we would return to the client, assuming that the id of the newly created news item is 3.

<?xml version="1.0" encoding="utf-8"?> 
<id>3</id>

It is possible to create this XML document simply with the following code.

String responseDocString = 
	"<?xml version=\"1.0\" encoding=\"utf-8\"?>" + 
"<id>" + newsItem.getId() + "</id>";

However, we use JDOM library to create the XML response document in order to provide more consistency in the code and because it provides us with a better foundation to create more complicated documents in the future. The following code is resposible for contructing the XML document comprising the response from the server.

      // Create response document with id of newly 
      // created news item.
      Element idElement = new Element("id");
      idElement.addContent(newsItem.getId().toString());
      Document responseDocument = new Document(idElement);
      StringWriter sw = new StringWriter();
      XMLOutputter outputter = new XMLOutputter();
      outputter.output(responseDocument, sw);
      String responseDocumentString = sw.toString();

In the above, we construct an instance of the element class to represent the id element that will contain the string representation of the id we created for the news item. Because XML documents must have root elements, the contructor of the document class naturally takes an element in its contructor. This is the reason we pass the id element to the constructor of the document class to create the object the represents our response. We then use the XML outputter class to serialize the XML document into a string. It is this string that we will return to the client.

Finally, we return the response document to the client. When doing this, we need to pay attention to the encoding of the characters. The default character encoding for XML is UTF-8, which all XML parsers are required to support. For this reason, it is safest to return the XML document in the UTF-8 character encoding. The following code shows the process of returning the response to the client.

      // Return response document.
      byte[] responseBytes = responseDocumentString.getBytes("UTF-8");
      resp.setContentLength(responseBytes.length);
      resp.setContentType("text/xml");
      OutputStream os = resp.getOutputStream();
      os.write(responseBytes);
      os.flush();

We start by invoking the getBytes method of the string representing the request document, passing into this method the argument UTF-8. This returns a byte array containing the raw binary representation of the message data. However, before we send the resulting bytes, we first set the HTTP content length and content type headers by calling the methods setContentLength and setContentType, respectively, on the response object. We then pass this byte array into the write method the output stream from the response object that was passed into our doPost method. When we invoke the write command on the output stream in this way, Tomcat first sends the HTTP status line, followed by the response headers (including our content-length and content-type headers), followed by the bytes in the array. This sequence of actions is important to keep in mind when developing server-side logic; confusion on this point can be the source of incorrect logic.

Note that if we omit the content length header in the response, Tomcat will use the chunked encoding format to send the response document. In order to keep the client code simple, we avoid the chunked encoding format by setting the content length explicitly.

The following XML fragment needs to be added to the publisher's deployment descriptor to configure the publish servlet.

   <servlet>
      <servlet-name>publish-service</servlet-name>
      <servlet-class>publisher.ws.PublishNewsItemService</servlet-class>
   </servlet>
   <servlet-mapping>
      <servlet-name>publish-service</servlet-name>
      <url-pattern>/publish</url-pattern>
   </servlet-mapping>

Initially, we will allow all clients to invoke the web service; later we will require that clients authenticate to the server. To accomplish this, in the doFilter method of SecurityFilter, add the following lines of code to the correct place in order to allow unauthenticated clients to invoke the web service.

      // Allow access to web service.
      if (servletPath.equals("/publish"))
      {
         chain.doFilter(req, resp);
         return;
      }

At this point, we can not easily test the functionality of the publish web service because a test requires that an XML document be submitted to the publisher application, which is something that a web browser does not do when a form is submitted. (We could use JavaScript to do it using the XMLHttpRequest object.) In the next section, we will be able to test our implementation of the publish service by modifying the wiki application so that it invokes the service.

back next

Copyright 2007-2009 David Turner and Jinseok Chae. All rights reserved.