7.4. Servlet ImplementationWe are almost finished! The remaining piece of the puzzle is to coordinate activity between the web browser, database, domain objects, JDOM producers, and XSLT stylesheets. This task lies in the servlet implementation and related classes. In an XSLT-driven web application, the servlet itself really does not do all that much. Instead, it acts as a mediator between all of the other actions taking place in the application. Figure 7-9 shows the UML class diagram for the com.oreilly.forum.servlet package. This design consists of a few key classes along with numerous subclasses of Renderer and ReqHandler. These subclasses are very repetitive in nature, which is indicative of the highly structured application design that XML and XSLT facilitate. Figure 7-9. Servlet designA single-servlet design has been adopted for this application. In this approach, the ForumServlet intercepts all inbound requests from clients. The requests are then delegated to subclasses of ReqHandler, which handle requests for individual pages. Once the request has been processed, a subclass of Renderer selects the XML and XSLT stylesheet. XSLTRenderHelper does the actual XSLT transformation, sending the resulting XHTML back to the browser. This is not designed to be a heavyweight web application framework. Instead, it is just a simple set of coding conventions and patterns that help keep the application highly modular. It is easy to eliminate the ReqHandler classes and use several servlets instead. The main advantage of explicit request handlers and renderers is that the design is clearly modularized, which may promote more consistency across a team of developers. The overall flow of control may be the hardest part to understand. Once this flow is clear, the implementation is a matter of creating additional request handlers and renderers. Figure 7-10 is a UML sequence diagram that shows how a single web browser request is intercepted and processed. Figure 7-10. Sequence diagramWhen a browser issues a request, it is always directed to the single servlet. This servlet then locates the appropriate request handler based on information found in the requested URL. The request handler is responsible for interacting with the data adapter layer to create and update domain objects and for creating the appropriate renderer. Once the renderer is created, the servlet asks it to render( ) its content. The renderer then asks the appropriate JDOM producer to create the XML data and performs the transformation using an XSLT stylesheet. The result of the transformation is sent back to the client browser. One request handler might map to several renderers. For example, suppose the user is trying to post a new message and submits this information to the PostMsgReqHandler class. If the request handler determines that some required fields are missing, it can return an instance of the PostMsgRenderer class. This allows the user to fill in the remaining fields. On the other hand, if a database error occurs, an instance of ErrorRenderer can be returned. Otherwise, ViewMsgRenderer is returned when the message is successfully posted. Because request handlers and renderers are cleanly separated, renderers can be invoked from any request handler. The code for ForumServlet is shown in Example 7-25. As already mentioned, this is the only servlet in the application. Example 7-25. ForumServlet.javapackage com.oreilly.forum.servlet; import com.oreilly.forum.ForumConfig; import com.oreilly.forum.jdbcimpl.DBUtil; import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; /** * The single servlet in the discussion forum. */ public class ForumServlet extends HttpServlet { private ReqHandlerRegistry registry; /** * Registers all request handlers and sets up the * ForumConfig object. */ public void init(ServletConfig sc) throws ServletException { super.init(sc); // get initialization parameters from the deployment // descriptor (web.xml) String jdbcDriverClassName = sc.getInitParameter( "jdbcDriverClassName"); String databaseURL = sc.getInitParameter( "databaseURL"); String adapterClassName = sc.getInitParameter( "adapterClassName"); ForumConfig.setValues(jdbcDriverClassName, databaseURL, adapterClassName); try { // load all request handlers this.registry = new ReqHandlerRegistry(new HomeReqHandler( )); this.registry.register(new PostMsgReqHandler( )); this.registry.register(new ViewMonthReqHandler( )); this.registry.register(new ViewMsgReqHandler( )); } catch (Exception ex) { log(ex.getMessage( ), ex); throw new UnavailableException(ex.getMessage( ), 10); } } /** * Closes all database connections. This method is invoked * when the Servlet is unloaded. */ public void destroy( ) { super.destroy( ); DBUtil.closeAllConnections( ); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { ReqHandler rh = this.registry.getHandler(request); Renderer rend = rh.doPost(this, request, response); rend.render(this, request, response); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { ReqHandler rh = this.registry.getHandler(request); Renderer rend = rh.doGet(this, request, response); rend.render(this, request, response); } } ForumServlet overrides the init( ) method to perform one-time initialization before any client requests are handled. This is where context initialization parameters are read from the deployment descriptor and stored in the ForumConfig instance: String jdbcDriverClassName = sc.getInitParameter("jdbcDriverClassName"); String databaseURL = sc.getInitParameter("databaseURL"); String adapterClassName = sc.getInitParameter("adapterClassName"); ForumConfig.setValues(jdbcDriverClassName, databaseURL, adapterClassName); The init( ) method then sets up instances of each type of request handler. These are registered with the ReqHandlerRegistry class, which has the ability to locate request handlers later on. In the destroy( ) method, which is called when the servlet is unloaded, any outstanding database connections are closed: public void destroy( ) { super.destroy( ); DBUtil.closeAllConnections( ); } While this currently has no real effect, the code was put in place because a future version of the software may use database connection pooling. This allows the application to close all connections in the pool just before exiting. The only remaining methods in the servlet are doGet( ) and doPost( ), which are virtually identical. All these methods do is locate the appropriate request handler instance, ask the handler to perform a GET or POST, and then use the renderer to send a response to the client. The code for ReqHandler.java is shown in Example 7-26. This is an abstract class that provides doGet( ) and doPost( ) methods. By default, each method returns an error message back to the client, so a derived class must override one or both methods to enable HTTP GET and/or POST. Once the method is complete, the derived class must return an instance of Renderer, which produces the next page to display. Example 7-26. ReqHandler.javapackage com.oreilly.forum.servlet; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; /** * All request handlers must extend from this class. */ public abstract class ReqHandler { protected abstract String getPathInfo( ); protected Renderer doGet(HttpServlet servlet, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { return new ErrorRenderer("GET not allowed"); } protected Renderer doPost(HttpServlet servlet, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { return new ErrorRenderer("POST not allowed"); } } The Renderer class is shown in Example 7-27. This class, like ReqHandler, is abstract. Derived classes are responsible for nothing more than producing content to the HttpServletResponse. Basically, each page in the discussion forum application is created using a subclass of Renderer. Example 7-27. Renderer.javapackage com.oreilly.forum.servlet; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; /** * All page renderers must extend from this class. */ public abstract class Renderer { public abstract void render(HttpServlet servlet, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException; } The most basic renderer is ErrorRenderer, which is shown in Example 7-28. This class displays an error message in a web browser using simple println( ) statements that generate HTML. Unlike all other parts of this application, the ErrorRenderer class does not use XML and XSLT. The reason for this is that a large percentage of errors occurs because an XML parser is not properly configured on the CLASSPATH.[33] If this sort of error occurs, this renderer will not be affected.
NOTE: ErrorRenderer can be written to use XML and XSLT, provided that a try/catch block catches any transformation errors and reverts to println( ) statements for error reporting. Example 7-28. ErrorRenderer.javapackage com.oreilly.forum.servlet; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; /** * Shows an error page. Since errors are frequently caused by improperly * configured JAR files, XML And XSLT are not used by this class. * If XML and XSLT were used, then the same CLASSPATH issue that caused * the original exception to occur would probably cause this page * to fail as well. */ public class ErrorRenderer extends Renderer { private String message; private Throwable throwable; public ErrorRenderer(Throwable throwable) { this(throwable, throwable.getMessage( )); } public ErrorRenderer(String message) { this(null, message); } public ErrorRenderer(Throwable throwable, String message) { this.throwable = throwable; this.message = message; } public void render(HttpServlet servlet, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { response.setContentType("text/html"); PrintWriter pw = response.getWriter( ); // just show a simple error page for now. pw.println("<html>"); pw.println("<body>"); pw.println("<p>"); pw.println(this.message); pw.println("</p>"); if (this.throwable != null) { pw.println("<pre>"); this.throwable.printStackTrace(pw); pw.println("</pre>"); } pw.println("</body></html>"); } } XSLTRenderHelper, shown in Example 7-29, is a utility class used by all remaining renderers. This class does the low-level XSLT transformations, eliminating a lot of duplicated code in each of the renderers. XSLTRenderHelper also maintains a cache of stylesheet filenames so they do not have to be repeatedly located using the ServletContext.getRealPath( ) method. Example 7-29. XSLTRenderHelper.javapackage com.oreilly.forum.servlet; import com.oreilly.javaxslt.util.StylesheetCache; import java.io.*; import java.net.URL; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; import javax.xml.transform.*; import javax.xml.transform.stream.*; import org.jdom.*; import org.jdom.output.*; /** * A helper class that makes rendering of XSLT easier. This * eliminates the need to duplicate a lot of code for each * of the web pages in this app. */ public class XSLTRenderHelper { private static Map filenameCache = new HashMap( ); /** * Perform an XSLT transformation. * * @param servlet provides access to the ServletContext so * the XSLT directory can be determined. * @param xmlJDOMData JDOM data for the XML Document. * @param xsltBaseName the name of the stylesheet without a directory. * @param response the Servlet response to write output to. */ public static void render(HttpServlet servlet, Document xmlJDOMData, String xsltBaseName, HttpServletResponse response) throws ServletException, IOException { String xsltFileName = null; try { // figure out the complete XSLT stylesheet file name synchronized (filenameCache) { xsltFileName = (String) filenameCache.get(xsltBaseName); if (xsltFileName == null) { ServletContext ctx = servlet.getServletContext( ); xsltFileName = ctx.getRealPath( "/WEB-INF/xslt/" + xsltBaseName); filenameCache.put(xsltBaseName, xsltFileName); } } // write the JDOM data to a StringWriter StringWriter sw = new StringWriter( ); XMLOutputter xmlOut = new XMLOutputter("", false, "UTF-8"); xmlOut.output(xmlJDOMData, sw); response.setContentType("text/html"); Transformer trans = StylesheetCache.newTransformer(xsltFileName); // pass a parameter to the XSLT stylesheet trans.setParameter("rootDir", "/forum/"); trans.transform(new StreamSource(new StringReader(sw.toString( ))), new StreamResult(response.getWriter( ))); } catch (IOException ioe) { throw ioe; } catch (Exception ex) { throw new ServletException(ex); } } private XSLTRenderHelper( ) { } } XSLTRenderHelper performs the XSLT transformation by first converting the JDOM Document into a String of XML and then reading that String back into a JAXP-compliant XSLT processor. This is not necessarily the most efficient way to integrate JDOM with JAXP, but it works reliably with some beta versions of JDOM. By the time you read this, JDOM will have more standardized APIs for integrating with JAXP. Another utility class, ReqHandlerRegistry, is shown in Example 7-30. This class is responsible for locating instances of ReqHandler based on path information found in the request URL. Basically, path information is any text that occurs after a slash character (/) following the servlet mapping. HttpServletRequest includes a method called getPathInfo( ) that returns any path information that is present. Example 7-30. ReqHandlerRegistry.javapackage com.oreilly.forum.servlet; import java.util.*; import javax.servlet.http.*; /** * A utility class that locates request handler instances based * on extra path information. */ public class ReqHandlerRegistry { private ReqHandler defaultHandler; private Map handlerMap = new HashMap( ); public ReqHandlerRegistry(ReqHandler defaultHandler) { this.defaultHandler = defaultHandler; } public void register(ReqHandler handler) { this.handlerMap.put(handler.getPathInfo( ), handler); } public ReqHandler getHandler(HttpServletRequest request) { ReqHandler rh = null; String pathInfo = request.getPathInfo( ); if (pathInfo != null) { int firstSlashPos = pathInfo.indexOf('/'); int secondSlashPos = (firstSlashPos > -1) ? pathInfo.indexOf('/', firstSlashPos+1) : -1; String key = null; if (firstSlashPos > -1) { if (secondSlashPos > -1) { key = pathInfo.substring(firstSlashPos+1, secondSlashPos); } else { key = pathInfo.substring(firstSlashPos+1); } } else { key = pathInfo; } if (key != null && key.length( ) > 0) { rh = (ReqHandler) this.handlerMap.get(key); } } return (rh != null) ? rh : this.defaultHandler; } } Throughout the discussion forum application, URLs take on the following form: http://hostname:port/forum/main/home In this URL, forum represents the web application and is the name of the WAR file. The next part of the URL, main, is a mapping to ForumServlet. Since the WAR file and servlet will not change, this part of the URL remains constant. The remaining data, /home, is path information. This is the portion of the URL that ReqHandlerRegistry uses to locate instances of ReqHandler. If the path information is null or does not map to any request handlers, the default request handler is returned. This simply returns the user to the home page. The first real request handler, HomeReqHandler, is shown in Example 7-31. This class is quite simple and merely returns an instance of HomeRenderer. The code is simple because the home page does not have any modes of operation other than to display all message boards. Other request handlers are more complex because they must process HttpServletRequest parameters. Example 7-31. HomeReqHandler.javapackage com.oreilly.forum.servlet; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; /** * This is the 'default' request handler in the app. The * first inbound request generally goes to an instance * of this class, which returns the home page renderer. */ public class HomeReqHandler extends ReqHandler { protected String getPathInfo( ) { return "home"; } protected Renderer doGet(HttpServlet servlet, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { return new HomeRenderer( ); } } All of the request handlers must override the getPathInfo( ) method. This determines the path info portion of the URL, so each request handler must return a unique string. The renderer for the home page, shown in Example 7-32, is also quite simple. As with the home request handler, this renderer is simple because it has only one mode of operation. Like other renderers, this class gets some data from the database using the DataAdapter class, asks a JDOM producer to convert the data into XML, and then tells XSLTRenderHelper which XSLT stylesheet to use when performing the transformation. Example 7-32. HomeRenderer.javapackage com.oreilly.forum.servlet; import com.oreilly.forum.*; import com.oreilly.forum.adapter.*; import com.oreilly.forum.domain.*; import com.oreilly.forum.xml.*; import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; import org.jdom.*; /** * Shows the home page. */ public class HomeRenderer extends Renderer { public void render(HttpServlet servlet, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { try { // get the data for the home page DataAdapter adapter = DataAdapter.getInstance( ); // an iterator of BoardSummary objects Iterator boards = adapter.getAllBoards( ); // convert the data into XML (a JDOM Document) Document doc = new Document(HomeJDOM.produceElement(boards)); // apply the appropriate stylesheet XSLTRenderHelper.render(servlet, doc, "home.xslt", response); } catch (DataException de) { new ErrorRenderer(de).render(servlet, request, response); } } } ViewMonthReqHandler, shown in Example 7-33, is slightly more complex than the home page request handler. Since this request handler requires the board id, month number, and year number as parameters, it must perform validation before it can handle the request properly. Example 7-33. ViewMonthReqHandler.javapackage com.oreilly.forum.servlet; import com.oreilly.forum.*; import com.oreilly.forum.adapter.*; import com.oreilly.forum.domain.*; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; /** * Handle a request to view a month for a message board. */ public class ViewMonthReqHandler extends ReqHandler { protected String getPathInfo( ) { return "viewMonth"; } protected Renderer doGet(HttpServlet servlet, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { try { DataAdapter adapter = DataAdapter.getInstance( ); // these are all required parameters long boardID = 0L; int month = 0; int year = 0; try { boardID = Long.parseLong(request.getParameter("boardID")); month = Integer.parseInt(request.getParameter("month")); year = Integer.parseInt(request.getParameter("year")); } catch (Exception ex) { return new ErrorRenderer("Invalid request"); } BoardSummary board = adapter.getBoardSummary(boardID); if (board == null) { return new ErrorRenderer("Invalid request"); } return new ViewMonthRenderer(board, new MonthYear(month, year)); } catch (DataException de) { return new ErrorRenderer(de); } } } Throughout this application, a seemingly harsh approach to error handling is followed. If any "impossible" requests are detected, the user is presented with a terse error message: try { boardID = Long.parseLong(request.getParameter("boardID")); month = Integer.parseInt(request.getParameter("month")); year = Integer.parseInt(request.getParameter("year")); } catch (Exception ex) { return new ErrorRenderer("Invalid request"); } When considering error-handling approaches, the primary concern should be break-in attempts by hackers. It is far too easy for a user to determine which parameters are passed to a web application and then try to wreak havoc by manually keying in various permutations of those parameters. By checking for illegal parameters and simply rejecting them as invalid, a web application gains a big security advantage. ViewMonthRenderer is shown in Example 7-34. This is another simple class that displays an entire month's worth of messages in a given board. Although the XHTML display can be quite complex for this page, the JDOM producer and XSLT stylesheet handle the real work, keeping the Java code to a minimum. Example 7-34. ViewMonthRenderer.javapackage com.oreilly.forum.servlet; import com.oreilly.forum.*; import com.oreilly.forum.adapter.*; import com.oreilly.forum.domain.*; import com.oreilly.forum.xml.*; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; import org.jdom.*; /** * Renders a page that shows all messages in a given month. */ public class ViewMonthRenderer extends Renderer { private BoardSummary board; private MonthYear month; public ViewMonthRenderer(BoardSummary board, MonthYear month) { this.board = board; this.month = month; } public void render(HttpServlet servlet, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { try { // convert the data into XML (a JDOM Document) Document doc = new Document(ViewMonthJDOM.produceElement( this.board, this.month)); // apply the appropriate stylesheet XSLTRenderHelper.render(servlet, doc, "viewMonth.xslt", response); } catch (DataException de) { throw new ServletException(de); } } } ViewMsgReqHandler, shown in Example 7-35, requires a parameter named msgID. As before, if this parameter is invalid, an error page is displayed to the user. Otherwise, an instance of ViewMsgRenderer is returned to the servlet. Example 7-35. ViewMsgReqHandler.javapackage com.oreilly.forum.servlet; import com.oreilly.forum.*; import com.oreilly.forum.adapter.*; import com.oreilly.forum.domain.*; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; /** * Handle a request to view a message. */ public class ViewMsgReqHandler extends ReqHandler { protected String getPathInfo( ) { return "viewMsg"; } protected Renderer doGet(HttpServlet servlet, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { try { DataAdapter adapter = DataAdapter.getInstance( ); // msgID is a required parameter and must be valid String msgIDStr = request.getParameter("msgID"); if (msgIDStr == null) { servlet.log("Required parameter 'msgID' was missing"); return new ErrorRenderer("Invalid request"); } Message msg = adapter.getMessage(Long.parseLong(msgIDStr)); MessageSummary inResponseTo = null; if (msg.getInReplyTo( ) > -1) { inResponseTo = adapter.getMessage(msg.getInReplyTo( )); } return new ViewMsgRenderer(msg, inResponseTo); } catch (NumberFormatException nfe) { servlet.log("'msgID' parameter was not a number"); return new ErrorRenderer("Invalid request"); } catch (DataException de) { return new ErrorRenderer(de); } } } The corresponding renderer, ViewMsgRenderer, is shown in Example 7-36. This class follows the same basic approach as other renderers: it produces a JDOM Document and uses XSLTRenderHelper to perform the XSLT transformation. Example 7-36. ViewMsgRenderer.javapackage com.oreilly.forum.servlet; import com.oreilly.forum.*; import com.oreilly.forum.domain.*; import com.oreilly.forum.xml.*; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; import org.jdom.*; /** * Show the "view message" page. */ public class ViewMsgRenderer extends Renderer { private Message message; private MessageSummary inResponseTo; public ViewMsgRenderer(Message message, MessageSummary inResponseTo) { this.message = message; this.inResponseTo = inResponseTo; } public void render(HttpServlet servlet, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // convert the data into XML (a JDOM Document) Document doc = new Document(ViewMessageJDOM.produceElement( this.message, this.inResponseTo)); // apply the appropriate stylesheet XSLTRenderHelper.render(servlet, doc, "viewMsg.xslt", response); } } The next class, PostMsgReqHandler, is shown in Example 7-37. In the doGet( ) method, the mode parameter indicates whether the user is trying to post a new message or reply to an existing message. The doGet( ) method is invoked as a result of an HTTP GET request, such as the user clicking on a hyperlink or typing in a URL. Example 7-37. PostMsgReqHandler.javapackage com.oreilly.forum.servlet; import com.oreilly.forum.*; import com.oreilly.forum.adapter.*; import com.oreilly.forum.domain.*; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; /** * Handles GET and POST requests for the page that allows users * to post or reply to a message. */ public class PostMsgReqHandler extends ReqHandler { protected String getPathInfo( ) { return "postMsg"; } /** * When an HTTP GET is issued, show the web page for the * first time. */ protected Renderer doGet(HttpServlet servlet, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { try { // mode must be "postNewMsg" or "replyToMsg" String mode = request.getParameter("mode"); DataAdapter adapter = DataAdapter.getInstance( ); if ("replyToMsg".equals(mode)) { long origMsgID = Long.parseLong( request.getParameter("origMsgID")); Message inResponseToMsg = adapter.getMessage(origMsgID); if (inResponseToMsg != null) { return new PostMsgRenderer(inResponseToMsg); } } else if ("postNewMsg".equals(mode)) { long boardID = Long.parseLong( request.getParameter("boardID")); BoardSummary board = adapter.getBoardSummary(boardID); if (board != null) { return new PostMsgRenderer(board); } } return new ErrorRenderer("Invalid request"); } catch (NumberFormatException nfe) { return new ErrorRenderer(nfe); } catch (DataException de) { return new ErrorRenderer(de); } } /** * Handles HTTP POST requests, indicating that the user has * filled in the form and pressed the Submit button. */ protected Renderer doPost(HttpServlet servlet, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // if the user hit the Cancel button, return to the home page if (request.getParameter("cancelBtn") != null) { return new HomeRenderer( ); } // lots of error checking follows... if (request.getParameter("submitBtn") == null) { servlet.log("Expected 'submitBtn' parameter to be present"); return new ErrorRenderer("Invalid request"); } // a null parameter indicates either a hack attempt, or a // syntax error in the HTML String mode = request.getParameter("mode"); String msgSubject = request.getParameter("msgSubject"); String authorEmail = request.getParameter("authorEmail"); String msgText = request.getParameter("msgText"); if (mode == null || msgSubject == null || authorEmail == null || msgText == null) { return new ErrorRenderer("Invalid request"); } // one of these may be null String origMsgIDStr = request.getParameter("origMsgID"); String boardIDStr = request.getParameter("boardID"); if (origMsgIDStr == null && boardIDStr == null) { return new ErrorRenderer("Invalid request"); } long origMsgID = 0; long boardID = 0; try { origMsgID = (origMsgIDStr != null) ? Long.parseLong(origMsgIDStr) : 0; boardID = (boardIDStr != null) ? Long.parseLong(boardIDStr) : 0; } catch (NumberFormatException nfe) { return new ErrorRenderer("Invalid request"); } // remove extra whitespace then verify that the user filled // in all required fields msgSubject = msgSubject.trim( ); authorEmail = authorEmail.trim( ); msgText = msgText.trim( ); try { DataAdapter adapter = DataAdapter.getInstance( ); if (msgSubject.length( ) == 0 || authorEmail.length( ) == 0 || msgText.length( ) == 0) { BoardSummary board = (boardIDStr == null) ? null : adapter.getBoardSummary(boardID); MessageSummary inResponseToMsg = (origMsgIDStr == null) ? null : adapter.getMessage(origMsgID); return new PostMsgRenderer(board, inResponseToMsg, true, msgSubject, authorEmail, msgText); } // // If this point is reached, no errors were detected so the // new message can be posted, or a response can be created // Message msg = null; if ("replyToMsg".equals(mode)) { msg = adapter.replyToMessage(origMsgID, msgSubject, authorEmail, msgText); } else if ("postNewMsg".equals(mode)) { msg = adapter.postNewMessage(boardID, msgSubject, authorEmail, msgText); } if (msg != null) { MessageSummary inResponseTo = null; if (msg.getInReplyTo( ) > -1) { inResponseTo = adapter.getMessage(msg.getInReplyTo( )); } return new ViewMsgRenderer(msg, inResponseTo); } return new ErrorRenderer("Invalid request"); } catch (DataException dex) { return new ErrorRenderer(dex); } } } Unlike other request handlers in this application, PostMsgReqHandler also has a doPost( ) method. The doGet( ) method is responsible for returning a renderer that displays the XHTML form, while the doPost( ) method is responsible for processing the form submission. Because the XHTML form contains several required fields and buttons, the doPost( ) method is far more complex than doGet( ). As the code reveals, almost all of this complexity is introduced because of error checking and validation logic. The doPost( ) method checks for illegal/impossible parameters first, returning an error page if any problems occur. Next, it checks to see what the user typed in. If the user left a required field blank, the parameter value will be an empty string rather than null. Of course, leading and trailing spaces must be trimmed in case the user hit the space bar: msgSubject = msgSubject.trim( ); authorEmail = authorEmail.trim( ); msgText = msgText.trim( ); If any of these fields are empty, the PostMsgRenderer is returned with form field values pre-filled: return new PostMsgRenderer(board, inResponseToMsg, true, msgSubject, authorEmail, msgText); This gives the user an opportunity to fill in missing values and try to submit the form again. If all is well, an instance of ViewMsgRenderer is returned. This allows the user to view the message that was just submitted. The source code for PostMsgRenderer is shown in Example 7-38. Example 7-38. PostMsgRenderer.javapackage com.oreilly.forum.servlet; import com.oreilly.forum.*; import com.oreilly.forum.domain.*; import com.oreilly.forum.xml.*; import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; import org.jdom.*; /** * Show the web page that allows a user to post or reply to * a message. */ public class PostMsgRenderer extends Renderer { private MessageSummary inResponseToMsg; private BoardSummary board; private String msgSubject; private String authorEmail; private String msgText; private boolean showError; /** * This constructor indicates that the user is replying to an * existing message. */ public PostMsgRenderer(Message inResponseToMsg) { this.board = inResponseToMsg.getBoard( ); this.inResponseToMsg = inResponseToMsg; this.showError = false; this.msgSubject = "Re: " + inResponseToMsg.getSubject( ); this.authorEmail = ""; StringTokenizer st = new StringTokenizer( inResponseToMsg.getText( ), "\n"); StringBuffer buf = new StringBuffer( ); buf.append("\n"); buf.append("\n> -----Original Message-----"); buf.append("\n> Posted by "); buf.append(inResponseToMsg.getAuthorEmail( )); buf.append(" on "); buf.append(inResponseToMsg.getCreateDate().toString( )); buf.append("\n"); while (st.hasMoreTokens( )) { String curLine = st.nextToken( ); buf.append("> "); buf.append(curLine); buf.append("\n"); } buf.append("> "); this.msgText = buf.toString( ); } /** * This constructor indicates that the user is posting * a new message. */ public PostMsgRenderer(BoardSummary board) { this(board, null, false, "", "", ""); } /** * This constructor is used when the user submitted a form * but did not fill out all required fields. */ public PostMsgRenderer(BoardSummary board, MessageSummary inResponseToMsg, boolean showError, String msgSubject, String authorEmail, String msgText) { this.board = board; this.inResponseToMsg = inResponseToMsg; this.showError = showError; this.msgSubject = msgSubject; this.authorEmail = authorEmail; this.msgText = msgText; } public void render(HttpServlet servlet, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // convert the data into XML (a JDOM Document) Document doc = new Document(PostMessageJDOM.produceElement( this.board, this.inResponseToMsg, this.showError, this.msgSubject, this.authorEmail, this.msgText)); // apply the appropriate stylesheet XSLTRenderHelper.render(servlet, doc, "postMsg.xslt", response); } } As the code shows, this class has several constructors that support different modes of operation. The first constructor does the most work, prefixing the original message with > characters as many email clients do when creating replies to existing messages. Other than having several constructors, however, the renderer works just like other renderers in the application. The JDOM producer and XSLT stylesheet actually do most of the work, distinguishing between the various modes of operation. Copyright © 2002 O'Reilly & Associates. All rights reserved. |
|