Previous | Next
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 Figure 7-9. Servlet designA single-servlet design has been adopted for this application. In this approach, the 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 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 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 The code for Example 7-25. ForumServlet.javapackage com.anonymous.forum.servlet; import com.anonymous.forum.ForumConfig; import com.anonymous.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); } }
String jdbcDriverClassName = sc.getInitParameter("jdbcDriverClassName"); String databaseURL = sc.getInitParameter("databaseURL"); String adapterClassName = sc.getInitParameter("adapterClassName"); ForumConfig.setValues(jdbcDriverClassName, databaseURL, adapterClassName); The In the 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 The code for ReqHandler.java is shown in Example 7-26. This is an abstract class that provides Example 7-26. ReqHandler.javapackage com.anonymous.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 Example 7-27. Renderer.javapackage com.anonymous.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
NOTE: Example 7-28. ErrorRenderer.javapackage com.anonymous.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>"); } }
Example 7-29. XSLTRenderHelper.javapackage com.anonymous.forum.servlet; import com.anonymous.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( ) { } }
Another utility class, Example 7-30. ReqHandlerRegistry.javapackage com.anonymous.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; Throughout the discussion forum application, URLs take on the following form: http://hostname:port/forum/main/home In this URL, The first real request handler, Example 7-31. HomeReqHandler.javapackage com.anonymous.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 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 Example 7-32. HomeRenderer.javapackage com.anonymous.forum.servlet; import com.anonymous.forum.*; import com.anonymous.forum.adapter.*; import com.anonymous.forum.domain.*; import com.anonymous.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); } } }
Example 7-33. ViewMonthReqHandler.javapackage com.anonymous.forum.servlet; import com.anonymous.forum.*; import com.anonymous.forum.adapter.*; import com.anonymous.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; 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) { 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.
Example 7-34. ViewMonthRenderer.javapackage com.anonymous.forum.servlet; import com.anonymous.forum.*; import com.anonymous.forum.adapter.*; import com.anonymous.forum.domain.*; import com.anonymous.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); } } }
Example 7-35. ViewMsgReqHandler.javapackage com.anonymous.forum.servlet; import com.anonymous.forum.*; import com.anonymous.forum.adapter.*; import com.anonymous.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( )); } The corresponding renderer, Example 7-36. ViewMsgRenderer.javapackage com.anonymous.forum.servlet; import com.anonymous.forum.*; import com.anonymous.forum.domain.*; import com.anonymous.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, Example 7-37. PostMsgReqHandler.javapackage com.anonymous.forum.servlet; import com.anonymous.forum.*; import com.anonymous.forum.adapter.*; import com.anonymous.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 { Unlike other request handlers in this application, The msgSubject = msgSubject.trim( ); authorEmail = authorEmail.trim( ); msgText = msgText.trim( ); If any of these fields are empty, the 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 The source code for Example 7-38. PostMsgRenderer.javapackage com.anonymous.forum.servlet; import com.anonymous.forum.*; import com.anonymous.forum.domain.*; import com.anonymous.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 |