7.4. Servlet Implementation
We 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 design
A 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 diagram
When 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.java
package 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.java
package 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.java
package 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.java
package 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.java
package 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.java
package 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.java
package 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.java
package 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.java
package 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.
Web Application Security
In the
ViewMonthRegHandler class, a
NumberFormatException is thrown if any of these
parameters are nonnumeric or null. Basically,
there are only two possible causes for this sort of error. First, one
of the XSLT stylesheets may have a bug, making it forget to pass one
of these required parameters. If this is the case, a developer should
theoretically catch this error during development and testing. The
second possibility is that someone is manually keying in parameters
without using the standard XHTML user interface. This could be a
hacker attacking the site by probing for an application error, so we
simply deny the request.
Standalone GUI applications do not have to contend with such issues
because the user interface can prevent illegal user input. But web
applications are essentially wide open for the entire world to see,
so developers must adopt a highly defensive style of programming. If
suppressing hack attempts is not a priority, the code could simply
redirect the user to the home page when an illegal request occurs. It
might be a good idea to write a log file entry that contains the
requesting user's IP address and any other relevant information
when errors occur. Log entries can be very useful when diagnosing
application bugs as well.
|
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.java
package 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.java
package 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.java
package 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.java
package 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.java
package 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.
 |  |  | | 7.3. Making the XML Dynamic |  | 7.5. Finishing Touches |
Copyright © 2002 O'Reilly & Associates. All rights reserved.
|