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

back next

Web Services (continued)

Video

The Unpublish Service

In this section, we add an unpublish service to the publisher application and we invoke this service from the wiki application when the user clicks on the unpublish link in a published wiki page.

We will implement the unpublish service differently than the publish service. In particular, we have the client send an HTTP GET request to the publisher application in order to unpublish a news item.

To remove a news item from the publisher's RSS feed, the client submits an HTTP GET request to the resource /publisher/unpublish. However, the publisher application also needs to know which news item in its database to delete. This is specified by an id parameter passed in as part of the resource specification. Thus, the full specification of the resource is as follows (assuming the id of the news item to remove is 3).

/publisher/unpublish?id=3

Create a new class in the publisher.ws package called UnpublishNewsItemService with the contents of the folliowing listing.

package publisher.ws;

import java.io.IOException;

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 publisher.data.NewsItem;
import publisher.data.NewsItemDAO;

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

   protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
   throws ServletException, IOException {
      logger.debug("doGet()");
      String id = req.getParameter("id");
      NewsItemDAO newsItemDAO = new NewsItemDAO();
      NewsItem newsItem = newsItemDAO.find(new Long(id));
      if (newsItem != null)
      {
         newsItemDAO.delete(newsItem);
      }
   }
}

In the doGet method we obtain the id through a call to the getParameter method of the HttpServletRequest object, which is passed in as an argument to the doGet method. After obtaining the id, we perform two interactions with the news item DAO: we call find to obtain an instance of the NewsItem class that represents the news item we wish to delete, then we call the delete method of the news item DAO to delete the news item from the database. Note that we only call delete on the news item DAO if the news item was actually found because it is possible that another user has already deleted this news item from the database.

If we had designed the delete method of the news item DAO to take a news item id rather than an instance of the news item class, we would have been able to make a single call into the news item DAO rather than the 2 calls given in the above code. Two different people are likely to differ on their preferences for the form that the delete method takes. It may be that one form is better than the other. However, a more important issue is consistency in the DAOs; the code is easier to read and maintain when the delete methods of all DAOs follow the same pattern.

The following XML fragment should be used to the configure the newly created unpublish news item service.

   <servlet>
      <servlet-name>unpublish-service</servlet-name>
      <servlet-class>publisher.ws.UnpublishNewsItemService</servlet-class>
   </servlet>

   <servlet-mapping>
      <servlet-name>unpublish-service</servlet-name>
      <url-pattern>/unpublish</url-pattern>
   </servlet-mapping>

Recall that the security filter in the publisher application only allows unauthenticated requests for the login page and the news feed. We need to modify this filter in order to permit unauthenticated requests for servlet paths equals to /unpublish. You can do this by adding the following code just after the code that checks for a match with /publish.

if (servletPath.equals("/unpublish"))
{
   chain.doFilter(req, resp);
   return;
}

Now, we need to modify the wiki application in order to invoke the unpublish web service when the user issues the command to unpublish a given wiki page. We prepared for this moment by defining a method stub (and unimplemented method) called unpublish in the unpublish page servlet. The following code provides the implementation of the unpublish method; add this code to the unpublish page servlet of the wiki application.

   private void unpublish(Page page)
   throws IOException
   {
      logger.debug("unpublish()");

      // Construct HTTP headers.
      String requestLine = 
         "GET /publisher/unpublish?id=" + 
         page.getPublishedId() + 
         " HTTP/1.1\r\n";
      String hostHeader = "Host: localhost\r\n";
      String connectionHeader = "Connection: close\r\n";

      // Send HTTP headers.
      Socket socket = new Socket("localhost", 8080);
      OutputStream os = socket.getOutputStream();
      os.write(requestLine.getBytes("US-ASCII"));
      os.write(hostHeader.getBytes("US-ASCII"));
      os.write(connectionHeader.getBytes("US-ASCII"));
      os.write("\r\n".getBytes("US-ASCII"));
      os.flush();

      // Read HTTP response.
      InputStream is = socket.getInputStream();
      InputStreamReader isr = new InputStreamReader(is);
      BufferedReader br = new BufferedReader(isr);
      while (true)
      {
         String headerLine = br.readLine();
         if (headerLine.length() == 0) break;
      }
   }

Organize imports and select the following classes from the choices presented to you.

The page object passed into the unpublish method represents the page that the client wishes to remove from the news feed. The only piece of information in this page object that we need is the news item id. This is what we need to send to the publisher application's unpublish service.

The following code shows how we construct the HTTP headers that comprise the HTTP request message that we will send to the publisher application.

      // Construct HTTP headers.
      String requestLine = 
         "GET /publisher/unpublish?id=" + 
         page.getPublishedId() + 
         " HTTP/1.1\r\n";
      String hostHeader = "Host: localhost\r\n";
      String connectionHeader = "Connection: close\r\n";

The request line is the first line in an HTTP request message. It is comprised of 3 tokens: the HTTP method (GET in this case), the resource being requested (/publisher/unpublish?id="3" for example), and the protocol version number (HTTP/1.1). The request line is where we use the news item id; this id is embedded in the resource token as the value of the id parameter.

The host header is a required in version 1.1 of HTTP, so we include this, although in our situation it has no effect other than to create a syntactically valid request message.

We include the connection close header so that the web service closes the TCP connection immediately after servicing the request. If we don't do this, the JDOM parser blocks on the input stream until the TCP connection times out. Therefore, we want the server to close the connection quickly, so that processing of the user request in the wiki application completes without delay.

After constructing the header strings we send them by making a socket connection with the server and writing the headers into the socket's output stream.

      // Send HTTP headers.
      Socket socket = new Socket("localhost", 8080);
      OutputStream os = socket.getOutputStream();
      os.write(requestLine.getBytes("US-ASCII"));
      os.write(hostHeader.getBytes("US-ASCII"));
      os.write(connectionHeader.getBytes("US-ASCII"));
      os.write("\r\n".getBytes("US-ASCII"));
      os.flush();

Unlike the XML documents that we send in the UTF-8 character encoding, we send the HTTP headers using the US-ASCII character encoding because it satisfies the HTTP protocol. All HTTP header lines are terminated by a carriage return and line feed sequence and the header section of HTTP messages is always terminated by a blank line. Therefore, the last header line we send is the string \r\n. After writing the headers, we flush the output stream to insure that the written bytes are pushed through any buffers and sent into the network.

The unpublish service is an example of one-way messaging because the client (wiki application) only sends a message to the service (publisher application) and does not read a response. However, the web service is implemented on top of HTTP, which is a request/response protocol. Therefore, the service will return an HTTP response message to the client. Rather than close the connection abruptly (and possibly generating an exception on the service end), we adhere to the HTTP protocol and read the HTTP response returned by the service.

      // Read HTTP response.
      InputStream is = socket.getInputStream();
      InputStreamReader isr = new InputStreamReader(is);
      BufferedReader br = new BufferedReader(isr);
      while (true)
      {
         String headerLine = br.readLine();
         if (headerLine.length() == 0) break;
      }

The implementation of the unpublish method presented in this section does not handle abnormal events. One possible abnormal event is for an error to occur in the service. In this case, the service may return an HTTP error code, rather than a success code. In a more developed implementation, the HTTP response would be examined to see if such an error is reported from the service. In this situation, the wiki application may report the error to the user or handle it in some other way, such as to queue the unpublish request for a later attempt. An alternative to reporting errors in this way is to use 2-way messaging where the service returns a status code that reports a success or failure of the requested operation.

Test that the modifications you have just made are correct. To do this, use the manager application to stop and start the publisher application and to restart the wiki application. Open three different browser windows to point at the following three pages.

Click the unpublish operation on the hello wiki page. Refresh the list news items page to see the hello page disappear in the list. Refresh the website page to see the new news item disappear there as well.

back next

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