« - »

The Approval Service: POJOs to XML via Betwixt

10 February 2008

To move beyond the sample XML and start generating some real, dynamic data, we need to create a servlet that will grab the data from its persistent source and send it to the requesting party in XML format. This process will be somewhat of a two-phase operation, first turning the stored data into a Java object and then turning the Java object into XML. We could start from either end, but since we’re doing this from the desired output backwards, the next step would be the 2nd half of that operation, turning the Request for Approval Java objects into XML.

For that, we will use Apache’s Betwixt. According to their web site:

“The Betwixt library provides an XML introspection mechanism for mapping beans to XML in a flexible way. It is implemented using an XMLIntrospector and XMLBeanInfo classes which are similar to the standard Introspector and BeanInfo from the Java Beans specification.”

For our purposes, we will have four POJOs, or .java files, for the basic elements of a Request for Approval, and then to support Betwixt, four corresponding .betwixt files with the same names:

Rather than list them all out here, I will just refer you to the new source .ear file, which you will find here: Example2.ear.

In addition to the .java and .betwixt files, we will also employ a Java utility module to encapsulate all of the steps necessary to utilize the Betwixt process. I called this module BetwixtTool, and placed it in the restifarian.jar file with the other common utilities:

package org.restafarian.core.utils;

import java.io.StringWriter;

import org.apache.commons.betwixt.io.BeanWriter;
import org.apache.commons.betwixt.strategy.ConvertUtilsObjectStringConverter;

/**
 * <p>A collection of static methods for using Betwixt.</p>
 */
public class BetwixtTool {

  /**
   * <p>Converts a single Java bean to XML.</p>
   *
   * @param bean the bean to convert
   * @return the bean in XML format
   */
  public static String toXml(Object bean) {
    return toXml(bean, null, true);
  }

  /**
   * <p>Converts a single Java bean to XML.</p>
   *
   * @param bean the bean to convert
   * @param xsl the URL for the XSL stylesheet for this bean
   * @return the bean in XML format
   */
  public static String toXml(Object bean, String xsl) {
    return toXml(bean, xsl, true);
  }

  /**
   * <p>Converts a single Java bean to XML.</p>
   *
   * @param bean the bean to convert
   * @param includeDeclaration when true, adds the XML
   * declaration to the output
   * @return the bean in XML format
   */
  public static String toXml(Object bean, boolean includeDeclaration) {
    return toXml(bean, null, includeDeclaration);
  }

  /**
   * <p>Converts a single Java bean to XML.</p>
   *
   * @param bean the bean to convert
   * @param xsl the URL for the XSL stylesheet for this bean
   * @param includeDeclaration when true, adds the XML
   * declaration to the output
   * @return the bean in XML format
   */
  public static String toXml(Object bean, String xsl, boolean includeDeclaration) {
    // set up XML bean writer
    StringWriter out = new StringWriter();
    BeanWriter writer = new BeanWriter(out);
    writer.enablePrettyPrint();
    writer.setInitialIndentLevel(0);
    writer.getBindingConfiguration().setMapIDs(false);
    writer.getBindingConfiguration().setObjectStringConverter(new
         ConvertUtilsObjectStringConverter());

    // create XML using bean writer
    String xml = "";
    try {
      if (includeDeclaration) {
        writer.writeXmlDeclaration("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        if (xsl != null && !"".equals(xsl.trim())) {
          writer.writeXmlDeclaration("<?xml-stylesheet
                 type=\"text/xsl\" href=\"" + xsl + "\"?>");
        }
      }
      writer.write(bean);
      out.flush();
      xml = out.toString();
    } catch (Exception e) {
      e.printStackTrace();
    }

    return xml;
  }
}

Which finally brings us to the servlet itself, RequestForApprovalServlet.java. Since it will do many other things in the completed version of the system, you will see a number of things stubbed out with TODOs, but there is enough here to make it work for our initial purposes. The data is still hard-coded test data, but we’ll be fixing that up soon enough. For now, though, the focus is on producing the sample output, which you can see using the servlet mapping of /approval/rfa/1 for now.

package org.restafarian.approval.servlets;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;

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

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.restafarian.approval.beans.RequestForApproval;
import org.restafarian.approval.beans.RequestForApprovalAction;
import org.restafarian.approval.beans.RequestForApprovalApprover;
import org.restafarian.core.beans.Person;
import org.restafarian.core.servlets.RestServletBase;
import org.restafarian.core.utils.BetwixtTool;

/**
 * <p>This servlet handles RFAs.</p>
 */
public class RequestForApprovalServlet extends RestServletBase {
  private static final long serialVersionUID = 1;
  private Log log = LogFactory.getLog(getClass());

  /**
   * <p>The Servlet "doGet()" method.</p>
   *
   * @param req the <code>HttpServletRequest</code> object
   * @param res the <code>HttpServletResponse</code> object
   * @throws ServletException
   * @throws IOException
   */
  public void doGet(HttpServletRequest req, HttpServletResponse
        res) throws ServletException, IOException {
    int id = getNumericIdFromUrl(req, "/rfa/");

    // log request, if enabled
    if (log.isDebugEnabled()) {
      String message = "Processing GET request; id=" + id;
      if (req.getQueryString() != null &&
                    req.getQueryString().length() > 0) {
        message += "; query string=" + req.getQueryString();
      }
      log.debug(message);
    }

    if (id > 0) {
      // send requested RFA
      sendRequestForApproval(id, req, res);
    } else {
      // send error
      sendError(req, res, 405, "Method Not Allowed.");
    }
  }

  /**
   * <p>The Servlet "doPost()" method.</p>
   *
   * @param req the <code>HttpServletRequest</code> object
   * @param res the <code>HttpServletResponse</code> object
   * @throws ServletException
   * @throws IOException
   */
  public void doPost(HttpServletRequest req, HttpServletResponse
              res) throws ServletException, IOException {
    int id = getNumericIdFromUrl(req, "/rfa/");

    // log request, if enabled
    if (log.isDebugEnabled()) {
      String message = "Processing POST request; id=" + id;
      if (req.getQueryString() != null &&
            req.getQueryString().length() > 0) {
        message += "; query string=" + req.getQueryString();
      }
      log.debug(message);
    }

    if (id > 0) {
      // update RFA
      //TODO
    } else {
      // send error
      sendError(req, res, 405, "Method Not Allowed. Only
                the \"GET\" and \"PUT\" methods are allowed
                for this URL (/rfa with no id).");
    }
  }

  /**
   * <p>The Servlet "doPut()" method.</p>
   *
   * @param req the <code>HttpServletRequest</code> object
   * @param res the <code>HttpServletResponse</code> object
   * @throws ServletException
   * @throws IOException
   */
  public void doPut(HttpServletRequest req, HttpServletResponse
                  res) throws ServletException, IOException {
    // log request, if enabled
    if (log.isDebugEnabled()) {
      log.debug("Processing PUT request.");
    }

    //TODO
  }

  /**
   * <p>The Servlet "doDelete()" method.</p>
   *
   * @param req the <code>HttpServletRequest</code> object
   * @param res the <code>HttpServletResponse</code> object
   * @throws ServletException
   * @throws IOException
   */
  public void doDelete(HttpServletRequest req, HttpServletResponse
               res) throws ServletException, IOException {
    int id = getNumericIdFromUrl(req, "/rfa/");

    // log request, if enabled
    if (log.isDebugEnabled()) {
      String message = "Processing DELETE request; id=" + id;
      if (req.getQueryString() != null &&
                      req.getQueryString().length() > 0) {
        message += "; query string=" + req.getQueryString();
      }
      log.debug(message);
    }

    if (id > 0) {
      // delete requested RFA
      //TODO
    } else {
      // send error
      sendError(req, res, 405, "Method Not Allowed. Only
               the \"GET\" and \"PUT\" methods are allowed
               for this URL (/rfa with no id).");
    }
  }

  /**
   * <p>Handles a get request for a single RFA.</p>
   *
   * @param id the id of the requested rfa
   * @param req the <code>HttpServletRequest</code> object
   * @param res the <code>HttpServletResponse</code> object
   */
  private void sendRequestForApproval(int id, HttpServletRequest
               req, HttpServletResponse res) throws IOException {
    RequestForApproval rfa = getRequestForApproval(id);

    if (rfa != null) {
      PrintWriter pw = res.getWriter();
      if (pw != null && !pw.equals("")) {
        pw.print(BetwixtTool.toXml(rfa, "/approval/xsl/rfa.xsl"));
      } else {
        sendError(req, res, 500, "There was a technical error
                while attempting to access this resource. Details
                of this error have been logged on the server.");
      }
    } else {
      sendError(req, res, 404, "The requested resource was not
            found on this server. If you entered the URL manually
            please check your spelling and try again.");
    }
  }

  /**
   * <p>This is a temporary stub that returns sample data
   * for testing.</p>
   *
   * @param id the RFA id
   */
  private RequestForApproval getRequestForApproval(int id) {
    RequestForApproval rfa = new RequestForApproval();

    rfa.setId(1);
    rfa.setType("Leave Request");
    rfa.setState(RequestForApproval.STATE_SUBMITTED);
    rfa.setPayloadId("HR5740-9217A");
    rfa.setPayloadURI("http://localhost:9080/hr/leave/HR5740-9217A");
    rfa.setDescription("Test User Vacation Request for 5/15/2008");
    Person author = new Person();
    author.setId("tuser");
    author.setUri("http://localhost:9080/id/user/tuser");
    author.setName("Test User");
    rfa.setAuthor(author);
    rfa.setDateTime(new Date());
    RequestForApprovalApprover rfaa = new RequestForApprovalApprover();
    rfaa.setId(1);
    Person approver = new Person();
    approver.setId("tmanager");
    approver.setUri("http://localhost:9080/id/user/tmanager");
    approver.setName("Test Manager");
    rfaa.setApprover(approver);
    rfaa.setActionList(RequestForApproval.ACTION_APPROVE
      + "," + RequestForApproval.ACTION_COMMENT
      + "," + RequestForApproval.ACTION_DENY);
    rfa.addApprover(rfaa);
    rfaa = new RequestForApprovalApprover();
    rfaa.setId(2);
    approver = new Person();
    approver.setId("tsupervisor");
    approver.setUri("http://localhost:9080/id/user/supervisor");
    approver.setName("Test Supervisor");
    rfaa.setApprover(approver);
    rfaa.setActionList(RequestForApproval.ACTION_APPROVE
      + "," + RequestForApproval.ACTION_COMMENT
      + "," + RequestForApproval.ACTION_DENY);
    rfa.addApprover(rfaa);
    RequestForApprovalAction action = new RequestForApprovalAction();
    action.setDateTime(new Date());
    action.setActivity("Submitted");
    action.setUser(author);
    action.setComments("Please let me have this day off!");
    rfa.addAction(action);

    return rfa;
  }

  /**
   * <p>Sends the HTTP error code and message, and logs the
   * code and message if enabled.</p>
   *
   * @param req the <code>HttpServletRequest</code> object
   * @param res the <code>HttpServletResponse</code> object
   * @param errorCode the error code to send
   * @param errorMessage the error message to send
   */
  private void sendError(HttpServletRequest req, HttpServletResponse
        res, int errorCode, String errorMessage) throws IOException {
    // log message, if enabled
    if (log.isDebugEnabled()) {
      log.debug("Sending error " + errorCode + "; message=" + errorMessage);
    }

    // send error
    res.sendError(errorCode, errorMessage);
  }
}


http://blog.restafarian.org/2008/02/the-approval-service-pojos-to-xml-via-betwixt/

Comments are closed.

Sorry, the comment form is closed at this time.