Multipage Example

This example shows how one might do a search results page.  It breaks up the search results into multiple pages.  It also shows how to handle session timeouts.  If you haven't already done so, please read How to Create Web Pages With Forms (Java Developer) or this will make no sense to you.

1. State Model

Let's say the spec looks like this:

"We want to simulate search and results pages.  There should be a page where we enter the number of results to return (this would simulate a search query).  It should look something like this:"


"There should also be an error page (not shown) if the user enters anything but a positive integer into the field."

"The results page should show at most 10 results per page and should provide links for next, previous, and results 1-10, 11-20, etc.  The links should only be there if there is a next or a previous."

Drawing the state diagram, we realize we have something like this:

In the diagram, "numbered navigation link" refers to any of the 1-10 | 11-20 | 21-27 links that take you directly to those results.

2. Building The Model Objects

2.1 ChoosingNumberOfResults Model

For the ChoosingNumberOfResults state, we have a relatively simple model:
/*
 * Copyright 2000 Startups.com. All Rights Reserved.
 *
 * This software is the proprietary information of Startups.com.
 * Use is subject to license terms.
 */

package com.startups.testmvc;

import com.startups.validation.ValidatableObject;
import com.startups.validation.ValidationFailedException;

/**
 * The model for the ChoosingNumberOfResults state.  In this
 * state, the user chooses the number of results to be
 * displayed in subsequent pages (this simulates a search
 * result that could span multiple pages).
**/
public class ChoosingNumberOfResultsModel extends ValidatableObject {
    private String number = "";

//
// Accessors
//
    public String getDesiredNumberOfResults() {
        return this.number;
    }

    public void setDesiredNumberOfResults(String desiredNumber) {
        this.number = desiredNumber;
    }

//
// Validations
//
    /**
     * Check that the desired number of results is an integer > 0.
    **/
    public void validateDesiredNumberOfResults() throws ValidationFailedException {
        try {
            int theNumber = Integer.parseInt(this.number);
            if (theNumber < 1) {
                throw new ValidationFailedException(
                    "Number of results must be > 0");
            }
        } catch (NumberFormatException e) {
            throw new ValidationFailedException(
                    "Number of results must be an integer.");
        } catch (NullPointerException e) {
            throw new ValidationFailedException(
                    "Somebody set the desired number to null");
        }
    }
}

2.2 DisplayingResults Model

The model for the DisplayingResults state is more complicated.  We choose the model to represent all of the results.  It follows that the model needs to be aware that not all of its results may be displayed at once.  We can think of the model as a list of results, and an index that points to the first displayed result.  Here's the listing of the entire model (we'll go through it piece by piece later):
/*
 * Copyright 2000 Startups.com. All Rights Reserved.
 *
 * This software is the proprietary information of Startups.com.
 * Use is subject to license terms.
 */

package com.startups.testmvc;

import java.util.Random;
import com.startups.validation.ValidatableObject;
import com.startups.validation.ValidationFailedException;

/**
 * The model for displaying the results of a simulated
 * search query.  These results may be displayed across
 * several jsp pages.  A single jsp view may only display
 * 10 results at a time.  Because of this, the model keeps
 * track of the position that is being displayed by the
 * current view (via getStartingIndex()).
**/
public class DisplayingResultsModel extends ValidatableObject {
    private static final Random random = new Random();
    private String[] resultsArray;
    private int startingIndex;

//
// Accessors
//
    /**
     * Set the number of results in this model.
     * Calling this method will generate a bunch
     * of random results.  In principle, this is
     * similar to what would happen if we did a
     * search.  The parameter wouldn't be a number,
     * but a string of search terms.  Instead of
     * generating random results, we'd actually
     * hit the database and come up with some
     * sensible results, which would probably
     * be more complicated than simple strings.
    **/
    public void setResultsNumber(int number) {
        this.resultsArray = new String[number];
        fillResultsArray();
    }

    /**
     * Fill the results array with random gibberish.
    **/
    private void fillResultsArray() {
        for (int i = 0; i < this.resultsArray.length; i++) {
            this.resultsArray[i] = String.valueOf(
                    DisplayingResultsModel.random.nextInt());
        }
    }

    /**
     * Sets the starting index of the results to display.
    **/
    public void setStartingIndex(int startingIndex) {
        this.startingIndex = startingIndex;
    }

    /**
     * Return the starting index of the results to display.
     * A single jsp view may only view a fraction
    **/
    public int getStartingIndex() {
        return this.startingIndex;
    }

    /**
     * Return the total number of results that could be displayed.
    **/
    public int getNumberOfResults() {
        return this.resultsArray.length;
    }

    /**
     * Return the result at the given index.  The result
     * is a simple string.
    **/
    public String getResultAt(int index) {
        return this.resultsArray[index];
    }

    /**
     * Returns the ending index of the results to display.
     * This is either the starting index + 10 or the
     * index of the last result.
    **/
    public int getEndingIndex() {
        if (this.resultsArray.length > getStartingIndex() + 10) {
            return getStartingIndex() + 10;
        } else {
            return resultsArray.length;
        }
    }
 

//
// Validation
//
    /**
     * Make sure that the starting index is >= 0 and is
     * not greater than the total number of results.
    **/
    public void validateStartingIndex() throws ValidationFailedException {
        if (getStartingIndex() < 0) {
            throw new ValidationFailedException(
                    "Starting index must be >= 0");
        }
        if (getStartingIndex() >= getNumberOfResults()) {
            throw new ValidationFailedException(
                    "Starting index must be < the number of results"
                    + " (" + getNumberOfResults() + ")");
        }
    }
}

Going through piece by piece, we have the declaration:
/*
 * Copyright 2000 Startups.com. All Rights Reserved.
 *
 * This software is the proprietary information of Startups.com.
 * Use is subject to license terms.
 */

package com.startups.testmvc;

import java.util.Random;
import com.startups.validation.ValidatableObject;
import com.startups.validation.ValidationFailedException;

/**
 * The model for displaying the results of a simulated
 * search query.  These results may be displayed across
 * several jsp pages.  A single jsp view may only display
 * 10 results at a time.  Because of this, the model keeps
 * track of the position that is being displayed by the
 * current view (via getStartingIndex()).
**/
public class DisplayingResultsModel extends ValidatableObject {
    private static final Random random = new Random();
    private String[] resultsArray;
    private int startingIndex;

The model stores a String array of simulated results, and a starting index that indicates which result the view should start showing.  The Random instance is used later to generate simulated search results.

The next part of the code contains the accessors--methods that allow the controller servlet to get and set properties of the model.  An important method is setResultsNumber(), which generates the random results via a helper method called fillResultsArray():

//
// Accessors
//
    /**
     * Set the number of results in this model.
     * Calling this method will generate a bunch
     * of random results.  In principle, this is
     * similar to what would happen if we did a
     * search.  The parameter wouldn't be a number,
     * but a string of search terms.  Instead of
     * generating random results, we'd actually
     * hit the database and come up with some
     * sensible results, which would probably
     * be more complicated than simple strings.
    **/
    public void setResultsNumber(int number) {
        this.resultsArray = new String[number];
        fillResultsArray();
    }

    /**
     * Fill the results array with random gibberish.
    **/
    private void fillResultsArray() {
        for (int i = 0; i < this.resultsArray.length; i++) {
            this.resultsArray[i] = String.valueOf(
                    DisplayingResultsModel.random.nextInt());
        }
    }

The next part of the code continues defining accessors:
    /**
     * Sets the starting index of the results to display.
    **/
    public void setStartingIndex(int startingIndex) {
        this.startingIndex = startingIndex;
    }

    /**
     * Return the starting index of the results to display.
     * A single jsp view may only view a fraction
    **/
    public int getStartingIndex() {
        return this.startingIndex;
    }

    /**
     * Return the total number of results that could be displayed.
    **/
    public int getNumberOfResults() {
        return this.resultsArray.length;
    }

    /**
     * Return the result at the given index.  The result
     * is a simple string.
    **/
    public String getResultAt(int index) {
        return this.resultsArray[index];
    }

The getEndingIndex() method returns a calculated value.  This method will be used to determine the last result to display on the page:
    /**
     * Returns the ending index of the results to display.
     * This is either the starting index + 10 or the
     * index of the last result.
    **/
    public int getEndingIndex() {
        if (this.resultsArray.length > getStartingIndex() + 10) {
            return getStartingIndex() + 10;
        } else {
            return resultsArray.length;
        }
    }
Finally, there is a validation method:
//
// Validation
//
    /**
     * Make sure that the starting index is >= 0 and is
     * not greater than the total number of results.
    **/
    public void validateStartingIndex() throws ValidationFailedException {
        if (getStartingIndex() < 0) {
            throw new ValidationFailedException(
                    "Starting index must be >= 0");
        }
        if (getStartingIndex() >= getNumberOfResults()) {
            throw new ValidationFailedException(
                    "Starting index must be < the number of results"
                    + " (" + getNumberOfResults() + ")");
        }
    }
}

3. Making The JSP Views Dynamic

Let's say the web developer has given you the following jsp skeleton files:

ChoosingNumberOfResults.jsp

<HTML>
<HEAD>
<TITLE> Multipage test </TITLE>
</HEAD>
<BODY>
<H1>Multipage test</H1>
<FORM ACTION="#" METHOD="POST">
<B>Number Of Results to Return:</B>
<INPUT TYPE="TEXT"
       NAME="#"
       VALUE="#"
       SIZE="20"
       MAXLENGTH="20">
<INPUT TYPE="SUBMIT">
</FORM>
</BODY>
</HTML>

DisplayingResults.jsp:

<HTML>
<HEAD>
<TITLE> Multipage test </TITLE>
</HEAD>
<BODY>
<H1>Multipage test results</H1>
 

<A HREF="#">Previous </A> |
<A HREF="#">Next </A> |
<A HREF="#">1 - 10

<P>

<TABLE>

        <TR>
        <TD> Result number: # </TD>
        <TD> # </TD>
        </TR>

</TABLE>
</FORM>

<HR>
<A HREF="/testmvc/ChoosingNumberOfResults.jsp">Another Search</A>
</BODY>
</HTML>

3.1 Modifying ChoosingNumberOfResults.jsp

The modifications to ChoosingNumberOfResults.jsp are straightforward:
<%@ page import="com.startups.testmvc.ChoosingNumberOfResultsModel" %>
<jsp:useBean id="choosingNumberOfResultsModel" class="com.startups.testmvc.ChoosingNumberOfResultsModel" scope="session" create="yes"/>

<HTML>
<HEAD>
<TITLE> Multipage test </TITLE>
</HEAD>
<BODY>
<H1>Multipage test</H1>
<FORM ACTION="/servlet/com.startups.testmvc.ChoosingNumberOfResultsServlet" METHOD="POST">
<B>Number Of Results to Return:</B>
<INPUT TYPE="TEXT"
       NAME="DesiredNumberOfResults"
       VALUE="<%= choosingNumberOfResultsModel.getDesiredNumberOfResults() %>"
       SIZE="20"
       MAXLENGTH="20">
<INPUT TYPE="SUBMIT">
</FORM>
</BODY>
</HTML>

3.2 Adding an Error Page

Don't forget the corresponding error page, ChoosingNumberOfResultsError.jsp, which should look familiar from the previous example:
<%@ page import="com.startups.testmvc.ChoosingNumberOfResultsModel" %>
<%@ page import="com.startups.validation.ValidatableObject" %>
<%@ page import="com.startups.validation.ValidationFailedException" %>
<%!
    //
    // Returns a red font color tag if the given fieldName in the given
    // model is invalid, otherwise returns a black font color tag.
    // This method could be broken out into a separate jsp
    // include.
    //
    public String fontColorTagFor(String fieldName, ValidatableObject model) {
        if (model.isFieldValid(fieldName)) {
            return "<FONT COLOR=\"black\">";
        } else {
            return "<FONT COLOR=\"red\">";
        }
    }
%>
<jsp:useBean id="choosingNumberOfResultsModel" class="com.startups.testmvc.ChoosingNumberOfResultsModel" scope="session" create="yes"/>

<HTML>
<HEAD>
<TITLE> Multipage test </TITLE>
</HEAD>
<BODY>
<H1>Multipage test</H1>
<%
    if (! choosingNumberOfResultsModel.isValid()) {
        //
        // print out the fields that need to be changed
        //
        %>
        <FONT COLOR="red">
        Please review the indicated fields:<BR>
        <%
        ValidationFailedException[] theExceptions =
               choosingNumberOfResultsModel.getValidationErrorList();
        for (int i = 0; i < theExceptions.length; i++) {
            %>
            &nbsp;&nbsp;&nbsp;<%= theExceptions[i].getMessage() %> <br>
            <%
        }
        %>
        </FONT>
        <%
    }
%>
<FORM ACTION="/servlet/com.startups.testmvc.ChoosingNumberOfResultsServlet" METHOD="POST">
<%= fontColorTagFor("DesiredNumberOfResults", choosingNumberOfResultsModel) %>
<B>Number Of Results to Return:</B>
</FONT>
<INPUT TYPE="TEXT"
       NAME="DesiredNumberOfResults"
       VALUE="<%= choosingNumberOfResultsModel.getDesiredNumberOfResults() %>"
       SIZE="20"
       MAXLENGTH="20">
<INPUT TYPE="SUBMIT">
</FORM>
</BODY>
</HTML>

3.3 Modifying DisplayingResults.jsp

The modifications to DisplayingResults.jsp are much more extensive, because the content on this page is nearly all dynamically generated.  Unfortunately it makes the jsp quite ugly:
<%@ page import="com.startups.testmvc.DisplayingResultsModel" %>
<jsp:useBean id="displayingResultsModel" class="com.startups.testmvc.DisplayingResultsModel" scope="session" create="yes"/>

<HTML>
<HEAD>
<TITLE> Multipage test </TITLE>
</HEAD>
<BODY>
<H1>Multipage test results</H1>

<%
    // Generate Previous | Next links, if necessary
    int theNumberOfResults = displayingResultsModel.getNumberOfResults();
    int startingIndex = displayingResultsModel.getStartingIndex();
    if (startingIndex != 0) {
        %>
        <A HREF="/servlet/com.startups.testmvc.DisplayingResultsServlet?startIndex=<%= startingIndex - 10 %>">
        Previous </A>
        <%
    } else {
        %>
        Previous
        <%
    }

    int endingIndex = displayingResultsModel.getEndingIndex();
    if (endingIndex < theNumberOfResults) {
        %>
        | <A HREF="/servlet/com.startups.testmvc.DisplayingResultsServlet?startIndex=<%= startingIndex + 10 %>">
        Next </A>
        <%
    } else {
        %>
        | Next
        <%
    }

    // Generate numbered links 1 - 10 | 11 - 20 | etc.
    for (int i = 0;
             i < theNumberOfResults;
             i += 10) {
        int theEndIndex = i + 10 < theNumberOfResults ? i + 10 : theNumberOfResults;
        if (i != startingIndex) {
            %>
            |
            <A HREF="/servlet/com.startups.testmvc.DisplayingResultsServlet?startIndex=<%= i %>">
           <%= i + 1 %> - <%= theEndIndex %>
            </A>
            <%
        } else {
            %>
            | <B>
            <%= i + 1 %> - <%= theEndIndex %> </B>
            <%
        }
    }

%>

<P>

<TABLE>
<%
    for (int i = displayingResultsModel.getStartingIndex();
             i < displayingResultsModel.getEndingIndex();
             i++) {
        %>
        <TR>
        <TD> Result number: <%= i + 1 %> </TD>
        <TD> <%= displayingResultsModel.getResultAt(i) %> </TD>
        </TR>
        <%
    }
%>
</TABLE>
</FORM>

<HR>
<A HREF="/testmvc/ChoosingNumberOfResults.jsp">Another Search</A>
</BODY>
</HTML>

Let's look at the thing piece by piece so we can understand the changes.  The first addition should be familiar:
<%@ page import="com.startups.testmvc.DisplayingResultsModel" %>
<jsp:useBean id="displayingResultsModel" class="com.startups.testmvc.DisplayingResultsModel" scope="session" create="yes"/>

<HTML>
<HEAD>
<TITLE> Multipage test </TITLE>
</HEAD>
<BODY>
<H1>Multipage test results</H1>

The next part creates the dynamically generated "Previous" navigation link.  It checks if there is a Previous link, and if there is it generates the HREF.  If there isn't a previous link (e.g. it's the first page), it just prints out "Previous" without making it an HREF.
<%
    // Generate Previous | Next links, if necessary
    int theNumberOfResults = displayingResultsModel.getNumberOfResults();
    int startingIndex = displayingResultsModel.getStartingIndex();
    if (startingIndex != 0) {
        %>
        <A HREF="/servlet/com.startups.testmvc.DisplayingResultsServlet?startIndex=<%= startingIndex - 10 %>">
        Previous </A>
        <%
    } else {
        %>
        Previous
        <%
    }
Notice the HREF (in red).  Note that we are referring to the controller servlet, even though this isn't a form.  The HREF issues an HTTP GET request rather than an HTTP POST request.  For GET requests, we don't have form elements to hold our parameters, so we must pass them in using a parameter string (the part that begins with a ? character).  The parameter "startIndex" will get passed to the controller servlet as if it were entered in a form.  The actual value of the parameter is the current model value of the startingIndex minus 10, since there are 10 results per page.

The next part is similar and generates the "Next" link:

    int endingIndex = displayingResultsModel.getEndingIndex();
    if (endingIndex < theNumberOfResults) {
        %>
        | <A HREF="/servlet/com.startups.testmvc.DisplayingResultsServlet?startIndex=<%= startingIndex + 10 %>">
        Next </A>
        <%
    } else {
        %>
        | Next
        <%
    }
The next part generates the numbered results page navigation links (e.g. 1-10 | 11-20 | 21-28).  The current page is not hyperlinked and is displayed in bold:
    // Generate numbered links 1 - 10 | 11 - 20 | etc.
    for (int i = 0;
             i < theNumberOfResults;
             i += 10) {
        int theEndIndex = i + 10 < theNumberOfResults ? i + 10 : theNumberOfResults;
        if (i != startingIndex) {
            %>
            |
            <A HREF="/servlet/com.startups.testmvc.DisplayingResultsServlet?startIndex=<%= i %>">
           <%= i + 1 %> - <%= theEndIndex %>
            </A>
            <%
        } else {
            %>
            | <B>
            <%= i + 1 %> - <%= theEndIndex %> </B>
            <%
        }
    }

%>

The last part of the jsp simply loops through the currently displayed indices and prints out up to 10 results on the page:
<P>

<TABLE>
<%
    for (int i = displayingResultsModel.getStartingIndex();
             i < displayingResultsModel.getEndingIndex();
             i++) {
        %>
        <TR>
        <TD> Result number: <%= i + 1 %> </TD>
        <TD> <%= displayingResultsModel.getResultAt(i) %> </TD>
        </TR>
        <%
    }
%>
</TABLE>
</FORM>

<HR>
<A HREF="/testmvc/ChoosingNumberOfResults.jsp">Another Search</A>
</BODY>
</HTML>

4. Building The Controller Servlets

4.1 Building ChoosingNumberOfResultsServlet

The servlets are relatively straightforward.  Here's ChoosingNumberOfResultsServlet:
/*
 * Copyright 2000 Startups.com. All Rights Reserved.
 *
 * This software is the proprietary information of Startups.com.
 * Use is subject to license terms.
 */

package com.startups.testmvc;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.ServletException;
import java.io.IOException;
import com.startups.webui.ControllerServlet;

/**
 *
**/
public class ChoosingNumberOfResultsServlet extends ControllerServlet {
    /**
     * Handle an Http post.  The request is expected to
     * contain the parameter "DesiredNumberOfResults" from the form
     * that points to this servlet.
    **/
    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response)
                          throws ServletException, IOException {
        updateModel(request);
        if (getModel(request).isValid()) {
            createDisplayingResultsModel(request, response);
            enterState("/testmvc/DisplayingResults.jsp", request, response);
        } else {
            enterState("/testmvc/ChoosingNumberOfResultsError.jsp", request, response);
        }
    }

    /**
     * Retrieve the model for this state from the session
     * and update it using values retrieved from the request.
     * If the model doesn't already exist, create it.
    **/
    private void updateModel(HttpServletRequest request) {
        HttpSession theSession = request.getSession();
        ChoosingNumberOfResultsModel theModel = getModel(request);
        if (theModel == null) {
            theModel = new ChoosingNumberOfResultsModel();
            theSession.putValue("choosingNumberOfResultsModel", theModel);
        }
        //
        // Set model properties with values from form
        //
        theModel.setDesiredNumberOfResults(
                request.getParameter("DesiredNumberOfResults"));
    }

    /**
     * Creates the model used for the next state and adds it to
     * the session.
    **/
    private void createDisplayingResultsModel(HttpServletRequest request,
            HttpServletResponse response) {
        DisplayingResultsModel theModel = new DisplayingResultsModel();
        theModel.setResultsNumber(Integer.parseInt(
                getModel(request).getDesiredNumberOfResults()));
        HttpSession theSession = request.getSession();
        theSession.putValue("displayingResultsModel", theModel);
    }

    /**
     * Return the model for this state given a request.
    **/
    private ChoosingNumberOfResultsModel getModel(HttpServletRequest request) {
        HttpSession theSession = request.getSession();
        return (ChoosingNumberOfResultsModel)
                theSession.getValue("choosingNumberOfResultsModel");
    }

    /**
     * See doPost().
    **/
    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response)
                         throws ServletException, IOException {
        doPost(request, response);
    }
}

The servlet is quite similar to the servlet in the previous example.  It updates the model, checks for validity, and forwards the request to either an error page or the results page.
 

4.2 Building DisplayingResultsServlet

The DisplayingResultsServlet has a few new concepts, so we'll discuss it piece by piece after we provide the listing:
/*
 * Copyright 2000 Startups.com. All Rights Reserved.
 *
 * This software is the proprietary information of Startups.com.
 * Use is subject to license terms.
 */

package com.startups.testmvc;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.ServletException;
import java.io.IOException;
import com.startups.webui.SessionValidControllerServlet;

/**
 * Controller for the DisplayingResults state.
**/
public class DisplayingResultsServlet extends SessionValidControllerServlet {
    /**
     * Handle an Http get.  The request may contain the parameter
     * "startIndex" from the jsp that points to this servlet,
     * where the value of "startIndex" should be a non-negative
     * integer.
    **/
    protected void performGet(HttpServletRequest request,
                              HttpServletResponse response)
                              throws ServletException, IOException {
        updateModel(request);
        if (getModel(request).isValid()) {
            enterState("/testmvc/DisplayingResults.jsp", request, response);
        } else {
            enterState("/testmvc/DisplayingResultsError.jsp", request, response);
        }
    }

    /**
     * Retrieve the model for this state from the session
     * and update it using values retrieved from the request.
     * If the model doesn't already exist, create it.
    **/
    private void updateModel(HttpServletRequest request) {
        HttpSession theSession = request.getSession();
        DisplayingResultsModel theModel = getModel(request);
        if (theModel == null) {
            theModel = new DisplayingResultsModel();
            theSession.putValue("displayingResultsModel", theModel);
        }
        int theStartIndex = Integer.parseInt(
                request.getParameter("startIndex"));

        theModel.setStartingIndex(theStartIndex);
    }
 

    /**
     * Return the model for this state given a request.
    **/
    private DisplayingResultsModel getModel(HttpServletRequest request) {
        HttpSession theSession = request.getSession();
        return (DisplayingResultsModel)
                theSession.getValue("displayingResultsModel");
    }

    /**
     * See doGet().
    **/
    protected void performPost(HttpServletRequest request,
                               HttpServletResponse response)
                               throws ServletException, IOException {
        performGet(request, response);
    }
}

The first thing to notice is that the servlet extends SessionValidControllerServlet instead of ControllerServlet:
/*
 * Copyright 2000 Startups.com. All Rights Reserved.
 *
 * This software is the proprietary information of Startups.com.
 * Use is subject to license terms.
 */

package com.startups.testmvc;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.ServletException;
import java.io.IOException;
import com.startups.webui.SessionValidControllerServlet;

/**
 * Controller for the DisplayingResults state.
**/
public class DisplayingResultsServlet extends SessionValidControllerServlet {

SessionValidControllerServlet extends ControllerServlet and adds session expiration checking--it checks that the session has not expired before letting subclasses do their work (see the javadoc for SessionValidControllerServlet for more info).  The reason why we need this is because the DisplayingResults state spans multiple pages.  Since a user can leave his or her browser after seeing any page, the session may expire.  Since the session contains the model, subsequent requests will fail with a nasty error. SessionValidControllerServlet checks for this condition and redirects the user to an error page if the session has expired.  The use of SessionValidControllerServlet leads to another difference from our previous example in the next few lines of code:
    /**
     * Handle an Http get.  The request may contain the parameter
     * "startIndex" from the jsp that points to this servlet,
     * where the value of "startIndex" should be a non-negative
     * integer.
    **/
    protected void performGet(HttpServletRequest request,
                              HttpServletResponse response)
                              throws ServletException, IOException {
        updateModel(request);
        if (getModel(request).isValid()) {
            enterState("/testmvc/DisplayingResults.jsp", request, response);
        } else {
            enterState("/testmvc/DisplayingResultsError.jsp", request, response);
        }
    }
Note that we now no longer define doGet(), but rather a method called performGet()SessionValidControllerServlet defines doGet() to check for session expiration, then calls an abstract method called performGet(), which can be interepreted as "do whatever you would do if the session is found to be valid."  In other respects, the method is similar to our previous example, with one subtle point.  Note that there is an error page associated with DisplayingResults.  At first glance, it appears as though there is no way to get the model into an invalid state, since the model variables seem to all be set programmatically.  However, the reason this state exists is because when we hit the "next", "previous", or "1-10" links, we are issuing HTTP GET requests instead of POST requests.  HTTP GET requests pass parameters via a URL query string (recall in our DisplayingResults.jsp page we used this property to pass the startIndex parameter), so the urls are visible in the browser url field.  This means someone could simply type in a URL that looks like "http://ted.startups.com:8082/servlet/com.startups.testmvc.DisplayingResultsServlet?startIndex=-1" and pass in an invalid startIndex.  The validation we perform will catch this.

DisplayingResultsError.jsp doesn't need to highlight any form input, since there was no form involved.  It doesn't have to be as polished because the only way to reach it is if a user does some clearly unexpected behavior such as editing the url field in the browser:

<HTML>
<HEAD>
<TITLE> Internal Error </TITLE>
</HEAD>
<BODY>
<H1>Internal Error</H1>

Sorry, an internal error has occurred.

<HR>
<A HREF="/testmvc/ChoosingNumberOfResults.jsp">Another Search</A>
</BODY>
</HTML>

That's it, we're done!  The working pages look like this:


 
 

Summary of New Things Introduced In This Example

Version History

0.1, July 7, 2000.  First version of this example.