struts-sandbox/struts2/apps/mailreader-bang/src/main/webapp/pages/tour.html [1:1109]: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - A Walking Tour of the Struts 2 MailReader Application

A Walking Tour of the Struts 2 MailReader Application

This article is meant to introduce a new user to Apache Struts 2 by "walking through" a simple, but functional, application. The article includes code snippets, but for the best result, you might want to install the MailReader application on your own development workstation and follow along. Of course, the full source code to the MailReader is included in the distribution.

The tour assumes the reader has a basic understanding of the Java language, JavaBeans, web applications, and JavaServer Pages. For background on these technologies, see the Key Technologies Primer.


Logging In


The premise of the MailReader is that it is the first iteration of a portal application. This version allows users to register and maintain a set of accounts with various mail servers. If completed, the application would let users read mail from their accounts.

The MailReader application demonstrates registering with an application, logging into an application, maintaining a master record, and maintaining child records. This article overviews the constructs needed to do these things, including the server pages, Java classes, and configuration elements.

For more about the MailReader, including alternate implementations and a set of formal Use Cases, please visit the Struts University MailReader site.


JAAS - Note that for compatibility and ease of deployment, the MailReader uses "application-based" authorization. However, use of the standard Java Authentication and Authorization Service (JAAS) is recommended for most applications. (See the Key Technologies Primer for more about authentication technologies.)


The tour starts with how the initial welcome page is displayed, and then steps through logging into the application and editing a subscription. Please note that this not a quick peek at a "Hello World" application. The tour is a rich trek into a realistic, best practices application. You may need to adjust your chair and get a fresh cup of coffee. Printed, the article is 29 pages long (US).

Welcome Page

A web application, like any other web site, can specify a list of welcome pages. When you open a web application without specifying a particular page, a default "welcome page" is served as the response.

web.xml

When a web application loads, the container reads and parses the "Web Application Deployment Descriptor", or "web.xml" file. The framework plugs into a web application via a servlet filter. Like any filter, the "struts2" filter is deployed via the "web.xml".


web.xml - The Web Application Deployment Descriptor
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>

  <display-name>Struts 2 MailReader</display-name>

  <filter>
    <filter-name>struts2</filter-name>
    <filter-class>
      org.apache.struts2.dispatcher.FilterDispatcher
    </filter-class>
   </filter>

  <filter-mapping>
    <filter-name>struts2</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <listener>
    <listener-class>
      org.springframework.web.context.ContextLoaderListener
    </listener-class>
  </listener>

  <!-- Application Listener for MailReader database -->
  <listener>
    <listener-class>
      mailreader2.ApplicationListener
    </listener-class>
  </listener>

  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
  </welcome-file-list>

  </web-app>

Among other things, the web.xml specifies the "Welcome File List" for an application. When a web address refers to a directory rather than an individual file, the container consults the Welcome File List for the name of a page to open by default.

However, most Struts applications do not refer to physical pages, but to "virtual resources" called actions. Actions specify code that we want to be run before a page or other resource renders the response. An accepted practice is to never link directly to server pages, but only to logical action mappings. By linking to actions, developers can often "rewire" an application without editing the server pages.


Best Practice:

"Link actions not pages."


The actions are listed in one or more XML configuration files, the default configuration file being named "struts.xml". When the application loads, the struts.xml, and any other files it includes, are parsed, and the framework creates a set of configuration objects. Among other things, the configuration maps a request for a certain page to a certain action mapping.

Sites can list zero or more "Welcome" pages in the web.xml. Unless you are using Java 1.5, actions cannot be specified as a Welcome page. So, in the case of a Welcome page, how do we follow the best practice of navigating through actions rather than pages?

One solution is to use a page to "bootstrap" one of our actions. We can register the usual "index.html" as the Welcome page and have it redirect to a "Welcome" action.


MailReader's index.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html><head>
  <META HTTP-EQUIV="Refresh" CONTENT="0;URL=Welcome.do">
  </head>
  <body>
    <p>Loading ...</p>
</body></html>

As an alternative, we could also have used a JSP page that issued the redirect with a Struts tag, but a plain HTML solution works as well.

Welcome.do

When the client requests "Welcome.do", the request is passed to the "struts2" FilterDispatcher (that we registered in the web.xml file). The FilterDispatcher retrieves the appropriate action mapping from the configuration. If we just wanted to forward to the Welcome page, we could use a simple configuration element.


A simple "forward thru" action element
<action name="Welcome">
  <result>/pages/Welcome.jsp</result>
</action>

If a client asks for the Welcome action ("Welcome.do), the "/page/Welcome.jsp" page would be returned in response. The client does not know, or need to know, that the physical resource is located at "/pages/Welcome.jsp". All the client knows is that it requested the resource "Welcome.do".

But if we peek at the configuration file for the MailReader, we find a slightly more complicated XML element for the Welcome action.


The Welcome action element
<action name="Welcome" class="mailreader2.Welcome">
    <result>/pages/Welcome.jsp</result>
    <interceptor-ref name="guest"/>
    </action>

Here, the Welcome Java class executes whenever someone asks for the Welcome action. As it completes, the Action class can select which "result" is displayed. The default result name is "success". Another available result, defined at a global scope, is "error".


Key concept:

The Action class doesn't need to know what result type is needed for "success" or "error". The Action can just return the logical name for a result, without knowing how the result is implemented.


The net effect is that all of the result details, including the paths to server pages, all can be declared once in the configuration. Tightly coupled implementation details are not scattered all over the application.


Key concept:

The Struts configuration lets us separate concerns and "say it once". The configuration helps us "normalize" an application, in much the same way we normalize a database schema.


OK ... but why would a Welcome Action want to choose between "success" and "error"?

Welcome Action

The MailReader application retains a list of users along with their email accounts. The application stores this information in a database. If the application can't connect to the database, the application can't do its job. So before displaying the Welcome page, the Welcome class checks to see if the database is available.

The MailReader is also an internationalized application. So, the Welcome Action class checks to see if the message resources are available too. If both resources are available, the class passes back the "success" token. Otherwise, the class passes back the "error" token, so that the appropriate messages can be displayed.


The Welcome Action class
package mailreader2;
public class Welcome extends MailreaderSupport {

  public String execute() {

    // Confirm message resources loaded
    String message = getText(Constants.ERROR_DATABASE_MISSING);
    if (Constants.ERROR_DATABASE_MISSING.equals(message)) {
      addActionError(Constants.ERROR_MESSAGES_NOT_LOADED);
    }

    // Confirm database loaded
    if (null==getDatabase()) {
      addActionError(Constants.ERROR_DATABASE_NOT_LOADED);
    }

    if (hasErrors()) {
      return ERROR;
    }
    else {
      return SUCCESS;
    }
  }
}

Several common result names are predefined, including ERROR, SUCCESS, LOGIN, NONE, and INPUT, so that these tokens can be used consistently across Struts 2 applications.

Global Results

As mentioned, "error" is defined in a global scope. Other actions may have trouble connecting to the database later, or other unexpected errors may occur. The MailReader defines the "error" result as a Global Result, so that any action can use it.


MailReader's global-result element
 <global-results>
  <result name="error">/pages/Error.jsp</result>
  <result name="invalid.token">/pages/Error.jsp</result>
  <result name="login" type="redirect-action">Logon!input</result>
</global-results>

Of course, if an individual action mapping defines its own "error" result type, the local result would be used instead.

ApplicationListener.java

The database is exposed as an object stored in application scope. The database object is based on an interface. Different implementations of the database could be loaded without changing the rest of the application. But how is the database object loaded in the first place?

The database is created by a custom Listener that we configured in the "web.xml".


mailreader2.ApplicationListener
 <listener>
  <listener-class>
    mailreader2.ApplicationListener
  </listener-class>
</listener>

By default, our ApplicationListener loads a MemoryDatabase implementation of the UserDatabase. MemoryDatabase stores the database content as a XML document, which is parsed and loaded as a set of nested hashtables. The outer table is the list of user objects, each of which has its own inner hashtable of subscriptions. When you register, a user object is stored in this hashtable. When you login, the user object is stored within the session context.

The database comes seeded with a sample user. If you check the "database.xml" file under "/src/main", you'll see the sample user described in XML.


The "seed" user element from the MailReader database.xml
<user username="user" fromAddress="John.User@somewhere.com"
  fullName="John Q. User" password="pass">
    <subscription host="mail.hotmail.com" autoConnect="false"
      password="bar" type="pop3" username="user1234">
    </subscription>
    <subscription host="mail.yahoo.com" autoConnect="false" password="foo"
      type="imap" username="jquser">
    </subscription>
</user>

The "seed" user element creates a registration record for "John Q. User", with the subscription detail for his hotmail and yahoo accounts.

Message Resources

As mentioned, MailReader is an internationalized application. The message resources for the application are loaded through a reference in the "struts.properties" file. Like the database contents, the "struts.properties" file is kept under "/src/main/" in the source tree.


struts.properties
struts.custom.i18n.resources = resources
struts.action.extension = do

When we specify "resources" in the properties file, we are telling the framework to scan the classpath for a Resource Bundle named "resources.properties". The bundle might be embedded in a JAR, or found in the "WEB-INF/classes" folder, or anywhere else on the runtime classpath. In the MailReader, we keep the original bundle in the source tree under "src/main/". When the application is built, the properties files are copied to "WEB-INF/classes", so that they are on the Java classpath.


Message Resource entries used by the Welcome page
index.heading=MailReader Application Options
index.logon=Log on to the MailReader Application
index.registration=Register with the MailReader Application
index.title=MailReader Demonstration Application
index.tour=A Walking Tour of the MailReader Demonstration Application

If you change a message in the resource, and then rebuild and reload the application, the change will appear throughout the application. If you provide message resources for additional locales, you can localize your application. The MailReader provides resources for English, Russian, and Japanese.

Welcome Page

After confirming that the necessary resources exist, the Welcome action forwards to the Welcome page.


Welcome.jsp
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="http://struts.apache.org/tags" %>
  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
    <head>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
      <title><s:text name="index.title"/></title>
      <link href="<s:url value="/css/mailreader.css"/>" rel="stylesheet"
      type="text/css"/>
    </head>

    <body>
      <h3><s:text name="index.heading"/></h3>

      <ul>
        <li><a href="<s:url action="Registration!input"/>"><s:text
          name="index.registration"/></a></li>
        <li><a href="<s:url action="Logon!input"/>"><s:text
          name="index.logon"/></a></li>
      </ul>

      <h3>Language Options</h3>
      <ul>
        <li><a href="<s:url action="Welcome?request_locale=en"/>">English</a></li>
        <li><a href="<s:url action="Welcome?request_locale=ja"/>">Japanese</a></li>
        <li><a href="<s:url action="Welcome?request_locale=ru"/>">Russian</a></li>
      </ul>

    <hr />

    <p><s:i18n name="alternate">
    <img src="<s:text name="struts.logo.path"/>"
      alt="<s:text name="struts.logo.alt"/>"/>
    </s:i18n></p>

    <p><a href="<s:url action="Tour" />"><s:text name="index.tour"/></a></p>

  </body>
</html>

At the top of the Welcome page, there are several directives that load the Struts 2 tag libraries. These are just the usual red tape that goes with any JSP file. The rest of the page utilizes three Struts JSP tags: "text", "url", and "i18n".

(We use the tag prefix "s:" in the Struts 2 MailReader application, but you can use whatever prefix you like in your applications.)

The text tag inserts a message from an application's default resource bundle. If the framework's locale setting is changed for a user, the text tag will render messages from the new locale's resource bundle instead.

The url tag can render a reference to an action or any other web resource, applying "URL encoding" to the hyperlinks as needed. Java's URL encoding feature lets your application maintain client state without requiring cookies.


Tip:

Cookies - If you turn cookies off in your browser, and then reload your browser and this page, you will see the links with the Java session id information attached. (If you are using Internet Explorer and try this, be sure you reset cookies for the appropriate security zone, and that you disallow "per-session" cookies.)


The i18n tag provides access to multiple resource bundles. The MailReader application uses a second set of message resources for non-text elements. When these are needed, we use the "i18n" tag to specify a different bundle.

The alternate bundle is stored next to the default bundle, so that it ends up under "classes", which is on the application's class path.

In the span of a single request for the Welcome page, the framework has done quite a bit already:

When rendered, the Welcome page lists two menu options: one to register with the application and one to log on (if you have already registered). Let's follow the Logon link first.

Logon

If you choose the Logon link, and all goes well, the Logon action forwards control to the Logon page.

Logon Page

The Logon page displays a form that accepts a username and password. You can use the default username and password to logon (user and pass), if you like. Try omitting or misspelling the username and password in various combinations to see how the application reacts. Note that both the username and password are case sensitive.


Login.jsp
<%@ page contentType="text/html; charset=UTF-8" %>
  <%@ taglib prefix="s" uri="http://struts.apache.org/tags"  %>
  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title><s:text name="logon.title"/></title>
      <link href="<s:url value="/css/mailreader.css"/>" rel="stylesheet"
        type="text/css"/>
  </head>
  <body onLoad="self.focus();document.Logon.username.focus()">
    <s:actionerror/>
    <s:form method="POST" validate="true">
      <s:textfield label="%{getText('username')}" name="username"/>
      <s:password label="%{getText('password')}" name="password"/>
      <s:submit value="%{getText('button.save')}"/>
      <s:reset value="%{getText('button.reset')}"/>
      <s:submit action="Logon!cancel" onclick="form.onsubmit=null"
        value="%{getText('button.cancel')}"/>
    </s:form>
    <jsp:include page="Footer.jsp"/>
  </body>
</html>

We already saw some of the tags used by the Logon page on the Welcome page. Let's focus on the new tags.

The first new tag on the Logon page is actionerrors. Most of the possible validation errors are related to a single field. If you don't enter a username, the framework can place an error message near the tag prompting you to enter a username. But some messages are not related to a single field. For example, the database might be down. If the action returns an "Action Error", as opposed to a "Field Error", the messages are rendered in place of the "actionerror" tag. The text for the validation errors, whether they are Action Errors or Field Errors, can be specified in the resource bundle, making the messages easy to manage and localize.

The second new tag is form. This tag renders a HTML form tag. By default, the form will submit back to whatever action invoked the page. The "validate=true" setting enables client-side validation, so that the form can be validated with JavaScript before being sent back to the server. The framework will still validate the form again, just to be sure, but the client-side validation can save a few round-trips to the server. You can use the method attribute to designate "GET" or "POST", just like the HTML form tag.

Within the form tag, we see four more new tags: "textfield", "password", "submit", and "reset". We also see a second usage of "submit" that utilizes an "action" attribute.

When we place a control on a form, we usually need to code a set of HTML tags to do everything we want to do. Most often, we do not just want a plain "input type=text" tag. We want the input field to have a label too, and maybe even a tooltip. And, of course, a place to print a message should invalid data be entered.

The UI Tags support templates and themes so that a set of HTML tags can be rendered from a single UI Tag. For example, the single tag


    <s:textfield label="%{getText('username')}" name="username"/>

generates a wad of HTML markup.


<tr>
  <td class="tdLabel">
    <label for="Logon_username" class="label">Username:</label>
  </td>
  <td>
    <input type="text" name="username" value="" id="Logon_username"/>
  </td>
</tr>

If for some reason you don't like the markup generated by a UI Tag, it's each to change. Each tag is driven by a template that can be updated on a tag-by-tag basis. For example, here is the default template that generates the markup for the ActionErrors tag:


<#if (actionErrors?exists && actionErrors?size > 0)>
  <ul>
    <#list actionErrors as error>
      <li><span class="errorMessage">${error}</span></li>
    </#list>
  </ul>
</#if>

If you wanted ActionErrors displayed in a table instead of a list, you could edit a copy of this file, save it as a file named "actionerror.ftl", and place this one file somewhere on your classpath.


<#if (actionErrors?exists && actionErrors?size > 0)>
  <table>
    <#list actionErrors as error>
      <tr><td><span class="errorMessage">${error}</span></td></tr>
    </#list>
  </table>
</#if>

Under the covers, the framework uses Freemarker for its standard templating language. FreeMarker is similar to Velocity, but it offers better error reporting and some additional features. If you prefer, Velocity and JSP templates can also be used to create your own UI Tags.

The password tag renders a "input type=password" tag, along with the usual template/theme markup. By default, the password tag will not retain input if the submit fails. If the username is wrong, the client will have to enter the password again too. (If you did want to retain the password when validation fails, you can set the tag's "showPassword" property to true.)

Unsurprisingly, the submit and reset tags render buttons of the corresponding types.

The second submit button is more interesting.

  <s:submit action="Logon!cancel" onclick="form.onsubmit=null"
    value="%{getText('button.cancel')}"/>

Here we are creating the Cancel button for the form. The button's attribute action="Logon!cancel" tells the framework to submit to the Logon's "cancel" method instead of the usual "execute" method. The onclick="form.onsubmit=null" script defeats client-side validation. On the server side, "cancel" is on a special list of methods that bypass validation, so the request will go directly to the Action's cancel method. (Other special aliases on the bypass list include "input" and "back".)


Tip:

The UI tags have options and capabilities beyond what we have shown here. For more see, the UI Tag documentation.


OK, but how do the tags know that both of these fields are required? How do they know what message to display when the fields are empty?

For the answers, we need to look at another flavor of configuration file: the "validation" file.

Logon-validation.xml

While it is not hard to code data-entry validation into an Action class, the framework provides an even easier way to validate input.

The validation framework is configured through another XML document, the Logon-validation.xml.


Validation file for Logon Action
<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN"
  "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
<validators>
  <field name="username">
    <field-validator type="requiredstring">
    <message key="error.username.required"/>
  </field-validator>
  </field>
  <field name="password">
    <field-validator type="requiredstring">
    <message key="error.password.required"/>
    </field-validator>
  </field>
</validators>

You may note that the DTD refers to "XWork". Open Symphony XWork is a generic command-pattern framework that can be used outside of a web environment. In practice, Struts 2 is a web-based extension of the XWork framework.

The field elements correspond to the ActionForm properties. The username and password field elements say that each field depends on the "requiredstring" validator. If the username is blank or absent, validation will fail and an error message is generated. The messages would be based on the "error.username.required" or "error.password.required" message templates, from the resource bundle.

Logon Action

If validation passes, the framework invokes the "execute" method of the Logon Action. The actual Logon Action is brief, since most of the functionality derives from the base class, MailreaderSupport.


Logon.java
package mailreader2;
import org.apache.struts.apps.mailreader.dao.User;
public final class Logon extends MailreaderSupport {
public String execute() throws ExpiredPasswordException {
  User user = findUser(getUsername(), getPassword());
  if (user != null) {
    setUser(user);
  }
  if (hasErrors()) {
    return INPUT;
  }
    return SUCCESS;
  }
}

Logon lays out what we do to authenticate a user. We try to find the user using the credentials provided. If the user is found, we cache a reference. If the user is not found, we return "input" so the client can try again. Otherwise, we return "success", so that the client can access the rest of the application.

MailreaderSupport.java

Let's look at the relevant properties and methods from MailreaderSupport and another base class, ActionSupport, namely "getUsername", "getPassword", "findUser", "setUser", and "hasErrors".

The framework lets you define JavaBean properties directly on the Action. Any JavaBean property can be used, including rich objects. When a request comes in, any public properties on the Action class are matched with the request parameters. When the names match, the request parameter value is set to the JavaBean property. The framework will make its best effort to convert the data, and, if necessary, it will report any conversion errors.

The Username and Password properties are nothing fancy, just standard JavaBean properties.


MailreaderSupport.getUsername() and getPassword()
private String username = null;
public String getUsername() {
  return this.username;
}
public void setUsername(String username) {
  this.username = username;
}

private String password = null;
public String getPassword() {
  return this.password;
}
public void setPassword(String password) {
  this.password = password;
}

We use these properties to capture the client's credentials, and pass them to the more interesting findUser method.


MailreaderSupport.findUser
public User findUser(String username, String password)
  throws ExpiredPasswordException {
  User user = getDatabase().findUser(username);
  if ((user != null) && !user.getPassword().equals(password)) {
    user = null;
  }
  if (user == null) {
    this.addFieldError("password", getText("error.password.mismatch"));
  }
  return user;
}

The "findUser" method dips into the MailReader Data Access Object layer, which is represented by the Database property. The code for the DAO layer is maintained as a separate component. The MailReader application imports the DAO JAR, but it is not responsible for maintaining any of the DAO source. Keeping the data access layer at "arms-length" is a very good habit. It encourages a style of development where the data access layer can be tested and developed independently of a specific end-user application. In fact, there are several renditions of the MailReader application, all which share the same MailReader DAO JAR!


Best Practice:

"Strongly separate data access and business logic from the rest of the application."


When "findUser" returns, the Logon Action looks to see if a valid (non-null) User object is returned. A valid User is passed to the User property. Although it is still a JavaBean property, the User property is not implemented in quite the same way as Username and Password.


MailreaderSupport.setUser
public User getUser() {
  return (User) getSession().get(Constants.USER_KEY);
}
public void setUser(User user) {
  getSession().put(Constants.USER_KEY, user);
}

Instead of using a field to store the property value, "setUser" passes it to a Session property. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - struts-sandbox/struts2/apps/mailreader-wildone/src/main/webapp/pages/tour.jsp [1:1109]: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - A Walking Tour of the Struts 2 MailReader Application

A Walking Tour of the Struts 2 MailReader Application

This article is meant to introduce a new user to Apache Struts 2 by "walking through" a simple, but functional, application. The article includes code snippets, but for the best result, you might want to install the MailReader application on your own development workstation and follow along. Of course, the full source code to the MailReader is included in the distribution.

The tour assumes the reader has a basic understanding of the Java language, JavaBeans, web applications, and JavaServer Pages. For background on these technologies, see the Key Technologies Primer.


Logging In


The premise of the MailReader is that it is the first iteration of a portal application. This version allows users to register and maintain a set of accounts with various mail servers. If completed, the application would let users read mail from their accounts.

The MailReader application demonstrates registering with an application, logging into an application, maintaining a master record, and maintaining child records. This article overviews the constructs needed to do these things, including the server pages, Java classes, and configuration elements.

For more about the MailReader, including alternate implementations and a set of formal Use Cases, please visit the Struts University MailReader site.


JAAS - Note that for compatibility and ease of deployment, the MailReader uses "application-based" authorization. However, use of the standard Java Authentication and Authorization Service (JAAS) is recommended for most applications. (See the Key Technologies Primer for more about authentication technologies.)


The tour starts with how the initial welcome page is displayed, and then steps through logging into the application and editing a subscription. Please note that this not a quick peek at a "Hello World" application. The tour is a rich trek into a realistic, best practices application. You may need to adjust your chair and get a fresh cup of coffee. Printed, the article is 29 pages long (US).

Welcome Page

A web application, like any other web site, can specify a list of welcome pages. When you open a web application without specifying a particular page, a default "welcome page" is served as the response.

web.xml

When a web application loads, the container reads and parses the "Web Application Deployment Descriptor", or "web.xml" file. The framework plugs into a web application via a servlet filter. Like any filter, the "struts2" filter is deployed via the "web.xml".


web.xml - The Web Application Deployment Descriptor
<?xml version="1.0" encoding="ISO-8859-1"?>
    <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">
    <web-app>

    <display-name>Struts 2 MailReader</display-name>

    <filter>
        <filter-name>struts2</filter-name>
        <filter-class>
        org.apache.struts2.dispatcher.FilterDispatcher
        </filter-class>
        </filter>

    <filter-mapping>
    <filter-name>struts2</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>

    <listener>
    <listener-class>
    org.springframework.web.context.ContextLoaderListener
    </listener-class>
    </listener>

    <!-- Application Listener for MailReader database -->
    <listener>
    <listener-class>
    mailreader2.ApplicationListener
    </listener-class>
    </listener>

    <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    </welcome-file-list>

    </web-app>

Among other things, the web.xml specifies the "Welcome File List" for an application. When a web address refers to a directory rather than an individual file, the container consults the Welcome File List for the name of a page to open by default.

However, most Struts applications do not refer to physical pages, but to "virtual resources" called actions. Actions specify code that we want to be run before a page or other resource renders the response. An accepted practice is to never link directly to server pages, but only to logical action mappings. By linking to actions, developers can often "rewire" an application without editing the server pages.


Best Practice:

"Link actions not pages."


The actions are listed in one or more XML configuration files, the default configuration file being named "struts.xml". When the application loads, the struts.xml, and any other files it includes, are parsed, and the framework creates a set of configuration objects. Among other things, the configuration maps a request for a certain page to a certain action mapping.

Sites can list zero or more "Welcome" pages in the web.xml. Unless you are using Java 1.5, actions cannot be specified as a Welcome page. So, in the case of a Welcome page, how do we follow the best practice of navigating through actions rather than pages?

One solution is to use a page to "bootstrap" one of our actions. We can register the usual "index.html" as the Welcome page and have it redirect to a "Welcome" action.


MailReader's index.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
    <html><head>
    <META HTTP-EQUIV="Refresh" CONTENT="0;URL=Welcome.do">
    </head>
    <body>
    <p>Loading ...</p>
    </body></html>

As an alternative, we could also have used a JSP page that issued the redirect with a Struts tag, but a plain HTML solution works as well.

Welcome.do

When the client requests "Welcome.do", the request is passed to the "struts2" FilterDispatcher (that we registered in the web.xml file). The FilterDispatcher retrieves the appropriate action mapping from the configuration. If we just wanted to forward to the Welcome page, we could use a simple configuration element.


A simple "forward thru" action element
<action name="Welcome">
    <result>/pages/Welcome.jsp</result>
    </action>

If a client asks for the Welcome action ("Welcome.do), the "/page/Welcome.jsp" page would be returned in response. The client does not know, or need to know, that the physical resource is located at "/pages/Welcome.jsp". All the client knows is that it requested the resource "Welcome.do".

But if we peek at the configuration file for the MailReader, we find a slightly more complicated XML element for the Welcome action.


The Welcome action element
<action name="Welcome" class="mailreader2.Welcome">
    <result>/pages/Welcome.jsp</result>
    <interceptor-ref name="guest"/>
    </action>

Here, the Welcome Java class executes whenever someone asks for the Welcome action. As it completes, the Action class can select which "result" is displayed. The default result name is "success". Another available result, defined at a global scope, is "error".


Key concept:

The Action class doesn't need to know what result type is needed for "success" or "error". The Action can just return the logical name for a result, without knowing how the result is implemented.


The net effect is that all of the result details, including the paths to server pages, all can be declared once in the configuration. Tightly coupled implementation details are not scattered all over the application.


Key concept:

The Struts configuration lets us separate concerns and "say it once". The configuration helps us "normalize" an application, in much the same way we normalize a database schema.


OK ... but why would a Welcome Action want to choose between "success" and "error"?

Welcome Action

The MailReader application retains a list of users along with their email accounts. The application stores this information in a database. If the application can't connect to the database, the application can't do its job. So before displaying the Welcome page, the Welcome class checks to see if the database is available.

The MailReader is also an internationalized application. So, the Welcome Action class checks to see if the message resources are available too. If both resources are available, the class passes back the "success" token. Otherwise, the class passes back the "error" token, so that the appropriate messages can be displayed.


The Welcome Action class
package mailreader2;
    public class Welcome extends MailreaderSupport {

    public String execute() {

    // Confirm message resources loaded
    String message = getText(Constants.ERROR_DATABASE_MISSING);
    if (Constants.ERROR_DATABASE_MISSING.equals(message)) {
    addActionError(Constants.ERROR_MESSAGES_NOT_LOADED);
    }

    // Confirm database loaded
    if (null==getDatabase()) {
    addActionError(Constants.ERROR_DATABASE_NOT_LOADED);
    }

    if (hasErrors()) {
    return ERROR;
    }
    else {
    return SUCCESS;
    }
    }
    }

Several common result names are predefined, including ERROR, SUCCESS, LOGIN, NONE, and INPUT, so that these tokens can be used consistently across Struts 2 applications.

Global Results

As mentioned, "error" is defined in a global scope. Other actions may have trouble connecting to the database later, or other unexpected errors may occur. The MailReader defines the "error" result as a Global Result, so that any action can use it.


MailReader's global-result element
 <global-results>
    <result name="error">/pages/Error.jsp</result>
    <result name="invalid.token">/pages/Error.jsp</result>
    <result name="login" type="redirect-action">Logon!input</result>
    </global-results>

Of course, if an individual action mapping defines its own "error" result type, the local result would be used instead.

ApplicationListener.java

The database is exposed as an object stored in application scope. The database object is based on an interface. Different implementations of the database could be loaded without changing the rest of the application. But how is the database object loaded in the first place?

The database is created by a custom Listener that we configured in the "web.xml".


mailreader2.ApplicationListener
 <listener>
    <listener-class>
    mailreader2.ApplicationListener
    </listener-class>
    </listener>

By default, our ApplicationListener loads a MemoryDatabase implementation of the UserDatabase. MemoryDatabase stores the database content as a XML document, which is parsed and loaded as a set of nested hashtables. The outer table is the list of user objects, each of which has its own inner hashtable of subscriptions. When you register, a user object is stored in this hashtable. When you login, the user object is stored within the session context.

The database comes seeded with a sample user. If you check the "database.xml" file under "/src/main", you'll see the sample user described in XML.


The "seed" user element from the MailReader database.xml
<user username="user" fromAddress="John.User@somewhere.com"
    fullName="John Q. User" password="pass">
    <subscription host="mail.hotmail.com" autoConnect="false"
    password="bar" type="pop3" username="user1234">
    </subscription>
    <subscription host="mail.yahoo.com" autoConnect="false" password="foo"
    type="imap" username="jquser">
    </subscription>
    </user>

The "seed" user element creates a registration record for "John Q. User", with the subscription detail for his hotmail and yahoo accounts.

Message Resources

As mentioned, MailReader is an internationalized application. The message resources for the application are loaded through a reference in the "struts.properties" file. Like the database contents, the "struts.properties" file is kept under "/src/main/" in the source tree.


struts.properties
struts.custom.i18n.resources = resources
    struts.action.extension = do

When we specify "resources" in the properties file, we are telling the framework to scan the classpath for a Resource Bundle named "resources.properties". The bundle might be embedded in a JAR, or found in the "WEB-INF/classes" folder, or anywhere else on the runtime classpath. In the MailReader, we keep the original bundle in the source tree under "src/main/". When the application is built, the properties files are copied to "WEB-INF/classes", so that they are on the Java classpath.


Message Resource entries used by the Welcome page
index.heading=MailReader Application Options
    index.logon=Log on to the MailReader Application
    index.registration=Register with the MailReader Application
    index.title=MailReader Demonstration Application
    index.tour=A Walking Tour of the MailReader Demonstration Application

If you change a message in the resource, and then rebuild and reload the application, the change will appear throughout the application. If you provide message resources for additional locales, you can localize your application. The MailReader provides resources for English, Russian, and Japanese.

Welcome Page

After confirming that the necessary resources exist, the Welcome action forwards to the Welcome page.


Welcome.jsp
<%@ page contentType="text/html; charset=UTF-8" %>
    <%@ taglib prefix="s" uri="http://struts.apache.org/tags" %>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title><s:text name="index.title"/></title>
    <link href="<s:url value="/css/mailreader.css"/>" rel="stylesheet"
    type="text/css"/>
    </head>

    <body>
    <h3><s:text name="index.heading"/></h3>

    <ul>
    <li><a href="<s:url action="Registration!input"/>"><s:text
    name="index.registration"/></a></li>
    <li><a href="<s:url action="Logon!input"/>"><s:text
    name="index.logon"/></a></li>
    </ul>

    <h3>Language Options</h3>
    <ul>
    <li><a href="<s:url action="Welcome?request_locale=en"/>">English</a></li>
    <li><a href="<s:url action="Welcome?request_locale=ja"/>">Japanese</a></li>
    <li><a href="<s:url action="Welcome?request_locale=ru"/>">Russian</a></li>
    </ul>

    <hr />

    <p><s:i18n name="alternate">
    <img src="<s:text name="struts.logo.path"/>"
    alt="<s:text name="struts.logo.alt"/>"/>
    </s:i18n></p>

    <p><a href="<s:url action="Tour" />"><s:text name="index.tour"/></a></p>

    </body>
    </html>

At the top of the Welcome page, there are several directives that load the Struts 2 tag libraries. These are just the usual red tape that goes with any JSP file. The rest of the page utilizes three Struts JSP tags: "text", "url", and "i18n".

(We use the tag prefix "s:" in the Struts 2 MailReader application, but you can use whatever prefix you like in your applications.)

The text tag inserts a message from an application's default resource bundle. If the framework's locale setting is changed for a user, the text tag will render messages from the new locale's resource bundle instead.

The url tag can render a reference to an action or any other web resource, applying "URL encoding" to the hyperlinks as needed. Java's URL encoding feature lets your application maintain client state without requiring cookies.


Tip:

Cookies - If you turn cookies off in your browser, and then reload your browser and this page, you will see the links with the Java session id information attached. (If you are using Internet Explorer and try this, be sure you reset cookies for the appropriate security zone, and that you disallow "per-session" cookies.)


The i18n tag provides access to multiple resource bundles. The MailReader application uses a second set of message resources for non-text elements. When these are needed, we use the "i18n" tag to specify a different bundle.

The alternate bundle is stored next to the default bundle, so that it ends up under "classes", which is on the application's class path.

In the span of a single request for the Welcome page, the framework has done quite a bit already:

When rendered, the Welcome page lists two menu options: one to register with the application and one to log on (if you have already registered). Let's follow the Logon link first.

Logon

If you choose the Logon link, and all goes well, the Logon action forwards control to the Logon page.

Logon Page

The Logon page displays a form that accepts a username and password. You can use the default username and password to logon (user and pass), if you like. Try omitting or misspelling the username and password in various combinations to see how the application reacts. Note that both the username and password are case sensitive.


Login.jsp
<%@ page contentType="text/html; charset=UTF-8" %>
    <%@ taglib prefix="s" uri="http://struts.apache.org/tags" %>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
    <head>
    <title><s:text name="logon.title"/></title>
    <link href="<s:url value="/css/mailreader.css"/>" rel="stylesheet"
    type="text/css"/>
    </head>
    <body onLoad="self.focus();document.Logon.username.focus()">
    <s:actionerror/>
    <s:form method="POST" validate="true">
    <s:textfield label="%{getText('username')}" name="username"/>
    <s:password label="%{getText('password')}" name="password"/>
    <s:submit value="%{getText('button.save')}"/>
    <s:reset value="%{getText('button.reset')}"/>
    <s:submit action="Logon!cancel" onclick="form.onsubmit=null"
    value="%{getText('button.cancel')}"/>
    </s:form>
    <jsp:include page="Footer.jsp"/>
    </body>
    </html>

We already saw some of the tags used by the Logon page on the Welcome page. Let's focus on the new tags.

The first new tag on the Logon page is actionerrors. Most of the possible validation errors are related to a single field. If you don't enter a username, the framework can place an error message near the tag prompting you to enter a username. But some messages are not related to a single field. For example, the database might be down. If the action returns an "Action Error", as opposed to a "Field Error", the messages are rendered in place of the "actionerror" tag. The text for the validation errors, whether they are Action Errors or Field Errors, can be specified in the resource bundle, making the messages easy to manage and localize.

The second new tag is form. This tag renders a HTML form tag. By default, the form will submit back to whatever action invoked the page. The "validate=true" setting enables client-side validation, so that the form can be validated with JavaScript before being sent back to the server. The framework will still validate the form again, just to be sure, but the client-side validation can save a few round-trips to the server. You can use the method attribute to designate "GET" or "POST", just like the HTML form tag.

Within the form tag, we see four more new tags: "textfield", "password", "submit", and "reset". We also see a second usage of "submit" that utilizes an "action" attribute.

When we place a control on a form, we usually need to code a set of HTML tags to do everything we want to do. Most often, we do not just want a plain "input type=text" tag. We want the input field to have a label too, and maybe even a tooltip. And, of course, a place to print a message should invalid data be entered.

The UI Tags support templates and themes so that a set of HTML tags can be rendered from a single UI Tag. For example, the single tag


    <s:textfield label="%{getText('username')}" name="username"/>

generates a wad of HTML markup.


<tr>
    <td class="tdLabel">
    <label for="Logon_username" class="label">Username:</label>
    </td>
    <td>
    <input type="text" name="username" value="" id="Logon_username"/>
    </td>
    </tr>

If for some reason you don't like the markup generated by a UI Tag, it's each to change. Each tag is driven by a template that can be updated on a tag-by-tag basis. For example, here is the default template that generates the markup for the ActionErrors tag:


<#if (actionErrors?exists && actionErrors?size > 0)>
    <ul>
    <#list actionErrors as error>
    <li><span class="errorMessage">${error}</span></li>
    </#list>
    </ul>
    </#if>

If you wanted ActionErrors displayed in a table instead of a list, you could edit a copy of this file, save it as a file named "actionerror.ftl", and place this one file somewhere on your classpath.


<#if (actionErrors?exists && actionErrors?size > 0)>
    <table>
    <#list actionErrors as error>
    <tr><td><span class="errorMessage">${error}</span></td></tr>
    </#list>
    </table>
    </#if>

Under the covers, the framework uses Freemarker for its standard templating language. FreeMarker is similar to Velocity, but it offers better error reporting and some additional features. If you prefer, Velocity and JSP templates can also be used to create your own UI Tags.

The password tag renders a "input type=password" tag, along with the usual template/theme markup. By default, the password tag will not retain input if the submit fails. If the username is wrong, the client will have to enter the password again too. (If you did want to retain the password when validation fails, you can set the tag's "showPassword" property to true.)

Unsurprisingly, the submit and reset tags render buttons of the corresponding types.

The second submit button is more interesting.

  <s:submit action="Logon!cancel" onclick="form.onsubmit=null"
    value="%{getText('button.cancel')}"/>

Here we are creating the Cancel button for the form. The button's attribute action="Logon!cancel" tells the framework to submit to the Logon's "cancel" method instead of the usual "execute" method. The onclick="form.onsubmit=null" script defeats client-side validation. On the server side, "cancel" is on a special list of methods that bypass validation, so the request will go directly to the Action's cancel method. (Other special aliases on the bypass list include "input" and "back".)


Tip:

The UI tags have options and capabilities beyond what we have shown here. For more see, the UI Tag documentation.


OK, but how do the tags know that both of these fields are required? How do they know what message to display when the fields are empty?

For the answers, we need to look at another flavor of configuration file: the "validation" file.

Logon-validation.xml

While it is not hard to code data-entry validation into an Action class, the framework provides an even easier way to validate input.

The validation framework is configured through another XML document, the Logon-validation.xml.


Validation file for Logon Action
<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN"
    "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
    <validators>
    <field name="username">
    <field-validator type="requiredstring">
    <message key="error.username.required"/>
    </field-validator>
    </field>
    <field name="password">
    <field-validator type="requiredstring">
    <message key="error.password.required"/>
    </field-validator>
    </field>
    </validators>

You may note that the DTD refers to "XWork". Open Symphony XWork is a generic command-pattern framework that can be used outside of a web environment. In practice, Struts 2 is a web-based extension of the XWork framework.

The field elements correspond to the ActionForm properties. The username and password field elements say that each field depends on the "requiredstring" validator. If the username is blank or absent, validation will fail and an error message is generated. The messages would be based on the "error.username.required" or "error.password.required" message templates, from the resource bundle.

Logon Action

If validation passes, the framework invokes the "execute" method of the Logon Action. The actual Logon Action is brief, since most of the functionality derives from the base class, MailreaderSupport.


Logon.java
package mailreader2;
    import org.apache.struts.apps.mailreader.dao.User;
    public final class Logon extends MailreaderSupport {
    public String execute() throws ExpiredPasswordException {
    User user = findUser(getUsername(), getPassword());
    if (user != null) {
    setUser(user);
    }
    if (hasErrors()) {
    return INPUT;
    }
    return SUCCESS;
    }
    }

Logon lays out what we do to authenticate a user. We try to find the user using the credentials provided. If the user is found, we cache a reference. If the user is not found, we return "input" so the client can try again. Otherwise, we return "success", so that the client can access the rest of the application.

MailreaderSupport.java

Let's look at the relevant properties and methods from MailreaderSupport and another base class, ActionSupport, namely "getUsername", "getPassword", "findUser", "setUser", and "hasErrors".

The framework lets you define JavaBean properties directly on the Action. Any JavaBean property can be used, including rich objects. When a request comes in, any public properties on the Action class are matched with the request parameters. When the names match, the request parameter value is set to the JavaBean property. The framework will make its best effort to convert the data, and, if necessary, it will report any conversion errors.

The Username and Password properties are nothing fancy, just standard JavaBean properties.


MailreaderSupport.getUsername() and getPassword()
private String username = null;
    public String getUsername() {
    return this.username;
    }
    public void setUsername(String username) {
    this.username = username;
    }

    private String password = null;
    public String getPassword() {
    return this.password;
    }
    public void setPassword(String password) {
    this.password = password;
    }

We use these properties to capture the client's credentials, and pass them to the more interesting findUser method.


MailreaderSupport.findUser
public User findUser(String username, String password)
    throws ExpiredPasswordException {
    User user = getDatabase().findUser(username);
    if ((user != null) && !user.getPassword().equals(password)) {
    user = null;
    }
    if (user == null) {
    this.addFieldError("password", getText("error.password.mismatch"));
    }
    return user;
    }

The "findUser" method dips into the MailReader Data Access Object layer, which is represented by the Database property. The code for the DAO layer is maintained as a separate component. The MailReader application imports the DAO JAR, but it is not responsible for maintaining any of the DAO source. Keeping the data access layer at "arms-length" is a very good habit. It encourages a style of development where the data access layer can be tested and developed independently of a specific end-user application. In fact, there are several renditions of the MailReader application, all which share the same MailReader DAO JAR!


Best Practice:

"Strongly separate data access and business logic from the rest of the application."


When "findUser" returns, the Logon Action looks to see if a valid (non-null) User object is returned. A valid User is passed to the User property. Although it is still a JavaBean property, the User property is not implemented in quite the same way as Username and Password.


MailreaderSupport.setUser
public User getUser() {
    return (User) getSession().get(Constants.USER_KEY);
    }
    public void setUser(User user) {
    getSession().put(Constants.USER_KEY, user);
    }

Instead of using a field to store the property value, "setUser" passes it to a Session property. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -