http://git-wip-us.apache.org/repos/asf/struts-examples/blob/0677ec5c/mailreader/src/main/webapp/tour.html ---------------------------------------------------------------------- diff --git a/mailreader/src/main/webapp/tour.html b/mailreader/src/main/webapp/tour.html new file mode 100644 index 0000000..7dc29f0 --- /dev/null +++ b/mailreader/src/main/webapp/tour.html @@ -0,0 +1,2470 @@ + + + + + + + + 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. + +

+ +
+ + + + + + + + + + +
+ +

+ 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>
+
+ +

+ You might note that the web.xml configuration does not specify which file extension + to use with actions. + The default extension for Struts 2 is ".action", + but the extension can be changed in the struts.properties file. + For compatability with prior releases, the MailReader uses a .do extension for actions. +

+ +
+
struts.properties
+
struts.action.extension = do
+
+ +

+ The web.xml does specify a "Welcome File List" for the 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">Login_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/resources", + 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. + In Struts 2, message resources are associated with the Action class being processed. + If we check the source, we find a language resource bundle named + MailreaderSupport. + MailreaderSupport is our base class for all the MailReader Actions. + Since all of our Actions extend MailreaderSupport, + all of our Actions can use the same resource bundle. +

+ +
+
Message Resource entries used by the Welcome page
+
index.heading=MailReader Application Options
+index.login=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="Login_input"/>"><s:text
+          name="index.login"/></a></li>
+      </ul>
+
+      <h3>Language Options</h3>
+      <ul>
+          <li>
+              <s:url id="en" action="Welcome">
+                  <s:param name="request_locale">en</s:param>
+              </s:url>
+               <s:a href="%{en}">English</s:a>
+           </li>
+          <li>
+              <s:url id="ja" action="Welcome">
+                <s:param name="request_locale">ja</s:param>
+              </s:url>
+              <s:a href="%{ja}">Japanese</s:a>
+          </li>
+          <li>
+              <s:url id="ru" action="Welcome">
+              <s:param name="request_locale">ru</s:param>
+              </s:url>
+              <s:a href="%{ru}">Russian</s: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 in the {{/src/main/resources}} folder, + 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 Login link first. +

+ +

Login

+ +

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

+ +

Login Page

+ +

+ The Login page displays a form that accepts a username and password. + You can use the default username and password to login + (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="login.title"/></title>
+      <link href="<s:url value="/css/mailreader.css"/>" rel="stylesheet"
+        type="text/css"/>
+  </head>
+  <body onLoad="self.focus();document.Login.username.focus()">
+    <s:actionerror/>
+    <s:form action="Login" validate="true">
+      <s:textfield key="username"/>
+      <s:password key="password"/>
+      <s:submit key="button.save"/>
+      <s:reset key="button.reset"/>
+      <s:submit action="Login_cancel" onclick="form.onsubmit=null"
+        key="button.cancel"/>
+    </s:form>
+    <jsp:include page="Footer.jsp"/>
+  </body>
+</html>
+
+ +

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

+ +

+ The first new tag on the Login 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. + 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. +

+ +

+ 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 Struts Tags support templates and themes so that a set of HTML tags can be + rendered from a single Struts Tag. For example, the single tag +

+ +

+    <s:textfield key="username"/>
+
+ +

+ generates a wad of HTML markup. +

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

+ If for some reason you don't like the markup generated by a Struts 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 + "template/simple/actionerror.ftl", + and place this one file at the base of your application's 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 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="Login_cancel" onclick="form.onsubmit=null"
+    key="button.cancel"/>
+
+ +

+ Here we are creating the Cancel button for the form. + The button's attribute action="Login_cancel" + tells the framework to submit to the Login'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. + Another entry on the special-case list is the "input" method. +

+ +
+
Tip:
+
+

+ The Struts Tags have options and capabilities beyond what we have shown here. + For more see, the + Struts 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. +

+ +

Login-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 + Login-validation.xml. +

+ +
+
Validation file for Login 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. Essentially, 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. +

+ + + +

Login Action

+ +

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

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

+ Login 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 Login 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. +

+ +
+
MailreaderSupport.getSession() and setSession()
+
private Map session;
+public Map getSession() {
+  return session;
+
+public void setSession(Map value) {
+  session = value;
+}
+
+ +

+ To look at the MailreaderSupport class, + you would think the Session property is a plain-old Map. + In fact, + the Session property is an adapter that is backed by the servlet session object at runtime. + The MailreaderSupport class doesn't need to know that though. + It can treat Session like any other Map. + We can also test the MailreaderSupport class by passing it some other implementation of + Map, running the test, + and then looking to see what changes MailreaderSupport made to our "mock" Session object. +

+ +

+ But, when MailreaderSupport is running inside a web application, + how does it acquire a reference to the servlet session? +

+ +

+ Good question. If you were to look at just the MailreaderSupport class, + you would not see a single line of code that sets the session property. + But, yet, when we run the class, the session property is not null. + Hmmm. +

+ +

+ The magic that provides the Session property a runtime value is called + "dependency injection". + The MailreaderSupport class implements a interface called SessionAware. + SessionAware is bundled with the framework, + and it defines a setter for the Session property. +

+ +

+ public void setSession(Map session); +

+ +

+ Also bundled with the framework is an object called the + ServletConfigInterceptor. + If the ServletConfigInterceptor sees that an Action implements the SessionAware interface, + it automatically set the session property. +

+ +
if (action instanceof SessionAware) {
+  ((SessionAware) action).setSession(context.getSession());
+}
+ +

+ The framework uses these "Interceptor" classes to create a front controller + for each action an application defines. + Each Interceptor can peek at the request before an Action class is invoked, + and then again after the Action class is invoked. + (If you have worked with Servlet + Filters, + you will recognize this pattern. + But, unlike Filters, Interceptors are not tied to HTTP. + Interceptors can be tested and developed outside of a web application.) +

+ +

+ You can use the same set of Interceptors for all your actions, + or define a special set of Interceptors for any given action, + or define different sets of Interceptors to use with different types of actions. + The framework comes with a default set of Interceptors, + that it will use when another set is not specified, + but you can designate your own default Interceptor set (or "stack") + in the Struts configuration. +

+ +

+ Many Interceptors provide a utility or helper functions, + like setting the session property. + Others, like the ValidationInterceptor, + can change the workflow of an action. + Interceptors are key feature of the framework, + and we will see a few more on the tour. +

+ +

+ If a valid User is not found, or the password doesn't match, + the "findUser" method invokes the addFieldError method to note the + problem. + When "findUser" returns, the Login Action checks for errors, + and then it returns either INPUT or SUCCESS. +

+ +

+ The "addFieldError" method is provided by the ActionSupport class, + which is bundled with the framework. + The constants for INPUT and SUCCESS are also provided by ActionSupport. + While the ActionSupport class provides many useful utilities, + you are not required to use it as a base class. + Any Java class can be used as an Action, if you like. +

+ +

+ It is a good practice to provide a base class with utilities + that can be shared by an application's Action classes. + The framework does this with ActionSupport, + and the MailReader application does the same with the MailreaderSupport class. +

+ +
+
Best Practice:
+
+

"Use a base class to define common functionality."

+
+
+ +

+ But, what happens if Login returns INPUT instead of SUCCESS. + How does the framework know what to do next? +

+ +

+ To answer that question, + we need to turn back to the Struts configuration + and look at how Login is declared. +

+ + +

Login Configuration

+ +

+ The Login action element outlines how the Login workflow operates, + including what to do when the Action returns "input", + or the default result name "success". +

+ +
+
mailreader-support.xml Login
+
<action name="Login_*" method="{1}" class="mailreader2.Login">
+  <result name="input">/pages/Login.jsp</result>
+  <result name="cancel" type="redirect-action">Welcome</result>
+  <result type="redirect-action">MainMenu</result>
+  <result name="expired" type="chain">ChangePassword</result>
+  <exception-mapping
+    exception="org.apache.struts.apps.mailreader.dao.ExpiredPasswordException"
+  result="expired"/>
+  <interceptor-ref name="guest"/>
+</action>
+
+ +

+ You might notice that the name of the Login action element is not "Login" + but "Login_*". + The asterisk is a special "wildcard" notation that tells the framework to match any series + of character at this point. + In the method attribute, + the "{1}" notation indicates that framework should substitute whatever characters match + the asterisk at runtime. + When we cite actions like "Login_cancel" or "Login_input", + the framework matches "cancel" or "input" with the wildcard and fills in the blanks. +

+ +

+ The "trailing bang" notation was hardwired into WebWork 2. + To provide backward compatibility, + the notation is supported by Struts 2.0. + If you prefer to use wildcards to emulate the same notation, + as the Mailreader does, + you should disable the old notation in the Struts properties file. +

+ +
+
struts.properties
+
struts.enable.DynamicMethodInvocation = false
+
+ +

+ Using wildcards with a exclamation point (or "bang") is not the only way we can use + wilcards to invoke methods. + If we wanted to use actions like "inputLogin", + we could move the asterisk and use an action name like "*Login". +

+ +

+ Within the Login action element, the first result element is named "input". + If validation or authentification fail, + the Action class will return "input" and the framework will transfer control to the + "Login.jsp" page. +

+ +

+ The second result element is named cancel. + If someone presses the cancel button on the Login page, + the Action class will return "cancel", this result will be selected, + and the framework will issue a redirect to the Welcome action. +

+ +

+ The third result has no name, + so it will be called if the default success token is returned. + So, if the Login succeeds, + control will transfer to the MainMenu action. +

+ +

+ The MailReader DAO exposes a "ExpiredPasswordException". + If the DAO throws this exception when the User logs in, + the framework will process the exception-mapping + and transfer control to the "ChangePassword" action. +

+ +

+ Just in case any other Exceptions are thrown, + the MailReader application also defines a global handler. +

+ +
+
mailreader-default.xml exception-mapping
+
<global-exception-mappings>
+  <exception-mapping
+    result="error"
+    exception="java.lang.Exception"/>
+</global-exception-mappings>
+
+ +

+ If an unexpected Exception is thrown, + the exception-mapping will transfer control to the action's "error" result, + or to a global "error" result. + The MailReader defines a global "error" result + which transfers control to an "Error.jsp" page + that can display the error message. +

+ +
+
Error.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>Unexpected Error</title>
+  </head>
+  <body>
+    <h2>An unexpected error has occured</h2>
+    <p>
+      Please report this error to your system administrator
+      or appropriate technical support personnel.
+      Thank you for your cooperation.
+    </p>
+    <hr />
+    <h3>Error Message</h3>
+    <s:actionerror />
+    <p>
+      <s:property value="%{exception.message}"/>
+    </p>
+    <hr />
+    <h3>Technical Details</h3>
+    <p>
+      <s:property value="%{exceptionStack}"/>
+    </p>
+    <jsp:include page="Footer.jsp"/>
+  </body>
+</html>
+
+ +

+ The Error page uses property tags to expose + the Exception message and the Exception stack. +

+ +

+ Finally, the Login action specifies an InterceptorStack + named defaultStack. + If you've worked with Struts 2 or WebWork 2 before, that might seem strange, + since "defaultStack" is the factory default. +

+ +

+ In the MailReader application, most of the actions are only available + to authenticated users. + The exceptions are the Welcome, Login, and Register actions + which are available to everyone. + To authenticate clients, + the MailReader uses a custom Interceptor and a custom Interceptor stack. +

+ +
+
mailreader2.AuthenticationInterceptor
+
package mailreader2;
+import com.opensymphony.xwork2.interceptor.Interceptor;
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.Action;
+import java.util.Map;
+import org.apache.struts.apps.mailreader.dao.User;
+
+public class AuthenticationInterceptor implements Interceptor {
+  public void destroy () {}
+  public void init() {}
+  public String intercept(ActionInvocation actionInvocation) throws Exception {
+    Map session = actionInvocation.getInvocationContext().getSession();
+    User user = (User) session.get(Constants.USER_KEY);
+    boolean isAuthenticated = (null!=user) && (null!=user.getDatabase());
+    if (isAuthenticated) {
+      return actionInvocation.invoke();
+    }
+    else {
+      return Action.LOGIN;
+    }
+  }
+}
+
+ +

+ The AuthenticationInterceptor looks to see if a User object + has been stored in the client's session state. + If so, it returns normally, and the next Interceptor in the set would be invoked. + If the User object is missing, the Interceptors returns "login". + The framework would match "login" to the global result, + and transfer control to the Login action. +

+ +

+ The MailReader defines three custom Interceptor stacks: "user", "user-submit", + and "guest". +

+ +
+
mailreader-default.xml interceptors
+
<interceptors>
+  <interceptor name="authentication"
+               class="mailreader2.AuthenticationInterceptor"/>
+  <interceptor-stack name="user" >
+      <interceptor-ref name="authentication" />
+      <interceptor-ref name="defaultStack"/>
+  </interceptor-stack>
+  <interceptor-stack name="user-submit" >
+      <interceptor-ref name="tokenSession" />
+      <interceptor-ref name="user"/>
+  </interceptor-stack>
+  <interceptor-stack name="guest" >
+      <interceptor-ref name="defaultStack"/>
+  </interceptor-stack>
+</interceptors>
+<default-interceptor-ref name="user"/>
+
+ +

+ The user stacks require that the client be authenticated. + In other words, that a User object is present in the session. + The actions using a guest stack can be accessed by any client. + The -submit versions of each can be used with actions + with forms, to guard against double submits. +

+ +
Double Submits
+ +

+ A common problem with designing web applications is that users are impatient + and response times can vary. + Sometimes, people will press a submit button a second time. + When this happens, the browser submits the request again, + so that we now have two requests for the same thing. + In the case of registering a user, if someone does press the submit button + again, and their timing is bad, + it could result in the system reporting that the username has already been + used. + (The first time the button was pressed.) + In practice, this would probably never happen, but for a longer running + process, like checking out a shopping cart, + it's easier for a double submit to occur. +

+ +

+ To forestall double submits, and "back button" resubmits, + the framework can generate a token that is embedded in the form + and also kept in the session. + If the value of the tokens do not compare, + then we know that there has been a problem, + and that a form has been submitted twice or out of sequence. +

+ +

+ The Token Session Interceptor will also attempt to provide intelligent + fail-over in the event of multiple requests using the same session. + That is, it will block subsequent requests until the first request is complete, + and then instead of returning the "invalid.token" code, + it will attempt to display the same response that the + original, valid action invocation would have displayed +

+ +

+ Because the default interceptor stack will now authenticate the client, + we need to specify the standard "defaultStack" for the three + "guest actions", Welcome, Login, and Register. + Requiring authentification by default is the better practice, since it + means that we won't forget to enable it when creating new actions. + Meanwhile, those pesky users will ensure that we don't forget to disable + authentification for "guest" services. +

+ +

MainMenu

+ +

+ On a successful login, the Main Menu page displays. + If you logged in using the demo account, + the page title should be "Main Menu Options for John Q. User". + Below this legend should be two links: +

+ + + +

+ Let's review the source for the "MainMenu" action mapping, + and the "MainMenu.jsp". +

+ +
+
Action mapping element for MainMenu
+
<action name="MainMenu" class="mailreader2.MailreaderSupport">
+    <result>/pages/MainMenu.jsp</result>
+    </action>
+ +
MainMenu.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="mainMenu.title"/></title>
+      <link href="<s:url value="/css/mailreader.css"/>" rel="stylesheet"
+      type="text/css"/>
+  </head>
+
+  <body>
+  <h3><s:text name="mainMenu.heading"/> <s:property
+    value="user.fullName"/></h3>
+  <ul>
+    <li><a href="<s:url action="Registration_input" />">
+        <s:text name="mainMenu.registration"/>
+      </a>
+    </li>
+    <li><a href="<s:url action="Logout" />">
+      <s:text name="mainMenu.logout"/>
+      </a>
+    </ul>
+  </body>
+</html>
+
+ +

+ The source for "MainMenu.jsp" also contains a new tag, + property, which we use to customize the page with the + "fullName" property of the authenticated user. +

+ +

+ Displaying the user's full name is the reason the MainMenu action + references the MailreaderSupport class. + The MailreaderSupport class has a User property that the text tag + can access. + If we did not utilize MailreaderSupport, + the property tag would not be able to find the User object to print + the full name. +

+ +

+ The customized MainMenu page offers two standard links. + One is to "Edit your user registration profile". + The other is to "Logout the MailReader Demonstration Application". +

+ +

Registration page +

+ +

+ If you follow the "Edit your user registration profile" link from the Main + Menu page, + we will finally reach the heart of the MailReader application: the + Registration, or "Profile", page. + This page displays everything MailReader knows about you + (or at least your login), + while utilizing several interesting techniques. +

+ +

+ To do double duty as the "Create" Registration page and the "Edit" + Registration page, + the "Registration.jsp" makes extensive use of the test tags, + to make it appears as though there are two distinct pages. +

+ +
+
Registration.jsp - head element
+
<head>
+  <s:if test="task=='Create'">
+    <title><s:text name="registration.title.create"/></title>
+  </s:if>
+  <s:if test="task=='Edit'">
+    <title><s:text name="registration.title.edit"/></title>
+  </s:if>
+  <link href="<s:url value="/css/mailreader.css"/>" rel="stylesheet"
+    type="text/css"/>
+</head>
+
+ +

+ For example, if client is editing the form (task == 'Edit'), + the page inserts the username from the User object. + For a new Registration (task == 'Create'), + the page creates an empty data-entry field. +

+ +
+
Note:
+
+

+ Presention Logic - + The "test" tag is a convenient way to express presentation + logic within your pages. + Customized pages help to prevent user error, + and dynamic customization reduces the number of server pages your + application needs to maintain, among other benefits. +

+
+
+ +

+ The page also uses logic tags to display a list of subscriptions + for the given user. + If the RegistrationForm has task set to "Edit", + the lower part of the page that lists the subscriptions is exposed. +

+ +
+
+
<s:if test="task == 'Edit'">
+  <div align="center">
+    <h3><s:text name="heading.subscriptions"/></h3>
+  </div>
+    <!-- ... -->
+  </s:if>
+<jsp:include page="Footer.jsp"/>
+</body></html>
+
+ +

+ Otherwise, the page contains just the top portion -- + a data-entry form for managing the user's registration. +

+ +

iterator

+ +

+ Besides "if" there are several other control tags that you can use + to sort, filter, or iterate over data. + The Registration page includes a good example of using the iterator + tag to display the User's Subscriptions. +

+ +

+ The subscriptions are stored in a hashtable object, which is in turn + stored in the user object. + So to display each subscription, we have to reach into the user object, + and loop through the members of the subscription collection. + Using the iterator tag, you can code it the way it sounds. +

+ +
+
Using iterator to list the Subscriptions
+
<s:iterator value="user.subscriptions">
+  <tr>
+    <td align="left">
+      <s:property value="host"/>
+    </td>
+    <td align="left">
+       <s:property value="username"/>
+   </td>
+  <td align="center">
+      <s:property value="type"/>
+  </td>
+  <td align="center">
+     <s:property value="autoConnect"/>
+  </td>
+  <td align="center">
+    <a href="<s:url action="Subscription_delete"><s:param name="host" value="host"/></s:url>">
+      <s:text name="registration.deleteSubscription"/>
+    </a> 
+    <a href="<s:url action="Subscription_edit"><s:param name="host" value="host"/></s:url>">
+      <s:text name="registration.editSubscription"/>
+     </a>
+   </td>
+ </tr>
+</s:iterator>
+
+ +

+ When the iterator renders, it generates a list of Subscriptions for the current User. +

+ +
+ +
+

Current Subscriptions

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ Host Name + + User Name + + Server Type + + Auto + + Action +
+ mail.hotmail.com + + user1234 + + pop3 + + false + + + Delete + +   + + Edit + +
+ mail.yahoo.com + + jquser + + imap + + false + + + Delete + +   + + Edit + +
+ Add + +
+ +

+ Now look back at the code used to generate this block. +

+

+ Notice anything nifty? +

+

+ How about that the markup between the iterator tag is + actually simpler than the markup that we would use to render one row of the + table? +

+

+ Instead of using a qualified reference like "value=user.subscription[0].host", + we use the simplest possible reference: "value=host". + We didn't have to define a local variable, and reference that local in the loop code. + The reference to each item in the list is automatically resolved, no fuss, no muss. +

+

+ Nice trick! +

+ +

+ The secret to this magic is the value stack. + Next to Interceptors, the value stack is probably the coolest thing there is about the + framework. + To explain the value stack, let's step back and start from the beginning. +

+ +

+ Merging dynamic data into static web pages is a primary reason + we create web applications. + The Java API has a mechanism that allows you to + place objects in a servlet scope (page, request, session, or + application), and then retrieve them using a JSP scriplet. + If the object is placed directly in one of the scopes, + a JSP tag or scriptlet can find that object by searching page scope and + then request scope, and session scope, and finally application scope. +

+ +

+ The value stack works much the same way, only better. + When you push an object on the value stack, + the public properties of that object become first-class properties of the stack. + The object's properties become the stack's properties. + If another object on the stack has properties of the same name, + the last object pushed onto the stack wins. (Last-In, First-Out.) +

+ +

+ When the iterator tag loops through a collection, + it pushes each item in the collection onto the stack. + The item's properties become the stack's property. + In the case of the Subscriptions, + if the Subscription has a public Host property, + then during that iteration, + the stack can access the same property. +

+ +

+ Of course, at the end of each iteration, the tag "pops" the item off the stack. + If we were to try and access the Host property later in the page, + it won't be there. +

+ +

+ When an Action is invoked, the Action class is pushed onto the value stack. + Since the Action is on the value stack, + our tags can access any property of the Action + as if it were an implicit property of the page. + The tags don't access the Action directly. + If a textfield tag is told to render the "Username" property, + the tag asks the value stack for the value of "Username", + and the value stack returns the first property it finds by that name, + on any object on the stack. +

+ +

+ The Validators also use the stack. + When validation fails on a field, + the value for the field is pushed onto the value stack. + As a result, if the client enters text into an Integer field, + the framework can still redisplay whatever was entered. + An invalid input value is not stored in the field (even if it could be). + The invalid input is pushed onto the stack for the scope of the request. +

+ +

+ The Subscription list uses another new tag: the param tag. + As tags go, "param" takes very few parameters of its own: just "name" and "value", + and neither is required. + Although simple, "param" is one of the most powerful tags the framework provides. + Not so much because of what it does, + but because of what "param" allows the other tags to do. +

+ +

+ Essentially, the "param" tag provides parameters to other tags. + A tag like "text" might be retrieving a message template with several replaceable + parameters. + No matter how many parameters are in the template, and no matter what they are named, + you can use the "param" tag to pass in whatever you need. +

+ +
pager.legend = Displaying {current} of {count} items matching {criteria}.
+...
+<s:text name="pager.legend">
+    <s:param name="current" value="42" />
+    <s:param name="count" value="314" />
+    <s:param name="criteria" value="Life, the Universe, and Everything" />
+</s:text>
+ +

+ In the case of an "url" tag, + we can use "param" to create the query string. + A statement like this: +

+ +

+  <s:url action="Subscription_edit"><s:param name="host" value="host"/></s:url>">
+
+ +

+ can render a hyperlink like this: +

+ +

+  <a href="/struts2-mailreader/Subscription_edit.do?host=mail.yahoo.com">Edit</a>
+
+ + + +

+ If a hyperlink needs more parameters, + you can use "param" to add as many parameters as needed. +

+ +

+ Subscription +

+ +

+ If we follow one of the "Edit" subscription links on the Registration page, + we come to the Subscriptions page, + which displays the details of our description in a data-entry form. + Let's have a look at the Subscription configuration + and follow the bouncing ball from page to action to page. +

+ +
+
mailreader-support.xml Subscription element
+
<action name="Subscription_*" method="{1}" class="mailreader2.Subscription">
+  <result name="input">/pages/Subscription.jsp</result>
+  <result type="redirect-action">Registration_input</result>
+</action>
+
+ +

+ The Edit link specified the Subscription action, + but also includes the qualifier _edit. + The wildcard notation tells the framework to use any characters given after "Subscription_" + as the name of a method to invoke on the Action class, + instead of the default execute method. + The "alternate" execute methods are called alias methods. +

+ +
+
Subscription edit alias
+
public String edit() {
+  setTask(Constants.EDIT);>
+  return find();
+}
+
+public String find() {
+  org.apache.struts.apps.mailreader.dao.Subscription
+    sub = findSubscription();
+   if (sub == null) {
+       return ERROR;
+   }
+   setSubscription(sub);
+   return INPUT;
+}
+
+ +

+ The "edit" alias has two responsibilities. + First, it must set the Task property to "Edit". + The Subscription page will render itself differently + depending on the value of the Task property. + Second, "edit" must locate the relevant Subscription + and set it to the Subscription property. + If all goes well, "edit" returns the INPUT token, + so that the "input" result will be invoked. +

+ +

+ In the normal course, the Subscription should always be found, + since we selected the entry from a system-generated list. + If the Subscription is not found, + it would be because the database disappeared + or the request is being spoofed. + If the Subscription is not found, + edit returns the token for the global "error" result, + because this condition is unexpected. +

+ +

+ The business logic for the "edit" alias is a simple wrapper + around the MailReader DAO classes. +

+ +
+
MailreaderSupport findSubscription()
+
public Subscription findSubscription() {
+    return findSubscription(getHost());
+}
+
+public Subscription findSubscription(String host) {
+    Subscription subscription;
+    subscription = getUser().findSubscription(host);
+    return subscription;
+}
+
+ +

+ This code is very simple + and doesn't seem to provide much in the way of error handling. + But, that's OK. + Since the page is suppose to be entered from a link that we created, + we do expect everything to go right here. + But, if it doesn't, the global exception handler we defined in the + MailReader configuration will trap the exception for us. +

+ +

+ Likewise, the AuthentificationInterceptor will ensure that only clients + with a valid User object can try to edit a Subscription. + If the session expired, or someone bookmarked the page, + the client will be redirected to the Login page automatically. +

+ +

+ As a final layer of defense, we also configured a validator for Subscription, + to ensure that we are passed a Host parameter. +

+ +
+
Subscription-validation.xml
+
<!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="host">
+    <field-validator type="requiredstring">
+        <message key="error.host.required"/>
+    </field-validator>
+  </field>
+</validators>
+
+ +

+ By keeping routine safety precautions out of the Action class, + the all-important Action becomes smaller and easier to maintain. +

+ +

+ After setting the relevent Subscription object to the Subscription property, + the framework transfers control to the (you guessed it) Subscription page. +

+ +
+
Subscription.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>
+    <s:if test="task=='Create'">
+        <title><s:text name="subscription.title.create"/></title>
+    </s:if>
+    <s:if test="task=='Edit'">
+        <title><s:text name="subscription.title.edit"/></title>
+    </s:if>
+    <s:if test="task=='Delete'">
+        <title><s:text name="subscription.title.delete"/></title>
+    </s:if>
+    <link href="<s:url value="/css/mailreader.css"/>" rel="stylesheet"
+          type="text/css"/>
+  </head>
+  <body onLoad="self.focus();document.Subscription.username.focus()">
+
+    <s:actionerror/>
+    <s:form action="Subscription_save" validate="true">
+      <s:token />
+      <s:hidden name="task"/>
+      <s:label key="username" name="user.username"/>
+
+      <s:if test="task == 'Create'">
+        <s:textfield key="mailHostname" name="host"/>
+      </s:if>
+      <s:else>
+        <s:label key="mailHostname" name="host"/>
+        <s:hidden name="host"/>
+      </s:else>
+
+      <s:if test="task == 'Delete'">
+        <s:label key="subscription.username"/>
+        <s:label key="subscription.password"/>
+        <s:label key="subscription.type"/>
+        <s:label key="subscription.autoConnect"/>
+        <s:submit key="button.confirm"/>
+      </s:if>
+      <s:else>
+        <s:textfield key="subscription.username"/>
+        <s:textfield key="subscription.password"/>
+        <s:select key="subscription.type" list="types"/>
+        <s:checkbox key="subscription.autoConnect"/>
+        <s:submit key="button.save"/>
+        <s:reset key="button.reset"/>
+      </s:else>
+
+      <s:submit action="Registration_input"
+                key="button.cancel"
+                onclick="form.onsubmit=null"/>
+  </s:form>
+
+  <jsp:include page="Footer.jsp"/>
+  </body>
+</html>
+
+ +

+ As before, we'll discuss the tags and attributes that are new to this page: + "token", "hidden", "label", "select", and "checkbox". +

+ +

+ The token tag works with the Token Session Interceptor to foil double + submits. + The tag generates a key that is embedded in the form and cached in the session. + Without this tag, the Interceptor can't work it's magic. +

+ +

+ The hidden tag embeds the Task property into the form. + When the form is submitted, + the Subscription_save action will use the Task property to decide + whether to insert or update the form. +

+ +

+ The label renders a "read only" version of a property, + suitable for placement in the form. + In Edit or Delete mode, we want the Host property to be immutable, + since it is used as a key. (As unwise as that might sound.) + In Delete mode, all of the properties are immutable, + since we are simply confirming the delete operation. +

+ +

+ Saving the best for last, the Subscription form utilizes two more interesting + tags, "select" and "checkbox". +

+ +

+ Unsurprisingly, the select tag renders a select control, + but the tag does so without requiring a lot of markup or redtape. +

+ +
<s:select key="subscription.type" list="types" />
+
+ +

+ The interesting attribute of the "select" tag is "list", + which, in our case, specifies a value of "types". + If we take another look at the Subscription action, + we can see that it implements an interface named Preparable + and populates a Types property in a method named "prepare". +

+ +
+
Subscription-validation.xml
+
public class Subscription extends MailreaderSupport
+  implements Preparable {
+
+  private Map types = null;
+  public Map getTypes() {
+    return types;
+   }
+
+   public void prepare() {
+     Map m = new LinkedHashMap();
+       m.put("imap", "IMAP Protocol");
+       m.put("pop3", "POP3 Protocol");
+       types = m;
+       setHost(getSubscriptionHost());
+    }
+
+    // ... 
+
+ +

+ The default Interceptor stack includes the PrepareInterceptor, + which observes the Preparable interface. +

+ +
+
PrepareInterceptor
+
public class PrepareInterceptor extends AroundInterceptor {
+
+  protected void after(ActionInvocation dispatcher, String result) throws Exception {
+  }
+
+  protected void before(ActionInvocation invocation) throws Exception {
+    Object action = invocation.getAction();
+     if (action instanceof Preparable) {
+        ((Preparable) action).prepare();
+    }
+  }
+}
+ +

+ The PrepareInterceptor ensures that the "prepare" method will always be called + before "execute" or an alias method is invoked. + We use "prepare" to setup the list of items for the select list to display. + We also transfer the Host property from our Subscription object + to a local property, where it is easier to manage. +

+ +

+ Subscription.java +

+ +

+ Like many applications, the MailReader uses mainly String properties. + One exception is the AutoConnect property of the Subscription object. + On the HTML form, the AutoConnect property is represented by a checkbox. +

+ +

+ When writing web applications, the checkbox can be a tricky control. + The Subscription object has a boolean AutoConnect property, + and the checkbox simply has to represent its state. + The problem is, if you clear a checkbox, the browser client will not submit anything. + Nada. Zip. + It is as if the checkbox control never existed. + The HTTP protocol has no way to affirm "false". + If the control is missing, we need to figure out it's been unclicked. +

+ +

+ In Struts 1, + we use the reset method to work around checkbox issues. + In Struts 2, checkbox state is handled automatically. + The framework can detect when a checkbox tag has not been sent back, + and when that happens, + a default "false" value is used for the checkbox value. + No worries, mate. +

+ +

+ If we press the SAVE button, + the form will be submitted to the Subscription_save action. + Since the save method needs some additional validation, + we can add a validation file. +

+ +
+
Subscription-Subscription_save-validation.xml
+
<!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="host">
+    <field-validator type="requiredstring">
+        <message key="error.host.required"/>
+    </field-validator>
+  </field>
+</validators>
+
+ +

+ The validators follow the same type of inheritance path as the classes. + SubscriptionSave extends Subscription, + so when Subscription_save is validated, + the Host property specified by "Subscription-validation.xml" will also be required. +

+ +

+ If validation succeeds, the save method of Subscription will fire. +

+ +
+
Subscription
+ +
public String save() throws Exception {
+
+  if (Constants.DELETE.equals(getTask())) {
+   removeSubscription();
+  }
+
+  if (Constants.CREATE.equals(getTask())) {
+    copySubscription(getHost());
+  }
+
+  saveUser();
+  return SUCCESS;
+}
+
+ +

+ The save method uses the Task property to handle + the special cases of deleting and creating, + and then updates the state of the User object. +

+ +

+ The removeSubscription method calls the DAO facade, + and then updates the application state. +

+ +
+
removeSubscription
+
public void removeSubscription() throws Exception {
+  getUser().removeSubscription(getSubscription());
+  getSession().remove(Constants.SUBSCRIPTION_KEY);
+}
+
+ +

+ The copySubscription method is a bit more interesting. + The MailReader DAO layer API includes some immutable fields + that can't be set once the object is created. + Because key fields are immutable, + we can't just create a Subscription, let the framework populate all the fields, + and then save it when we are done -- because some fields can't be populated, + except at construction. +

+ +

+ One workaround would be to declare properties on the Action + for all the properties we need to pass to the Subscription or User objects. + When we are ready to create the object, + we could pass the new object values from the Action properties. +

+ +

+ Another workaround is to declare only the immutable properties on the Action, + and then use what we can from the domain object. +

+ +

+ This implementation of the MailReader utilizes the second alternative. + We define User and Subscription objects on our base Action, + and add other properties only as needed. +

+ +

+ To add a new Subscription or User, + we create a blank object to capture whatever fields we can. + When this "input" object returns, we create a new object, + setting the immutable fields to appropriate values, + and copy over the rest of the properties. +

+ +
+
copySubscription
+
public void copySubscription(String host) {
+  Subscription input = getSubscription();
+  Subscription sub = createSubscription(host);
+  if (null != sub) {
+    BeanUtils.setValues(sub, input, null);
+    setSubscription(sub);
+    setHost(sub.getHost());
+  }
+}
+
+ +

+ Of course, this is not a preferred solution, + but merely a way to work around an issue in the MailReader DAO API + that would not be easy for us change. +

+ +

Subscription Submit

+ +

+ When we pressed the SAVE button, there was one step that we overlooked. + The Mailreader application uses a "double submit" guard to keep people + from clicking the SAVE button multiple times and submitting the form again. +

+ +

+ To add the double-submit guard, we can change the actions default processing + stack to user-submit. + But, we don't want to just copy and paste the other action settings from + the main Subscription action. + What we can do is put the subscription actions in their own package, + so that they can share result types. +

+ +
+
mailreader-support.xml
+

+</package>
+
+<package name="subscription" namespace="/" extends="mailreader-support">
+
+    <global-results>
+        <result name="input">/Subscription.jsp</result>
+        <result type="redirect-action">Registration_input</result>
+    </global-results>
+
+    <action name="Subscription_save" method="save" class="mailreader2.Subscription">
+        <interceptor-ref name="user-submit" />
+    </action>
+
+    <action name="Subscription_*" method="{1}" class="mailreader2.Subscription" />
+
+</package>
+
+<package name="wildcard" namespace="/" extends="mailreader-support">
+
+    <action name="*" class="mailreader2.MailreaderSupport">
+        <result>/{1}.jsp</result>
+    </action>
+
+</package>
+}
+
+ +

+ Aftering a successful save, + the Subscription Action will return "success", + and the framework will redirect us back to Registration input. +

+ +

Summary

+

+ At this point, we've booted the application, logged on, + reviewed a Registration record, and edited a Subscription. + Of course, there's more, but from here on, it is mostly more of the same. + The full source code for MailReader is + + available online + and in the distribution. +

+ +

+ Enjoy! +

+
+ + http://git-wip-us.apache.org/repos/asf/struts-examples/blob/0677ec5c/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml deleted file mode 100644 index 0965639..0000000 --- a/pom.xml +++ /dev/null @@ -1,95 +0,0 @@ - - - - 4.0.0 - - org.apache.struts - struts2-apps - 2.5-SNAPSHOT - - - struts2-mailreader - war - Struts 2 Mail Reader Webapp - - - - - javax.servlet - servlet-api - provided - - - - ${project.groupId} - struts-mailreader-dao - 1.3.5 - - - - - org.apache.logging.log4j - log4j-core - ${log4j2.version} - - - org.apache.logging.log4j - log4j-jcl - ${log4j2.version} - - - - - - - - src/main/java - - **/*.xml - **/*.properties - - - - - - org.mortbay.jetty - jetty-maven-plugin - 8.1.16.v20140903 - - CTRL+C - 8999 - 10 - ${basedir}/src/main/webapp/ - - /struts2-mailreader - ${basedir}/src/main/webapp/WEB-INF/web.xml - - - - - - - UTF-8 - -