How to Create Web Pages With Forms (Java Developer)

Draft 0.9
Aug 22, 2000
Ted Lee (tlee@startups.com)

1. Applicability

This is a cookbook on how to create web pages with forms from the perspective of the Java developer.

Read this document when all of the following are true:

If this document doesn't meet your needs, you may want to look at:

2. Steps for Creating a Web Page With A Form

Typically there will be at least three people involved in building a form--a web developer, a Java programmer, and a site producer.  Briefly, the responsibilities of each are: The specific responsibilities of each for producing web forms are shown below:

The following is a detailed description of the three balloons that involve you, the Java developer.  The steps you need to take to build a form, along with an example set of pages.  The summary is:
  1. Agree on a state model of the web site
  2. Understand the MVC concept
  3. Build the model object
  4. Make the JSP views dynamic
  5. Build the controller servlets

2.1  Agree On a State Model Of the Web Site

Before you start creating a page, you will ideally have a spec of what the pages are supposed to do.  This might be a verbal description or a more formal set of documents.  The first thing you should do is to find, discover, or create a state model of the set of web pages you intend to build.  The state model does the following: If you're lucky, your site producer will have already produced the state model for you.  However, it doesn't matter who creates the state model--it could be you, it could be the information architect who designed the spec, it could be the Web developer.  The important thing is that everybody agrees on the same state model, since all the coding that everybody does will be affected by it.  The following supersimplified example shows how you might create a state model.  Imagine that the following images are a spec for a series of web pages to be built:

"We want a page with a form where we can enter the name of an existing user in a textfield and a legal structure from a pulldown.  We will retrieve the legal structure pulldown entries from the database (we can't just put them into html).  It should look something like this."

"Also, if the username is invalid and the user submits, it should come back to this page and highlight Username in red, along with a message that says what the problem was."
"When the username is really valid, hitting submit should take them to a success page which shows the userID and the email address of that user."


The first thing to do is to name all the pages.  Try to pick something which is descriptive of the user task, or the state that the user is in when they are looking at the page.  For example, you might pick:

Then draw a state diagram showing the possible transitions between the pages:
The balloons indicate states, and the arrows indicate transitions between states.  Note that the EnteringUserName state has a state transition that leads back to the same state--if the username isn't valid, it sends you back into the same state (with appropriate error messages).  Please make sure that everyone (you, the Java developer, the spec writer) agree on the state model!

2.2 Understand the Model View Controller (MVC) Concept

Each state in your state model (except for error pages) is represented by three code components:  a model, a view, and a controller (MVC).  The model represents the state in presentation-independent form--it contains the data which is typically read into the page at runtime and is a Java object.  The view is the presentation of the state--it uses model variables to render itself and is a JSP page.  The controller receives user input and determines which new state to transition to depending on that input, and is realized as a Java servlet.  The relationships between model, view, and controller are shown below:


Arrows indicate dependencies.  The reason why we break up each state into three components is that each component has a very specific responsibility, which makes for easier maintenance.  In addition, there is a fair amount of separation between the presentation component and the logic, which allows a Web developer to be less dependent on the Java developer if he or she wants to make purely visual changes.  In our situation, the View is owned by the web developer, while the model and controller components are owned by the Java developer.  For more information, see the references in the design discussion section.
 
Concept Implementation Responsibility
Model Java object named <StateName>Model.java Business logic.  Includes dynamic state, validation.  May make use of data access and persistent objects.
View JSP page named <StateName>.jsp Present dynamic state of application to end user using Model properties.
Controller Java servlet named <StateName>Servlet.java Handle control flow between pages.  Updates the Model with form input.

2.3 Build the Model Objects

The model is the guts of the page.  It contains the data that the jsp grabs at runtime to render itself, along with the heavy duty business logic.  The state of the model is also used by the controller servlet to determine where to go next.  In the example we introduced earlier, we'll first examine what we need to do to implement the model for the EnteringUserName state.  The first thing to do is to name the java class representing the model according to a naming convention.  Name the model object <state name>Model.java.  So for our example, we name the class EnteringUserNameModel.  As we can see from the spec for that page, it's clear that the model must have access to some kind of user information.  It also needs to obtain the names of legal structures from somewhere.  Both of these need to be in the model.  In addition, there are some validation requirements.  A sample class definition follows:
/*
 * 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.webui.Model;
import com.startups.validation.ValidationFailedException;
import com.vivid.startups.users.User;
import com.vivid.exception.BaseException;
import com.vivid.ServiceFactory;
import com.vivid.dataaccess.DBAccess;

/**
 * The model object for the EnteringUserName state.
 * It is currently a facade for a User object.
**/
public class EnteringUserNameModel extends Model {
    private static final String[] LEGAL_STRUCTURES = new String[] {
            "C Corporation",
            "S Corporation",
            "LLC",
            "Partnership",
            "Sole Proprietorship",
            "No Formal Entity",
            "Other"};

    private User user = new User();

//
// Accessors
//
    public void setUserID(String userID) {
        this.user.setUserID(userID);
    }

    public User getUser() {
        return this.user;
    }

    public String getUserID() {
        return this.user.getUserID();
    }

    public String[] getLegalStructures() {
        return LEGAL_STRUCTURES;
    }

//
// Validation
//
    public void validateUserID() throws ValidationFailedException {
        if (getUserID() == null || getUserID().trim().equals("")
                || getUserID().indexOf("'") != -1) {
            throw new ValidationFailedException("Invalid user name");
        }
        try {
            DBAccess db = ServiceFactory.getInstance().getDBAccess();
            getUser().load(db);
        } catch (BaseException e) {
            throw new ValidationFailedException("User does not exist");
        }
    }

}


Let's go through this listing piece by piece.  The model is declared to extend a Model object:

public class EnteringUserNameModel extends Model {

Model is the superclass of all model objects and provides two types of functionality to its subclasses.  First, it provides a validation framework.  Second, it provides a few convenience methods for use from within JSP views (please see the javadoc for Model for more information).  Since we know the model contains some user information, it makes sense to have an instance variable representing the current user, hence the User object instance variable declaration:

    private static final String[] LEGAL_STRUCTURES = new String[] {
            "C Corporation",
            "S Corporation",
            "LLC",
            "Partnership",
            "Sole Proprietorship",
            "No Formal Entity",
            "Other"};

    private User user = new User();


Normally, there would be an actual instance variable for the legal structures, which would probably be read out of a database somewhere.  For the purposes of this toy example, we'll just pretend that these values came out of the database and just declare them as a static String array to fake it.

The next bit of code contains method definitions to allow the view or controller components to inspect or modify the state of the model:

//
// Accessors
//
    public void setUserID(String userID) {
        this.user.setUserID(userID);
    }

    public User getUser() {
        return this.user;
    }

    public String getUserID() {
        return this.user.getUserID();
    }

    public String[] getLegalStructures() {
        return LEGAL_STRUCTURES;
    }


Finally, the last bit of code contains any validations that should be done on the model.  Recalling our state diagram, we note that in order to make certain state transitions we need to have a valid user.  Because our model object extends Model, which in turn extends ValidatableObject, it inherits the isValid() method.  This method invokes all methods on the object with a method signature void validate<something>() throws ValidationFailedException, and saves the validation errors for later retrieval (see the javadoc for ValidatableObject for more info).

//
// Validation
//
    public void validateUserID() throws ValidationFailedException {
        if (getUserID() == null || getUserID().trim().equals("")
                || getUserID().indexOf("'") != -1) {
            throw new ValidationFailedException("Invalid user name");
        }
        try {
            DBAccess db = ServiceFactory.getInstance().getDBAccess();
            getUser().load(db);
        } catch (BaseException e) {
            throw new ValidationFailedException("User does not exist");
        }
    }


The method defined above validates that the userID is legal, and tries to load up the user from persistent storage.  If for some reason these fail, it throws a ValidationFailedException.  We can define as many validation methods as we need, as long as they have the proper method signature.  It's actually a good idea at this point to write some test code to ensure that the validations do the right thing.  Since your model is view-independent, there's no reason why you can't write test code that doesn't require a web server or servlet engine.  For example, you might write the following automated test suite using JUnit.

/*
 * 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 junit.framework.*;

/**
 * Unit test for EnteringUserNameModel class.
**/
public class EnteringUserNameModelTest extends TestCase {
    private EnteringUserNameModel model;

//
// Constructors
//
    public EnteringUserNameModelTest(String name) {
        super(name);
    }

//
// Test Fixtures
//
    protected void setUp() {
        this.model = new EnteringUserNameModel();
    }

    protected void tearDown() {
        this.model = null;
    }

//
// Tests
//
    public void testNullUserID() {
        this.model.setUserID(null);
        assert(this.model.isValid() == false);
    }

    public void testBlankSpaceUserID() {
        this.model.setUserID("  ");
        assert(this.model.isValid() == false);
    }

    public void testUserIDWithQuotes() {
        this.model.setUserID("Harry''Potter");
        assert(this.model.isValid() == false);
    }

    // etc.

//
// Suite
//
    public static Test suite() {
        return new TestSuite(EnteringUserNameModelTest.class);
    }
}


There is another state which contains a model.  It's the SuccessModel corresponding to the Success state, where the user's ID and email address are displayed:

/*
 * 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.webui.Model;
import com.vivid.startups.users.User;

/**
 * The model for the Success state, where a user has been
 * found to exist.
**/
public class SuccessModel extends Model {
    private User user;

//
// Constructors
//
    public SuccessModel(User user) {
        setUser(user);
    }

//
// Accessors
//
    public void setUser(User user) {
        this.user = user;
    }

    public String getUserID() {
        return this.user.getUserID();
    }

    public String getEmailAddress() {
        return this.user.getEmailAddress();
    }
}

2.4 Make The JSP Views Dynamic

It is the web developer's responsibility to provide you with static jsp pages containing the appearance of the page.  This will typically be done through CVS.  When the web developer is done creating the preliminary static pages, he or she will commit them in CVS and inform you of their availability.  You need to do a cvs update to obtain them.  You will obtain a file with HTML along with some hash marks that you need to fill in with dynamic code.  For example, EnteringUserName.jsp might look like:
<HTML>
<HEAD>
<TITLE> Test MVC Page </TITLE>
</HEAD>
<BODY>

<H1> Test MVC Model </H1>
<FORM ACTION="#" METHOD="POST">
 

<B>Username:</B>
<INPUT TYPE="TEXT"
       NAME="#"
       VALUE="#"
       SIZE="20"
       MAXLENGTH="40">

<P>

<B>Legal Structure</B>
<SELECT NAME="#">
<OPTION VALUE="#">#
</SELECT>

<INPUT TYPE="SUBMIT">
</FORM>
</BODY>
</HTML>

And Success.jsp might look like:
<HTML>
<HEAD>
<TITLE> Test MVC Page Success</TITLE>
</HEAD>
<BODY>

<H1> Success! </H1>

<B>UserID:</B> # <BR>
<B>Email Address:</B> # <BR>

</BODY>
</HTML>
 

2.4.1 Modify the JSP For Each State

You need to fill in the hash marks with the appropriate form variable names and dynamic values.  You will also add in some code to handle validation errors.  Let's start with Success.jsp (blue indicates scripting code added, while red indicates replacements for hash marks):
<%--
 * Copyright 2000 Startups.com. All Rights Reserved.
 *
 * This software is the proprietary information of Startups.com.
 * Use is subject to license terms.
 *
--%>
<jsp:useBean id="successModel" class="com.startups.testmvc.SuccessModel" scope="session" />

<HTML>
<HEAD>
<TITLE> Test MVC Page Success</TITLE>
</HEAD>
<BODY>

<H1> Success! </H1>

<B>UserID:</B> <%= successModel.getUserID() %> <BR>
<B>Email Address:</B> <%= successModel.getEmailAddress() %> <BR>

</BODY>
</HTML>

The first significant addition is a usebean tag that indicates the model that this jsp depends on to get its dynamic data.  Also note that the hash marks have been replaced by jsp evals calling methods defined on the SuccessModel.  Since the success page does not itself include a form, there isn't any reference to any servlet code.  The EnteringUserName.jsp additions are a bit more complex.  We'll first list the changed file, then discuss each change piece by piece:

EnteringUserName.jsp:

<%--
 * Copyright 2000 Startups.com. All Rights Reserved.
 *
 * This software is the proprietary information of Startups.com.
 * Use is subject to license terms.
 *
--%>
<%@ page import="com.startups.testmvc.EnteringUserNameModel" %>

<jsp:useBean id="enteringUserNameModel" class="com.startups.testmvc.EnteringUserNameModel" scope=
"session" create="yes"/>

<HTML>
<HEAD>
<TITLE> Test MVC Page </TITLE>
</HEAD>
<BODY>

<H1> Test MVC Model </H1>
<%= enteringUserNameModel.displayFailedValidations() %>

<FORM ACTION="/servlet/com.startups.testmvc.EnteringUserNameServlet" METHOD="POST">
 

<%= enteringUserNameModel.fontColorTagFor("UserID") %>
<B>Username:</B>
</FONT>
<INPUT TYPE="TEXT"
       NAME="UserID"
       VALUE="<%= enteringUserNameModel.getUserID() %>"
       SIZE="20"
       MAXLENGTH="40">

<P>

<B>Legal Structure</B>
<SELECT NAME="LegalTypes">
<%
    String[] theLegalTypes = enteringUserNameModel.getLegalStructures();
    for (int i = 0; i < theLegalTypes.length; i++) {
        %>
        <OPTION VALUE="<%= theLegalTypes[i] %>"><%= theLegalTypes[i] %>
        <%
    }
%>
</SELECT>

<INPUT TYPE="SUBMIT">
</FORM>
</BODY>
</HTML>

Let's go through the jsp piece by piece.  The first part of the scripting code simply declares that the jsp depends on the EnteringUserNameModel:
<%@ page import="com.startups.testmvc.EnteringUserNameModel" %>
<jsp:useBean id="enteringUserNameModel" class="com.startups.testmvc.EnteringUserNameModel" scope="session" create="yes"/>
The next part contains an addition for validation purposes.  This jsp will double as an error page.  In other words, it's possible that the model could fail validation and the user could get sent back to this page.  In these cases, the page will display a list of failed validations at the top of the page in red.  The code to do this is defined in the Model object, which EnteringUserNameModel extends, and all that is needed in the jsp is a simple jsp eval:
<HTML>
<HEAD>
<TITLE> Test MVC Page </TITLE>
</HEAD>
<BODY>

<H1> Test MVC Model </H1>
<%= enteringUserNameModel.displayFailedValidations() %>

For more information on displayFailedValidations(), see the javadoc for Model.  The next part contains the form tag, which must point to a controller servlet.  Controller servlets should be named according to the convention <state name>Servlet, so you always know the name of the servlet corresponding to a given state.  Since you know what package your servlet is in, and you know the name of the servlet corresponding to this state, you can easily fill this in.
 
<FORM ACTION="/servlet/com.startups.testmvc.EnteringUserNameServlet" METHOD="POST">
The next part of the jsp is a form element.  Since you're building the servlet to process the form, you get to name the individual form elements.  Pick names corresponding to the method names on your model.  Since you have a method called getUserID() on the model object that corresponds to the value of the input field, you should name the form variable "UserID".
<%= enteringUserNameModel.fontColorTagFor("UserID") %>
<B>Username:</B>
</FONT>
<INPUT TYPE="TEXT"
       NAME="UserID"
       VALUE="<%= enteringUserNameModel.getUserID() %>"
       SIZE="20"
       MAXLENGTH="40">

<P>

The first jsp eval produces an HTML font tag for the "UserID" field using the method fontColorTagFor() defined on Model.  This tag will evaluate to either black or red color, depending on whether the UserID field has passed validation or not.  The jsp eval of the value parameter simply fills in the form with the userID of the user in the model.  Also make sure that the maxlength attributes of text fields make sense.  Most likely there are database limits that should not be exceeded!  Since you are in a better position to know what these limits are than the web developer, it is your responsibility to ensure that the field entries don't overflow the database columns.  The next bit of the jsp shows what you need to do to create an pulldown with values obtained from the model:
<B>Legal Structure</B>
<SELECT NAME="LegalTypes">
<%
    String[] theLegalTypes = enteringUserNameModel.getLegalStructures();
    for (int i = 0; i < theLegalTypes.length; i++) {
        %>
        <OPTION VALUE="<%= theLegalTypes[i] %>"><%= theLegalTypes[i] %>
        <%
    }
%>
</SELECT>
In this situation, you need to write some scriptlet code to pull the legal types out of the model and generate a bunch of option values. This completes your modification of EnteringUserName.jsp.

You are free to make changes to these jsp files.  Just remember to commit your changes to CVS, and the web developer will get them.  It's a good idea to inform your web developer when you make changes, so he or she will remember to do cvs updates.  Likewise, you should expect the web developer to occassionally modify the jsp file's HTML layout.  You'll get these changes via cvs updates.

2.5 Build the Controller Servlets

Now that you have the model and views, you need to build the controller servlets.  Name your servlets <state name>Servlet.java.  For the EnteringUserName state, the servlet would be named EnteringUserNameServlet.java.  A listing follows, with detailed descriptions after that:
/*
 * 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.webui.ControllerServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import java.io.IOException;

/**
 * The controller for two views:  EnteringUserName.jsp and
 * EnteringUserNameError.jsp.  It may forward the request
 * to either "/testmvc/Success.jsp" or "/testmvc/EnteringUserName.jsp".
 *
 * @see com.startups.testmvc.EnteringUserNameModel
**/
public class EnteringUserNameServlet extends ControllerServlet {

    /**
     * Handle an Http post.  The request is expected to
     * contain the parameter "UserID" from the form
     * that points to this servlet.
    **/
    protected void performPost(HttpServletRequest request,
                               HttpServletResponse response)
                               throws ServletException, IOException {
        updateModel(request);
        if (getModel(request).isValid()) {
            createSuccessModel(request, response);
            enterState("/testmvc/Success.jsp", request, response);
        } else {
            enterState("/testmvc/EnteringUserName.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) {
        EnteringUserNameModel theModel = (EnteringUserNameModel)
                getModel(request);
        if (theModel == null) {
            theModel = new EnteringUserNameModel();
            addModelToSession(theModel, request);
        }

        //
        // Update the model object using parameters gathered
        // from the form.
        //
        theModel.setUserID(request.getParameter("UserID"));
    }

    /**
     * Creates the model used for the next state and adds it to
     * the session.  The reason we do this here instead of
     * in the usebean declaration in the jsp is that this
     * model takes a parameter in its constructor.
    **/
    private void createSuccessModel(HttpServletRequest request,
                                    HttpServletResponse response) {
        SuccessModel theModel = new SuccessModel(
                ((EnteringUserNameModel) getModel(request)).getUser());
        addModelToSession(theModel, request);
    }
 

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

The servlet extends ControllerServlet, which provides a number of useful methods and some validation logic common to all controller servlets.  The main concept to understand about ControllerServlet is that it defines doPost() to do some framework bookkeeping and then calls performPost(), which as a subclass author is your responsibility to implement.  A similar concept exists for doGet().
/**
 * The controller for two views:  EnteringUserName.jsp and
 * EnteringUserNameError.jsp.  It may forward the request
 * to either "/testmvc/Success.jsp" or "/testmvc/EnteringUserName.jsp".
 *
 * @see com.startups.testmvc.EnteringUserNameModel
**/
public class EnteringUserNameServlet extends ControllerServlet {
The meat of the servlet is in the performPost() method.  This method contains the actual conditional logic where the servlet performs its controller duties by forwarding the user the appropriate page depending on the state of the model:
    /**
     * Handle an Http post.  The request is expected to
     * contain the parameter "UserID" from the form
     * that points to this servlet.
    **/
    protected void performPost(HttpServletRequest request,
                               HttpServletResponse response)
                               throws ServletException, IOException {
        updateModel(request);
        if (getModel(request).isValid()) {
            createSuccessModel(request, response);
            enterState("/testmvc/Success.jsp", request, response);
        } else {
            enterState("/testmvc/EnteringUserName.jsp", request, response);
        }
    }
The first thing performPost() does is to update the model based on request parameters.  This usually means pulling data out of forms and using setter methods on the model object.  Now, based on the state of the model, the servlet decides which state to transition into.  If the model is valid, the servlet creates the SuccessModel and forwards the request to the Success state using the enterState() method inherited from ControllerServlet.  Otherwise, the servlet forwards the request back to the EnteringUserName state to show the validation errors.  It is important that the argument to enterState() be an absolute path from the web root (it must start with a slash).  The actual path should be obvious, since when you obtained the jsp files from CVS they will be in a particular directory.

The rest of the servlet just handles the details of model creation and updating based on parameters gathered from the HTTP requests.

    /**
     * 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) {
        EnteringUserNameModel theModel = (EnteringUserNameModel)
                getModel(request);
        if (theModel == null) {
            theModel = new EnteringUserNameModel();
            addModelToSession(theModel, request);
        }

        //
        // Update the model object using parameters gathered
        // from the form.
        //
        theModel.setUserID(request.getParameter("UserID"));
    }

The updateModel() method checks to see if the session already contains a model object.  If not, it creates a new one and adds it to the session using the addModelToSession() method inherited from ControllerServlet.  The actual updating of the model occurs in the last line of the method--this is the most important part of the method, since it actually changes the model object!  The modification to the model is that the userID is updated based on whatever the end user typed into the form.  The parameter name "UserID" is the name you put into the jsp form variable when you were replacing # marks.
    /**
     * Creates the model used for the next state and adds it to
     * the session.  The reason we do this here instead of
     * in the usebean declaration in the jsp is that this
     * model takes a parameter in its constructor.
    **/
    private void createSuccessModel(HttpServletRequest request,
                                    HttpServletResponse response) {
        SuccessModel theModel = new SuccessModel(
                ((EnteringUserNameModel) getModel(request)).getUser());
        addModelToSession(theModel, request);
    }
The createSuccessModel() method just instantiates a new SuccessModel and puts it into the user's session, so it can be used later by Success.jsp.

After you've built, tested, and deployed all of your components and you've gotten a signoff from your site producer, you're done!  Hopefully these steps have allowed you to concentrate on and own the part that you do best--the back end logic--while allowing the web developer to do what he or she does best without a lot of back and forth.

Summary of Naming Conventions

Throughout the text, we've mentioned a number of naming conventions.  They are repeated here for convenience:
 
Item to be named Name to Use Example
Model <StateName>Model.java EnteringUserNameModel.java
View <StateName>.jsp EnteringUserName.jsp
Controller <StateName>Servlet.java EnteringUserNameServlet.jsp
JSP bean tag id <stateName>Model enteringUserNameModel
Model property accessors get<Property>, is<Property> getUserID()
Form variable names <Property> UserID
Validate property method validate<Property> validateUserID()

More Examples

The following are additional detailed examples with sample code.
MultiPage Results 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.
Examples with Radio Buttons, Popup
This example shows how to modify jsp code which includes radio buttons and select drop down menus.

FAQ

Q:  I have a confirmation page view that doesn't seem to require its own model, but relies on other models.  What do I do in this situation?
A:  Create an empty model for the state.  If you need to obtain data from previously created models in your jsp, just add an additional jsp usebean tag into the jsp:
<!-- usebean tag for previously defined model -->
<jsp:useBean id="enteringUserNameModel"
             class="com.startups.testmvc.EnteringUserNameModel"
             scope="session" />

<!-- usebean tag for model corresponding to current state -->
<jsp:useBean id="thankYouModel"
             class="com.startups.testmvc.ThankYouModel"
             scope="session"
             create="yes"/>

If you ever need to access a previously created model from within a ControllerServlet, use the getModel() method that takes a class object as a parameter:
// some code in ThankYouServlet
EnteringUserNameModel thePreviouslyCreatedModel = (EnteringUserNameModel)
        getModel(EnteringUserNameModel.class, request);
If you ever use this, you need to take special care that the session doesn't expire between calls to the servlets.  In order to handle this, see the next question.

Q:  Do I need to do anything special if I have a multipage form?
A:  The framework stores Model objects in the user's http session.  In a multipage form, there is no guarantee that the user will move between pages in a timely fashion, so it's possible that the user's session may expire between pages.  If this were to happen, Model objects created in earlier pages will not be available.  In order to handle this, you may have your controller servlet extend SessionValidControllerServlet instead of ControllerServletSessionValidControllerServlet checks that the session is valid before attempting the performPost() or performGet() methods. If the session is found to be invalid, which would happen if the session timed out, the SessionValidControllerServlet forwards the user to a session expired error page.

Q:  I am not using a form, can I still use the MVC method?
A:  Yes.  In the above examples you had a form with the post action set to the servlet (e.g. <form action="/servlet/com.startups.testmvc.EnteringUserNameServlet" method="POST">).  You can just as easily put this into an href (e.g. <a href="/servlet/com.startups.testmvc.EnteringUserNameServlet">).  In fact, it's preferred that you go between states through a controller servlet, rather than just moving from jsp to jsp.

Q:  Do I need to do anything special if I am going to read or write to persistent storage?
A:  Yes.  The Model class has a method defined on it called getSession(), which returns a TOPLink Session object (not to be confused with an HTTP session).  This method will only return a valid session if the model is used in conjunction with a ControllerDataServlet, which contains the logic to assign the proper TOPLink session to the Model.  So for states requiring either reads from persistent storage or writes to persistent storage, have your controller servlet extend ControllerDataServlet.  More detail on persistence can be found in another cookbook (not yet completed).

Q:  I have a bunch of business and persistence logic.  Where do I put it, in the Model or in the Servlet?
A:  Put it in the Model, or have the Model delegate to some other object.  The only logic that should be in the Servlet is deciding which state to go to next depending on the state of the model, and updating the model objects.  Updating the model objects typically involves pulling data out of forms and using setters on the model, but may also involve higher level operations.  For example, if you had some method which saves data to persistent storage, it might need to be invoked by the servlet prior to transitioning to a TransactionConfirmed state.  In this example, the model might have a saveData() method on it which is invoked by the servlet as part of the model update step.
 

3.  Design Discussion

Web Pages Are A Set Of Discrete States

A sequence of web pages can be thought of as a sequence of discrete states:

A single state is represented by Model-View-Controller components, using the following naming convention:  <state>.jsp, <state>Servlet, and <state>Model.

View (<state>.jsp)

The view contains the presentation of a particular state.  It is implemented as a jsp which has some dynamic content and contains a form.  The dynamic content is obtained from the model, usually via simple jsp evals (e.g. <%= model.getSomeAttribute() %>) or bean properties.  When the form is submitted, this indicates a possible state change.  The form is submitted to the controller component, which is a servlet named for the state.

Controller (<state>Servlet)

The servlet's responsibility is to update the model and to figure out which state to go to next.  It does this via very simple conditional logic and potentially some help from model objects.  It may need to instantiate new model objects or modify existing model objects corresponding to the next state.

Model (<state>Model)

The model represents the presentation-independent logic of the state, and is implemented as a Java object.  The model may actually be a facade object which may delegate much of its work to other persistent objects such as User and Address.  However, the view does not depend on any object other than the model, so from the point of view of the view the model contains all the logic.  Model objects may live in page scope, or may sometimes live in session scope, but are not persistent.

Rationale for Using MVC

We expect to have at least two distinctly different classes of developers working on the site simultaneously:  web designers and Java programmers.  We'd like to have these developers work as independently as possible.  For instance, it would be undesirable to require a Java programmer to recode and recompile servlets in order to make a color change to the look of a web page.  It would be equally undesirable to require the web designer to have to understand arcane jsp code in order to make such a change.

Goals:

MVC allows for a reasonable separation between logic and presentation, which helps web designers and Java programmers do what they do best.  Having one MVC triplet per web application state simplifies conditional logic and improves maintainability.

The main consequence of using the MVC system is that there will be more files than if we didn't use MVC.  For every state, there are at least three files that are involved.  However, each file should be much simpler and easier to understand than if these things were in fewer files, so it seems to be an acceptable tradeoff.

Known Problems

References

4 Version History

0.8, Aug 22, 2000.  Added FAQ section.
0.7, July 19, 2000.  Removed error page generation and combined it into single jsp view.
0.6, July 17, 2000.  Added Model object to package up and remove some ugly jsp error handling code and the explanation of it.  Added an example involving radio buttons and drop down menus.
0.5, July 7, 2000.  Added Known Problems section.  Added summary of naming conventions table.  Added link to another major example.
0.4, June 30, 2000.  Major code examples and fleshing out of cookbook.  This is the first version where the Java cookbook has been split off from the web developer cookbook.
0.3, June 20, 2000.  Added note for checking timed out sessions, fixed a declaration problem in one of the code samples.
0.2, June 19, 2000.  Rewrote lots, added lots.  This should have been the first release.  This release is not organized in cookbook form yet.
0.1, Some time ago, an in-progress version of this document was released in a very incomplete state.