Article
All Java, No Froth: 6 Easy Steps to MVC Web Apps
Servlets
If you've followed along, you've now got a to-do list database and a couple of classes that view and update it. The next step is to take that functionality to the web by wrapping it in a web application. On the Java platform, there are dozens of ways to do this. The simplest approach is to write a servlet. A servlet is simply a class that has methods that handle requests from web browsers.
Basic page requests are handled by a doGet() method. Form submissions that use the POST method are handled by a doPost() method. Objects passed to these methods provide access to information about the browser request and allow control over the servlet's response.
An XML configuration file controls the URLs the servlet is responsible for, and provides any configuration information that the servlet may require.
The Java web application standard (J2EE) even specifies a directory structure, so that the servlet always knows where to find any class files, configuration files, and additional web resources (like images and style sheets) that it needs.
Let's start by putting the stuff we have already into the correct directory structure. Create a new empty directory to work in, then create a subdirectory called WEB-INF. All of the "normal" web resources (HTML pages, images, style sheets and JavaScript files) will go in the main directory, while all the Java stuff (classes, libraries and configuration files) will go in WEB-INF.
Within WEB-INF, create two more subdirectories: classes and lib. WEB-INF/classes will contain all the Java classes for our web application, and WEB-INF/lib will contain any libraries that those classes may require.
Speaking of required libraries, remember from the previous section that our ToDoList class requires the JDBC driver for MySQL (MySQL Connector/J) to access the database. For a standalone application, we needed to add the JAR file (mysql-connector-java-version-bin.jar) to the classpath. For a Java web application, simply drop the file into the WEB-INF/lib directory.
Since we'll be using the classes we've already developed (ToDoList and ToDoItem) in our web application, we need to put them in the WEB-INF/classes directory. You might as well drop the source files in there along with the compiled classes, just in case you need to make any changes -- recompiling will then be a breeze.
Here's our file and directory structure so far:
/WEB-INF/classes/com/sitepoint/ToDoItem.class
/WEB-INF/classes/com/sitepoint/ToDoItem.java
/WEB-INF/classes/com/sitepoint/ToDoList.class
/WEB-INF/classes/com/sitepoint/ToDoList.java
/WEB-INF/lib/mysql-connector-java-version-bin.jar
Let's now turn our attention to building the servlet for our application. Remember, servlets are just Java classes, and this one will be com.sitepoint.ToDoServlet:
package com.sitepoint;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class ToDoServlet extends HttpServlet {
As you can see, servlet classes must extend the javax.servlet.HttpServlet class. You can view documentation on this and related classes in the J2EE API Specification.
Our servlet will use an instance of our ToDoList class, which we want to create when the servlet is first loaded. We can perform initialization tasks like this in the init() method, which the server will call when it loads the servlet:
private ToDoList toDoList;
// Initialize global variables
public void init() throws ServletException {
toDoList = new ToDoList(getInitParameter("jdbcDriver"),
getInitParameter("jdbcConnectionString"));
}
You may recall that the ToDoList constructor takes the name of the JDBC driver you want to use, and a JDBC connection string so that it can connect to your database. Rather than hard-coding these strings into our servlet, we use this code, which uses initialization parameters for these values, so that they can be configured without recompiling the servlet. We'll look more at this later.
You'll notice that the init() method, like all standard servlet methods, can throw a ServletException. This exception is used to let the server know that something went wrong in the servlet. We'll see how this works a bit later.
Next up is the doGet() method, which handles normal browser requests. It takes two parameters: an HttpServletRequest that contains detailed information about the browser request, and a HttpServletResponse that the servlet can use to control its response to the browser:
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Once again, the method is able to throw a ServletException to signal an error in the servlet. The servlet specification also allows this function to throw an IOException, in case there is a problem reading the request, or writing the response.
In response to normal browser requests, we want to display the current to-do list and give the user the ability to add items to the list, and also to delete existing items on the list. First, we must let the HttpServletResponse object know what content type we will be sending back to the browser:
response.setContentType("text/html");
Now we can ask it for a PrintWriter object that we can use to send HTML code to the browser:
PrintWriter out = response.getWriter();
The PrintWriter object lets us send HTML code to the browser using its println() method:
out.println("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n" +
" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">");
out.println("<html xmlns=\"http://www.w3.org/1999/xhtml\">");
out.println("<head>");
out.println("<title>To-Do List</title>");
out.println("<meta http-equiv=\"content-type\" " +
"content=\"text/html; charset=iso-8859-1\" />");
If this doesn't look very pretty to you, then you've spotted the biggest weakness of servlets: the HTML markup is mixed in with the Java logic of your application, which makes it difficult to read and maintain.
Pressing on, we want to link a CSS style sheet to our page:
out.println("<link rel=\"stylesheet\" type=\"text/css\" href=\"" +
request.getContextPath() + "/styles.css\" />");
out.println("</head>");
out.println("<body>");
Since we don't yet know which directory of the web server our application will be installed in, we don't know the path at which resources like style sheets and images will be available. The above code uses the request object's getContextPath() method to obtain the path to the root of our application, and from there we can point to the styles.css file, which we'll create a little later.
Next up, we'll display our to-do list in an HTML form menu. We can start by grabbing an Iterator from our ToDoList with its getToDoItems() method. Then, we'll check to see if it contains any items with its hasNext() method:
Iterator toDoItems = toDoList.getToDoItems();
if (toDoItems.hasNext()) {
We want the form to submit back to the same URL, so that this servlet can handle that request as well, so we use the getRequestURI() method to get it:
out.println("<form action=\"" + request.getRequestURI() +
"\" method=\"post\">");
Since HTML form menus turn into drop-down lists with a size of 1, we want the <select> tag to have a size of at least 2, but we also want it to stretch to accomodate the number of items in our to-do list. We use the Math.max() method choose between 2 and the total number of items in the list, as given by the getItemCount() method of our ToDoList:
out.println("<select name=\"deleteid\" size=\"" +
Math.max(2, toDoList.getItemCount()) + "\">");
We can then loop through the toDoItems with a while loop. Each time we move through the loop, we pull a ToDoItem out of the Iterator with next(). Since it doesn't know it contains ToDoItems, we need to cast them to the right class before we can use them:
while (toDoItems.hasNext()) {
ToDoItem toDoItem = (ToDoItem) toDoItems.next();
For each item, we want to create a <option> tag with the item's ID as its value. Outputting the ID is no problem, since it's just an integer, but the to-do item itself gets tricky. What if the to-do item contains HTML code -- possibly even malicious script code? We don't want to output that stuff and have it interpreted by the browser as part of our site! The solution is to escape any special characters in the value before printing it out.
This is another area where servlets are weak: there is no built-in HTML escaping functionality in the servlet API or in Java in general (at least none that is accessible to us). Thankfully, this is a longstanding issue, and 3rd party classes have been created to do the job. The one I've chosen is developed and distributed by AnyWare. The class is called uk.co.anyware.html.HTMLEscaper, and its source code and license are included in the code archive I've provided below.
To use HTMLEscaper, drop the class (and source code if you want to hold onto it) into the WEB-INF/classes directory and add the required import to the top of the ToDoServlet class:
import uk.co.anyware.html.*;
With the class in hand, we can now safely output the to-do item. We'll also polish off the rest of the form with a submit button for deleting selected items:
out.println("</select>");
out.println("<input type=\"submit\" value=\"Delete Selected Item\" />");
out.println("</form>");
}
We'll finish off the page with a second form, this time for submitting new to-do items:
out.println("<form action=\"" + request.getRequestURI() +
"\" method=\"post\">");
out.println("<input type=\"text\" name=\"newtodo\" />");
out.println("<input type=\"submit\" value=\"Add New Item\" />");
out.println("</form>");
out.println("</body>");
out.println("</html>");
out.close();
}
That takes care of displaying the initial page to the user, but we've now got two forms submitting back to this same servlet!
Both of these forms submit using the POST method (method="post"), so we could handle them in the servlet's doPost() method. But to make the servlet as flexible as possible we'll also support submissions for adding new to-do items, and deleting existing ones, via GET requests. To do this, we'll simply feed POST requests back into our doGet() method:
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
At the top of doGet(), we now need to check for and process our form submissions. Here's the code for handling new to-do items:
String newToDo = request.getParameter("newtodo");
if (newToDo != null) {
toDoList.addItem(newToDo);
// Redirect to self
response.sendRedirect(request.getRequestURI());
return;
}
The getParameter() method lets us retrieve a submitted value with a given name as a string. If the value isn't null, we know we have a submission on our hands. We add it to the to-do list using its addItem method, then redirect the browser to the current page with the response's sendRedirect() method.
If we didn't redirect the browser, the rest of the doGet() method would indeed display the updated to-do list for the user, but refreshing the page would cause the form submission to be sent again, resulting in a duplicate entry on the to-do list. Redirecting the browser back to the same page forces the browser to think of it as a separate request, and therefore it will not resubmit the form when refreshing the page.