[ Team LiB ] Previous Section Next Section

20.10 ListManager Controller

The controller class for our ListManager application is named Controller, and is listed in Example 20-9. When first created, its init( ) method connects to a database and uses that database connection to create the UserFactory object that manages mailing list subscriptions. The most important thing to understand about this servlet is that its doGet( ) method handles all requests made to the ListManager application. Regardless of what page is displayed in response, the request is first handled through the Controller servlet.

The key to making this work is the WEB-INF/web.xml configuration file in the je3.war web application archive. This file contains the servlet initialization parameters used in the init( ) method to specify how to connect to the database. Most importantly, however, the web.xml file includes servlet mappings that specify that the Controller servlet should be invoked in response to the filename "ListManager/" (prefixed with the appropriate host, port, and "je3/", of course) and also in response to any filename of the form "ListManager/*.action". The doGet( ) method looks up the name by which the servlet was invoked, and uses that to dispatch to a method that can take the appropriate action. Hyperlinks within the web application all include a ".action" suffix, so they are all directed back to the Controller servlet.

The Controller servlet knows how to handle actions named "login", "edit", "unsubscribe", and "logout", and dispatches to methods with the same name. An important point about these method is that they each return a RequestDispatcher object that represents the page—or View—that the servlet should display to represent the new state of the Model. The controller servlet calls the forward( ) method of the RequestDispatcher to forward the request to the new page. (RequestDispatcher also defines a useful include( ) method for including the output of one servlet or JSP page within another.) Note that the comments for the login( ), edit( ), and related methods make a point of specifying exactly which parameters must be included with the request. When we examine the JSP pages that constitute the View objects, we'll see that these pages provide those necessary parameters.

Example 20-9. Controller.java
package je3.servlet;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;

/**
 * This is the Controller servlet for the ListManager web application.  It must
 * be configured to be invoked in response to URLs ending with ".action".  When
 * it is invoked this way, it uses the file name it is invoked as
 * ("login.action", "edit.action", etc.) to determine what to do.  Each
 * supported action name has a corresponding method which performs the
 * requested action and returns an appropriate View object in the form of a
 * a RequestDispatcher wrapped around a JSP page. The servlet dispatches to the
 * JSP page which generates an HTML document to display to the user.
 */
public class Controller extends HttpServlet {
    Connection db;                // Database connection
    UserFactory userFactory;      // Factory for managing User objects

    /**
     * This method is called when the servlet is first created. It reads its
     * initialization parameters and uses them to connect to a database.
     * It uses the database connection to create a UserFactory object.
     */
    public void init( ) throws ServletException {
        // Read initialization parameters from the web.xml deployment file
        ServletConfig config = getServletConfig( );
        String jdbcDriver = config.getInitParameter("jdbcDriver");
        String jdbcURL = config.getInitParameter("jdbcURL");
        String jdbcUser = config.getInitParameter("jdbcUser");
        String jdbcPassword = config.getInitParameter("jdbcPassword");
        String tablename = config.getInitParameter("tablename");

        // Use those parameters to connect to the database
        try {
            // Load the driver class.
            // It registers itself; we don't need to retain the returned Class
            Class.forName(jdbcDriver);

            // Connect to database.  If the database server ever crashes,
            // this Connection object will become invalid, and the servlet
            // will crash too, even if the database server has come back up.
            db = DriverManager.getConnection(jdbcURL, jdbcUser, jdbcPassword);

            // Use the DB connection to instantiate a UserFactory object
            userFactory = new UserFactory(db, tablename);
        }
        catch(Exception e) {
            log("Can't connect to database", e);
            throw new ServletException("Can't connect to database", e);
        }

        // Save an init param where our JSP pages can find it.  They need
        // this so they can display the name of the mailing list.
        ServletContext context = config.getServletContext( );
        context.setAttribute("listname",config.getInitParameter("listname"));
    }

    /**
     * If the servlet is destroyed, we need to release the database connection
     */
    public void destroy( ) {
        try { if (db != null) db.close( ); }
        catch(SQLException e) {  }
    }

    /* Handle POST requests as if they were GET requests */
    public void doPost(HttpServletRequest req,HttpServletResponse resp)
        throws IOException, ServletException 
    {
        doGet(req, resp);
    }

    public void doGet(HttpServletRequest req, HttpServletResponse resp)
        throws IOException, ServletException 
    {
        // Look up the information we need to dispatch this request
        // We need to know what name we were invoked under and whether there
        // is already a User object in the session.
        String name = req.getServletPath( );
        User user = (User) req.getSession(true).getAttribute("user");

        // This will hold the page we dispatch to for the response
        RequestDispatcher nextPage; 

        // If no user is defined yet, go to the login page.
        // Otherwise, dispatch to one of the methods below based on the name
        // by which we were invoked (see web.xml for the mapping).  The page
        // to display is the return value of the method we dispatch to.
        try {
            if (name.endsWith("/login.action"))
                nextPage = login(req, resp);
            else if (user == null)
                nextPage = req.getRequestDispatcher("login.jsp");
            else if (name.endsWith("/edit.action"))
                nextPage = edit(req, resp);
            else if (name.endsWith("/unsubscribe.action"))
                nextPage = unsubscribe(req, resp);
            else if (name.endsWith("/logout.action"))
                nextPage=logout(req, resp);
            else {
                // If we don't recoginze the name we're invoked under, 
                // send a HTTP 404 error.
                resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Not Found");
                return;
            }
        }
        catch(SQLException e) {
            // If anything goes wrong while processing the action, then
            // output an error page.  This demonstrates how a servlet can 
            // produce its own output instead of forwarding to a JSP page.
            // We could also use resp.sendError( ) here to send an error code.
            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            resp.setContentType("text/html");
            PrintWriter out = resp.getWriter( );
            out.print("<h1>Error</h1>");
            out.print("An unexpected error has occurred.<pre>");
            out.print(e);
            out.print("</pre>Please contact the webmaster.");
            return;
        }
        // Now send the nextPage as the response to the client.
        // See the RequestDispatcher class.
        nextPage.forward(req, resp);
    }

    // This method handles "/login.action", which is either a request to
    // subscribe a new user, or a request to log in an existing subscriber.
    // A form that links to "/login.action" must define a parameter named
    // "email" and a parameter named "password".  If this is a subscription
    // request for a new user, the parameter "subscribe" must also be defined.
    RequestDispatcher login(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException, SQLException
    {
        // This action can dispatch to one of two pages
        RequestDispatcher loginPage = req.getRequestDispatcher("login.jsp");
        RequestDispatcher editPage = req.getRequestDispatcher("edit.jsp");

        // Get parameters from the request
        String email = req.getParameter("email");
        String password = req.getParameter("password");

        // Make sure e-mail address is not the empty string!
        if (email.length( ) == 0) {
            req.setAttribute("loginMessage",
                             "You must specify an e-mail address");
            return loginPage;
        }

        // Now try to subscribe or login.  If all goes well, we'll end up
        // with a User object
        User user = null;
        try {
            // This action is either for subscribing a new user or for 
            // logging in an existing user.  It depends on which submit button
            // was pressed.
            if (req.getParameter("subscribe") != null) {
                // A new subscription
                user = userFactory.insert(email, password);
            }
            else {
                // A login
                user = userFactory.select(email, password);
            }
        }
        // If anything goes wrong, we send the user back to the login page
        // with an error message.
        catch(UserFactory.NoSuchUser e) {
            req.setAttribute("loginMessage", "Unknown e-mail address.");
            return loginPage;
        }
        catch(UserFactory.BadPassword e) {
            req.setAttribute("loginMessage", "Incorrect Password");
            return loginPage;
        }
        catch(UserFactory.UserAlreadyExists e) {
            req.setAttribute("loginMessage", email + " is already subscribed");
            return loginPage;
        }

        // If we got here, the user is subscribed or logged in. Store the User
        // object in the current session and move on to the edit page.
        HttpSession session = req.getSession(true);
        session.setAttribute("user", user);
        return editPage;
    }

    // This method handles the URL "/edit.action".
    // A form that links to this URL must define parameters "html" and
    // "digest" if the user wants HTML messages or digests.
    RequestDispatcher edit(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException, SQLException
    {
        // Update the user's email delivery preferences based on request params
        User user = (User) req.getSession( ).getAttribute("user");
        user.setPrefersHTML(req.getParameter("html") != null);
        user.setPrefersDigests(req.getParameter("digest") != null);
        // Ask the factory to save the new preferences to the database
        userFactory.update(user);
        // And re-display the edit page
        return req.getRequestDispatcher("edit.jsp");
    }

    // This method handles the URL "/unsubscribe.action"
    // No parameters are necessary for this action.
    RequestDispatcher unsubscribe(HttpServletRequest req,
                                  HttpServletResponse resp)
        throws ServletException, IOException, SQLException
    {
        // Get the User object from the session
        User user = (User) req.getSession( ).getAttribute("user");
        // Note the e-mail address before destroying it.
        String email = user.getEmailAddress( );
        // Delete the user from the database
        userFactory.delete(user);
        // Terminate the session
        req.getSession( ).invalidate( ); // log out
        // Now display the login page again with an unsubscribed message
        req.setAttribute("loginMessage", 
                         email + " unsubscribed and logged out.");
        return req.getRequestDispatcher("login.jsp");
    }

    // This method handles the URL "/logout.action".
    // No parameters are necessary for this action.
    RequestDispatcher logout(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException, SQLException
    {
        // Destroy the session object, and return to the login page with 
        // a "logged out" message for confirmation.
        User user = (User) req.getSession( ).getAttribute("user");
        req.setAttribute("loginMessage", user.getEmailAddress( )+" logged out");
        req.getSession( ).invalidate( ); // delete session
        return req.getRequestDispatcher("login.jsp");
    }
}
    [ Team LiB ] Previous Section Next Section