http://git-wip-us.apache.org/repos/asf/struts-examples/blob/0677ec5c/src/main/webapp/tour.html
----------------------------------------------------------------------
diff --git a/src/main/webapp/tour.html b/src/main/webapp/tour.html
deleted file mode 100644
index 7dc29f0..0000000
--- a/src/main/webapp/tour.html
+++ /dev/null
@@ -1,2470 +0,0 @@
-<!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">
-<head>
- <meta http-equiv="Content-Type" content="text/html; charset=us-ascii"/>
- <link rel="stylesheet" type="text/css" href="../css/mailreader.css"/>
-
- <title>A Walking Tour of the Struts 2 MailReader Application</title>
-</head>
-
-<body>
-<blockquote>
-<h2>A Walking Tour of the Struts 2 MailReader Application</h2>
-
-<p>
- <i>
- 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.
- </i>
-</p>
-
-<p>
- <i>
- 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
- <a href="http://struts.apache.org/primer.html">
- Key Technologies Primer</a>.
- </i>
-</p>
-
-<hr/>
-
-<ul>
- <li>
- <a href="#Welcome">Welcome</a>
-
- <ul>
- <li><a href="#web.xml">web.xml and resources.properties</a></li>
-
- <li><a href="#Welcome.do">Welcome.do</a></li>
-
- <li><a href="#Welcome.java">Welcome Action</a></li>
-
- <li><a href="#global-results">Global Results</a></li>
-
- <li><a
href="#ApplicationListener.java">ApplicationListener.java</a></li>
-
- <li><a href="#resources.properties">Message Resources</a></li>
-
- <li><a href="#Welcome.jsp">Welcome Page</a></li>
-
- </ul>
- </li>
-</ul>
-
-<ul>
- <li>
- <a href="#Login">Login</a>
- <ul>
-
- <li><a href="#Login.jsp">Login Page</a></li>
-
- <li><a href="#Login-validation.xml">Login-validation.xml</a></li>
-
- <li><a href="#Login.java">Login.java</a></li>
-
- <li><a
href="#MailreaderSupport.java">MailreaderSupport.java</a></li>
-
- <li><a href="#Login.xml">Login Configuration</a></li>
-
- </ul>
- </li>
-</ul>
-
-<ul>
- <li>
- <a href="#MainMenu">MainMenu</a>
- </li>
-</ul>
-
-<ul>
- <li>
- <a href="#Registration.jsp">Registration page</a>
- <ul>
- <li><a href="#iterator">iterator</a></li>
- </ul>
- </li>
-</ul>
-
-<ul>
- <li>
- <a href="#Subscription">Subscription</a>
-
- <ul>
- <li><a href="#Subscription.java">Subscription.java</a>
- </li>
- </ul>
- </li>
-</ul>
-<hr/>
-
-<p>
- 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.
-</p>
-
-<p>
- 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.
-</p>
-
-<p>
- For more about the MailReader, including alternate implementations and a
- set of formal Use Cases,
- please visit the <a href="http://www.StrutsUniversity.org/MailReader">
- Struts University MailReader site</a>.
-</p>
-
-<hr/>
-<blockquote>
- <p><font class="hint">
- <strong>JAAS</strong> -
- 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 <a
- href="http://struts.apache.org/primer.html">
- Key Technologies Primer</a> for more about
- authentication technologies.)
- </font></p>
-</blockquote>
-<hr/>
-
-<p>
- 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).
-</p>
-
-<h3><a name="Welcome" id="Welcome">Welcome Page</a></h3>
-
-<p>
- 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.
-</p>
-
-<h4><a name="web.xml" id="web.xml">web.xml</a></h4>
-
-<p>
- 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".
-</p>
-
-<hr/>
-<h5>web.xml - The Web Application Deployment Descriptor</h5>
-<pre><code><?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>
-
- <strong><filter>
- <filter-name>struts2</filter-name>
- <filter-class>
- org.apache.struts2.dispatcher.FilterDispatcher
- </filter-class>
- </filter></strong>
-
- <filter-mapping>
- <filter-name><strong>struts2</strong></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></code></pre>
-<hr/>
-
-<p>
- 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.
-</p>
-
-<hr/>
-<h5>struts.properties</h5>
-<pre><code>struts.action.extension = <strong>do</strong></code></pre>
-<hr/>
-
-<p>
- 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.
-</p>
-
-<p>
- However, most Struts applications do not refer to physical pages,
- but to "virtual resources" called <i>actions</i>.
- 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.
-</p>
-
-<hr/>
-<h5>Best Practice:</h5>
-<blockquote>
- <p><font class="hint">"Link actions not pages."</font></p>
-</blockquote>
-<hr/>
-
-<p>
- 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.
-</p>
-
-
-<p>
- Sites can list zero or more "Welcome" pages in the web.xml.
- <a href="http://forum.java.sun.com/thread.jspa?threadID=721445">
- Unless you are using Java 1.5,</a>
- 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?
-</p>
-
-<p>
- 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.
-</p>
-
-<hr/>
-<h5>MailReader's index.html</h5>
-<pre><code><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html><head>
- <META HTTP-EQUIV="Refresh" CONTENT="0;<strong>URL=Welcome.do</strong>">
- </head>
- <body>
- <p>Loading ...</p>
-</body></html></code></pre>
-<hr/>
-
-<p>
- 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.
-</p>
-
-<h4><a name="Welcome.do" id="Welcome.do">Welcome.do</a></h4>
-
-<p>
- 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.
-</p>
-<hr/>
-<h5>A simple "forward thru" action element</h5>
-<pre><code><action name="<strong>Welcome</strong>">
- <result><strong>/pages/Welcome.jsp</strong></result>
-</action></code></pre>
-<hr/>
-
-<p>
- 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".
-</p>
-
-<p>
- But if we peek at the configuration file for the MailReader,
- we find a slightly more complicated XML element for the Welcome action.
-</p>
-
-<hr/>
-<h5>The Welcome action element</h5>
-<pre><code><action name="Welcome" <b>class="mailreader2.Welcome"</b>>
- <result>/pages/Welcome.jsp</result>
- <strong><interceptor-ref name="guest"/></strong>
- </action></code></pre>
-<hr/>
-
-<p>
- Here, the <strong>Welcome</strong> 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".
-</p>
-
-<hr/>
-<h5>Key concept:</h5>
-<blockquote>
- <p>
- 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.
- </p>
-</blockquote>
-<hr/>
-
-<p>
- The net effect is that all of the result details,
- including the paths to server pages,
- all can be declared <em>once</em> in the configuration.
- Tightly coupled implementation details are not scattered all over
- the application.
-</p>
-
-<hr/>
-<h5>Key concept:</h5>
-<blockquote>
- <p>
- 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.
- </p>
-</blockquote>
-<hr/>
-
-
-<p>
- OK ... but why would a Welcome Action want to choose between "success" and
- "error"?
-</p>
-
-<h4><a name="Welcome.java" id="Welcome.java">Welcome Action</a></h4>
-
-<p>
- 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 <strong>page</strong>, the Welcome
- <strong>class</strong> checks to see if the database is available.
-</p>
-
-<p>
- 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.
-</p>
-
-<hr/>
-<h5>The Welcome Action class</h5>
-<pre><code>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)) {
- <strong>addActionError(Constants.ERROR_MESSAGES_NOT_LOADED);</strong>
- }
-
- // Confirm database loaded
- if (null==getDatabase()) {
- <strong>addActionError(Constants.ERROR_DATABASE_NOT_LOADED);</strong>
- }
-
- if (hasErrors()) {
- <strong>return ERROR;</strong>
- }
- else {
- <strong>return SUCCESS;</strong>
- }
- }
-}</code></pre>
-<hr/>
-
-<p>
- 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.
-</p>
-
-
-<h4><a name="global-results" id="global-results">Global Results</a></h4>
-
-<p>
- 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.
-</p>
-
-<hr/>
-<h5>MailReader's global-result element</h5>
-<pre><code> <global-results>
- <result
name=<strong>"error"</strong>><strong>/pages/Error.jsp</strong></result>
- <result name="invalid.token">/pages/Error.jsp</result>
- <result name="login" type="redirect-action">Login_input</result>
-</global-results></code></pre>
-<hr/>
-
-<p>
- Of course, if an individual action mapping defines its own "error" result
type,
- the local result would be used instead.
-</p>
-
-<h4><a name="ApplicationListener.java"
id="ApplicationListener.java">ApplicationListener.java</a>
-</h4>
-
-<p>
- 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?
-</p>
-
-<p>
- The database is created by a custom Listener that we configured in the
"web.xml".
-</p>
-
-<hr/>
-<h5>mailreader2.ApplicationListener</h5>
-<pre><code> <listener>
- <listener-class>
- <strong>mailreader2.ApplicationListener</strong>
- </listener-class>
-</listener></code></pre>
-<hr/>
-
-<p>
- By default, our ApplicationListener loads a <strong>MemoryDatabase</strong>
- 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.
-</p>
-
-<p>
- 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.
-</p>
-
-<hr/>
-<h5>The "seed" user element from the MailReader database.xml</h5>
-<pre><code><user username="<strong>user</strong>"
fromAddress="john.u...@somewhere.com"
- fullName="<strong>John Q. User</strong>" password="<strong>pass</strong>">
- <subscription host="<strong>mail.hotmail.com"</strong>
autoConnect="false"
- password="bar" type="pop3" username="user1234">
- </subscription>
- <subscription host="<strong>mail.yahoo.com</strong>"
autoConnect="false" password="foo"
- type="imap" username="jquser">
- </subscription>
-</user></code></pre>
-<hr/>
-
-<p>
- The "seed" user element creates a registration record for "John Q. User",
- with the subscription detail for his hotmail and yahoo accounts.
-</p>
-
-<h4><a name="resources.properties" id="resources.properties">Message
Resources</a>
-</h4>
-
-<p>
- 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
- <em>MailreaderSupport.</em>
- 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.
-</p>
-
-<hr/>
-<h5>Message Resource entries used by the Welcome page</h5>
-<pre><code><strong>index.heading=</strong>MailReader Application Options
-<strong>index.login=</strong>Log on to the MailReader Application
-<strong>index.registration=</strong>Register with the MailReader Application
-<strong>index.title=</strong>MailReader Demonstration Application
-<strong>index.tour=</strong>A Walking Tour of the MailReader Demonstration
Application</code></pre>
-<hr/>
-
-<p>
- 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.
-</p>
-
-<h4><a name="Welcome.jsp" id="Welcome.jsp">Welcome Page</a></h4>
-
-<p>
- After confirming that the necessary resources exist, the Welcome action
- forwards to the Welcome page.
-</p>
-<hr/>
-<h5>Welcome.jsp</h5>
-<pre><code><%@ page contentType="text/html; charset=UTF-8" %>
-<strong><%@ taglib prefix="s" uri="http://struts.apache.org/tags"
%></strong>
- <!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><strong><s:text name="index.title"/></strong></title>
- <link href="<strong><s:url value="/css/mailreader.css"/></strong>"
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><strong><s:i18n name="alternate"></strong>
- <img src="<s:text name="struts.logo.path"/>"
- alt="<s:text name="struts.logo.alt"/>"/>
- <strong></s:i18n></strong></p>
-
- <p><a href="<s:url action="Tour" />"><s:text
name="index.tour"/></a></p>
-
- </body>
-</html></code></pre>
-<hr/>
-
-<p>
- 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".
-</p>
-
-<p>
- (We use the tag prefix "s:" in the Struts 2 MailReader application,
- but you can use whatever prefix you like in your applications.)
-</p>
-
-<p>
- The <strong>text</strong> 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.
-</p>
-
-<p>
- The <strong>url</strong> 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.
-</p>
-
-<hr/>
-<h5>Tip:</h5>
-<blockquote>
- <p><font class="hint">
- <strong>Cookies</strong> -
- 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.)
- </font></p>
-</blockquote>
-<hr/>
-
-<p>
- The <strong>i18n</strong> 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.
-</p>
-
-<p>
- The <strong>alternate</strong> bundle is stored in the
{{/src/main/resources}} folder,
- so that it ends up under "classes", which is on the application's class
path.
-</p>
-
-<p>
- In the span of a single request for the Welcome page, the framework has
done
- quite a bit already:
-</p>
-
-<ul>
- <li>
- Confirmed that required resources were loaded during initialization.
- </li>
-
- <li>
- Written all the page headings and labels from internationalized
- message resources.
- </li>
-
- <li>
- Automatically URL-encoded paths as needed.
- </li>
-</ul>
-
-<p>
- 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.
-</p>
-
-<h3><a name="Login" id="Login">Login</a></h3>
-
-<p>
- If you choose the Login link, and all goes well, the Login action forwards
- control to the Login page.
-</p>
-
-<h4><a name="Login.jsp" id="Login.jsp">Login Page</a></h4>
-
-<p>
- The Login page displays a form that accepts a username and password.
- You can use the default username and password to login
- (<strong>user</strong> and <strong>pass</strong>), 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.
-</p>
-
-<hr/>
-<h5>Login.jsp</h5>
-<pre><code><%@ 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()">
- <strong><s:actionerror/></strong>
- <strong><s:form action="Login" validate="true"></strong>
- <strong><s:textfield key="username"/></strong>
- <strong><s:password key="password"/></strong>
- <strong><s:submit key="button.save"/></strong>
- <strong><s:reset key="button.reset"/></strong>
- <s:submit <strong>action="Login_cancel"
onclick="form.onsubmit=null"</strong>
- key="button.cancel"/>
- </s:form>
- <jsp:include page="Footer.jsp"/>
- </body>
-</html></code></pre>
-<hr/>
-
-<p>
- We already saw some of the tags used by the Login page on the Welcome page.
- Let's focus on the new tags.
-</p>
-
-<p>
- The first new tag on the Login page is <strong>actionerrors</strong>.
- 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.
-</p>
-
-<p>
- The second new tag is <strong>form</strong>.
- 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.
-</p>
-
-<p>
- 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.
-</p>
-
-<p>
- 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.
-</p>
-
-<p>
- 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
-</p>
-
-<pre><code>
- <s:<strong>textfield</strong> key="username"/>
-</code></pre>
-
-<p>
- generates a wad of HTML markup.
-</p>
-
-<hr/>
-<pre><code><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></code></pre>
-<hr/>
-
-<p>
- 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:
-</p>
-
-<hr/>
-<pre><code><#if (actionErrors?exists && actionErrors?size > 0)>
- <ul>
- <#list actionErrors as error>
- <li><span class="errorMessage">${error}</span></li>
- </#list>
- </ul>
-</#if></code></pre>
-<hr/>
-
-<p>
- 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.
-</p>
-
-<hr/>
-<pre><code><#if (actionErrors?exists && actionErrors?size > 0)>
- <strong><table></strong>
- <#list actionErrors as error>
- <strong><tr><td></strong><span
class="errorMessage">${error}</span><strong></td></tr></strong>
- </#list>
- <strong></table></strong>
-</#if></code></pre>
-<hr/>
-
-<p>
- Under the covers, the framework uses
- <a href="http://freemarker.sourceforge.net/">Freemarker</a>
- for its standard templating language.
- FreeMarker is similar to
- <a href="http://jakarta.apache.org/velocity/">Velocity</a>,
- 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.
-</p>
-
-<p>
- The <strong>password</strong> 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.)
-</p>
-
-<p>
- Unsurprisingly, the <strong>submit</strong> and <strong>reset</strong> tags
- render buttons of the corresponding types.
-</p>
-
-<p>
- The second submit button is more interesting.
-</p>
-
-<pre><code> <s:submit <strong>action="Login_cancel"
onclick="form.onsubmit=null"</strong>
- key="button.cancel"/>
-</code></pre>
-
-<p>
- Here we are creating the Cancel button for the form.
- The button's attribute <em>action="Login<strong>_</strong>cancel"</em>
- tells the framework to submit to the Login's "cancel" method
- instead of the usual "execute" method.
- The <em>onclick="form.onsubmit=null"</em> 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 <strong>cancel</strong>
method.
- Another entry on the special-case list is the "input" method.
-</p>
-
-<hr/>
-<h5>Tip:</h5>
-<blockquote>
- <p><font class="hint">
- The Struts Tags have options and capabilities beyond what we have
shown here.
- For more see, the <a
href="http://cwiki.apache.org/WW/tag-developers-guide.html">
- Struts Tag documentation.</a>
- </font></p>
-</blockquote>
-<hr/>
-
-<p>
- 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?
-</p>
-
-<p>
- For the answers, we need to look at another flavor of configuration file:
- the "validation" file.
-</p>
-
-<h4><a name="Login-validation.xml"
id="Login-validation.xml">Login-validation.xml</a>
-</h4>
-
-<p>
- While it is not hard to code data-entry validation into an Action class,
- the framework provides an even easier way to validate input.
-</p>
-
-<p>
- The validation framework is configured through another XML document, the
<strong>
- Login-validation.xml</strong>.
-</p>
-
-<hr/>
-<h5>Validation file for Login Action</h5>
-<pre><code><!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="<strong>username</strong>">
- <field-validator type="<strong>requiredstring</strong>">
- <message key="<strong>error.username.required</strong>"/>
- </field-validator>
- </field>
- <field name="<strong>password</strong>">
- <field-validator type="<strong>requiredstring</strong>">
- <message key="<strong>error.password.required</strong>"/>
- </field-validator>
- </field>
-</validators>
-</code></pre>
-<hr/>
-
-<p>
- You may note that the DTD refers to "XWork".
- <a href="http://www.opensymphony.com/xwork/">
- Open Symphony XWork
- </a> 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.
-</p>
-
-<p>
- The field elements correspond to the ActionForm properties.
- The <strong>username</strong> and <strong>password</strong> 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.
-</p>
-
-<!--
-<p>
- The <strong>password</strong> field (or property) is also required.
- In addition, it must also pass the "maxlength" and "minlength"
- validations.
- Here, the minimum length is three characters and the maximum length is
- sixteen.
- If the length of the password doesn't meet these criteria, a corresponding
- error message is generated.
- Of course, the messages are generated from the MessageResource bundles and
- are easy to localize.
-</p>
--->
-
-<h4><a name="Login.java" id="Login.java">Login Action</a></h4>
-
-<p>
- 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, <strong>MailreaderSupport</strong>.
-</p>
-
-<hr/>
-<h5>Login.java</h5>
-<pre><code>package mailreader2;
-import org.apache.struts.apps.mailreader.dao.User;
-public final class <strong>Login</strong> extends MailreaderSupport {
-public String <strong>execute()</strong> throws ExpiredPasswordException {
- User user = <strong>findUser(getUsername(), getPassword());</strong>
- if (user != null) {
- <strong>setUser(user);</strong>
- }
- if (<strong>hasErrors()</strong>) {
- return INPUT;
- }
- return SUCCESS;
- }
-}</code></pre>
-<hr/>
-
-<p>
- 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.
-</p>
-
-<h4><a name="MailreaderSupport.java"
id="MailreaderSupport.java">MailreaderSupport.java</a></h4>
-
-<p>
- Let's look at the relevant properties and methods from MailreaderSupport
- and another base class, <strong>ActionSupport</strong>, namely
- "getUsername", "getPassword", "findUser", "setUser", and "hasErrors".
-</p>
-
-<p>
- The framework lets you define
- <a href="http://struts.apache.org/primer.html#javabeans">JavaBean
properties</a>
- 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.
-</p>
-
-<p>
- The <strong>Username</strong> and <strong>Password</strong> properties are
nothing fancy,
- just standard JavaBean properties.
-</p>
-
-<hr/>
-<h5>MailreaderSupport.getUsername() and getPassword()</h5>
-<pre><code>private String username = null;
-public String <strong>getUsername()</strong> {
- return this.username;
-}
-public void setUsername(String username) {
- this.username = username;
-}
-
-private String password = null;
-public String <strong>getPassword()</strong> {
- return this.password;
-}
-public void setPassword(String password) {
- this.password = password;
-}</code></pre>
-<hr/>
-
-<p>
- We use these properties to capture the client's credentials,
- and pass them to the more interesting <strong>findUser</strong> method.
-</p>
-
-<hr/>
-<h5>MailreaderSupport.findUser</h5>
-<pre><code>public User <strong>findUser</strong>(String username, String
password)
- throws <strong>ExpiredPasswordException</strong> {
- User user = <strong>getDatabase().findUser(username)</strong>;
- if ((user != null) && !user.getPassword().equals(password)) {
- user = null;
- }
- if (user == null) {
- this.<strong>addFieldError</strong>("password",
getText("error.password.mismatch"));
- }
- return user;
-}</code></pre>
-<hr/>
-
-<p>
- The "findUser" method dips into the MailReader Data Access Object layer,
- which is represented by the <strong>Database</strong> 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!
-</p>
-
-<hr/>
-<h5>Best Practice:</h5>
-<blockquote>
- <p>
- <font class="hint">"Strongly separate data access and business logic
from the rest of
- the application."</font>
- </p>
-</blockquote>
-<hr/>
-
-<p>
- 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 <strong>User property</strong>.
- Although it is still a JavaBean property,
- the User property is not implemented in quite the same way as Username and
Password.
-</p>
-
-<hr/>
-<h5>MailreaderSupport.setUser</h5>
-<pre><code>public User getUser() {
- return (User) <strong>getSession().get(Constants.USER_KEY)</strong>;
-}
-public void setUser(User user) {
- getSession().put(Constants.USER_KEY, user);
-}</code></pre>
-<hr/>
-
-<p>
- Instead of using a field to store the property value,
- "setUser" passes it to a <strong>Session</strong> property.
-</p>
-
-<hr />
-<h5>MailreaderSupport.getSession() and setSession()</h5>
-<pre><code>private Map session;
-public Map <strong>getSession()</strong> {
- return session;
-
-public void <strong>setSession(Map value)</strong> {
- session = value;
-}</code></pre>
-<hr />
-
-<p>
- 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.
-</p>
-
-<p>
- But, when MailreaderSupport is running inside a web application,
- how does it acquire a reference to the servlet session?
-</p>
-
-<p>
- 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.
-</p>
-
-<p>
- The magic that provides the Session property a runtime value is called
- "dependency injection".
- The MailreaderSupport class implements a interface called
<strong>SessionAware</strong>.
- SessionAware is bundled with the framework,
- and it defines a setter for the Session property.
-</p>
-
-<p>
- <code>public void <strong>setSession</strong>(Map session);</code>
-</p>
-
-<p>
- Also bundled with the framework is an object called the
- <strong>ServletConfigInterceptor</strong>.
- If the ServletConfigInterceptor sees that an Action implements the
SessionAware interface,
- it automatically set the session property.
-</p>
-
-<pre><code>if (action instanceof <code>SessionAware</code>) {
- ((SessionAware) action).<code>setSession</code>(context.getSession());
-}</code></pre>
-
-<p>
- The framework uses these "Interceptor" classes to create a <strong>front
controller</strong>
- 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
- <a href="http://struts.apache.org/primer.html#filters">Filters</a>,
- 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.)
-</p>
-
-<p>
- 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.
-</p>
-
-<p>
- Many Interceptors provide a utility or helper functions,
- like setting the session property.
- Others, like the <strong>ValidationInterceptor</strong>,
- can change the workflow of an action.
- Interceptors are key feature of the framework,
- and we will see a few more on the tour.
-</p>
-
-<p>
- If a valid User is not found, or the password doesn't match,
- the "findUser" method invokes the <strong>addFieldError</strong> method to
note the
- problem.
- When "findUser" returns, the Login Action checks for errors,
- and then it returns either INPUT or SUCCESS.
-</p>
-
-<p>
- 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.
-</p>
-
-<p>
- 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.
-</p>
-
-<hr/>
-<h5>Best Practice:</h5>
-<blockquote>
- <p><font class="hint">"Use a base class to define common
functionality."</font></p>
-</blockquote>
-<hr/>
-
-<p>
- But, what happens if Login returns INPUT instead of SUCCESS.
- How does the framework know what to do next?
-</p>
-
-<p>
- To answer that question,
- we need to turn back to the Struts configuration
- and look at how Login is declared.
-</p>
-
-
-<h4><a name="Login.xml" id="Login.xml">Login Configuration</a></h4>
-
-<p>
- 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".
-</p>
-
-<hr/>
-<h5>mailreader-support.xml Login</h5>
-<pre><code><action name="<strong>Login_*</strong>" method="{1}"
class="mailreader2.Login">
- <result name="<strong>input</strong>">/pages/Login.jsp</result>
- <result name="<strong>cancel</strong>"
type="redirect-action">Welcome</result>
- <result type="redirect-action">MainMenu</result>
- <result name="<strong>expired</strong>"
type="chain">ChangePassword</result>
- <<strong>exception-mapping</strong>
- exception="org.apache.struts.apps.mailreader.dao.ExpiredPasswordException"
- result="<strong>expired</strong>"/>
- <interceptor-ref name="<strong>guest</strong>"/>
-</action></code></pre>
-<hr/>
-
-<p>
- You might notice that the name of the Login action element is not "Login"
- but "Login<strong>_*</strong>".
- 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.
-</p>
-
-<p>
- 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.
-</p>
-
-<hr/>
-<h5>struts.properties</h5>
-<pre><code>struts.enable.DynamicMethodInvocation = false</code></pre>
-<hr/>
-
-<p>
- 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".
-</p>
-
-<p>
- 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.
-</p>
-
-<p>
- The second result element is named <strong>cancel</strong>.
- 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.
-</p>
-
-<p>
- The third result has no name,
- so it will be called if the default <strong>success</strong> token is
returned.
- So, if the Login succeeds,
- control will transfer to the MainMenu action.
-</p>
-
-<p>
- 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.
-</p>
-
-<p>
- Just in case any other Exceptions are thrown,
- the MailReader application also defines a global handler.
-</p>
-
-<hr/>
-<h5>mailreader-default.xml exception-mapping</h5>
-<pre><code><global-exception-mappings>
- <exception-mapping
- result="error"
- exception="java.lang.Exception"/>
-</global-exception-mappings></code></pre>
-<hr/>
-
-<p>
- 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.
-</p>
-
-<hr/>
-<h5>Error.jsp</h5>
-<pre><code><%@ 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>
- <strong><s:actionerror /></strong>
- <p>
- <strong><s:property value="%{exception.message}"/></strong>
- </p>
- <hr />
- <h3>Technical Details</h3>
- <p>
- <strong><s:property value="%{exceptionStack}"/></strong>
- </p>
- <jsp:include page="Footer.jsp"/>
- </body>
-</html></code></pre>
-<hr/>
-
-<p>
- The Error page uses <strong>property</strong> tags to expose
- the Exception message and the Exception stack.
-</p>
-
-<p>
- Finally, the Login action specifies an <strong>InterceptorStack</strong>
- named <strong>defaultStack.</strong>
- If you've worked with Struts 2 or WebWork 2 before, that might seem
strange,
- since "defaultStack" is the factory default.
-</p>
-
-<p>
- 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.
-</p>
-
-<hr/>
-<h5>mailreader2.AuthenticationInterceptor</h5>
-<pre><code>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 <strong>AuthenticationInterceptor</strong> implements Interceptor
{
- public void destroy () {}
- public void init() {}
- public String <strong>intercept</strong>(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 (<strong>isAuthenticated</strong>) {
- return actionInvocation.invoke();
- }
- else {
- return Action.LOGIN;
- }
- }
-}</code></pre>
-<hr/>
-
-<p>
- The <strong>AuthenticationInterceptor</strong> 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.
-</p>
-
-<p>
- The MailReader defines three custom Interceptor stacks: "user",
"user-submit",
- and "guest".
-</p>
-
-<hr/>
-<h5>mailreader-default.xml interceptors</h5>
-<pre><code><interceptors>
- <interceptor name="<strong>authentication</strong>"
- class="mailreader2.AuthenticationInterceptor"/>
- <interceptor-stack name="<strong>user</strong>" >
- <interceptor-ref name="authentication" />
- <interceptor-ref name="defaultStack"/>
- </interceptor-stack>
- <interceptor-stack name="<strong>user-submit</strong>" >
- <interceptor-ref name="tokenSession" />
- <interceptor-ref name="user"/>
- </interceptor-stack>
- <interceptor-stack name="<strong>guest</strong>" >
- <interceptor-ref name="defaultStack"/>
- </interceptor-stack>
-</interceptors>
-<<strong>default-interceptor-ref</strong> name="user"/></code></pre>
-<hr/>
-
-<p>
- The <strong>user</strong> stacks require that the client be authenticated.
- In other words, that a User object is present in the session.
- The actions using a <strong>guest</strong> stack can be accessed by any
client.
- The <strong>-submit</strong> versions of each can be used with actions
- with forms, to guard against double submits.
-</p>
-
-<h5>Double Submits</h5>
-
-<p>
- 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.
-</p>
-
-<p>
- 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.
-</p>
-
-<p>
- 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
-</p>
-
-<p>
- 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.
-</p>
-
-<h3><a name="MainMenu" id="MainMenu">MainMenu</a></h3>
-
-<p>
- 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:
-</p>
-
-<ul>
- <li>
- Edit your user registration profile
- </li>
- <li>
- Log off MailReader Demonstration Application
- </li>
-</ul>
-
-<p>
- Let's review the source for the "MainMenu" action mapping,
- and the "MainMenu.jsp".
-</p>
-
-<hr/>
-<h5>Action mapping element for MainMenu</h5>
-<pre><code><action name="MainMenu" class="mailreader2.MailreaderSupport">
- <result>/pages/MainMenu.jsp</result>
- </action></code></pre>
-
-<h5>MainMenu.jsp</h5>
-<pre><code><%@ 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"/> <strong><s:property
- value="user.fullName"/></strong></h3>
- <ul>
- <li><a href="<s:url <strong>action="Registration_input"</strong>
/>">
- <s:text name="mainMenu.registration"/>
- </a>
- </li>
- <li><a href="<s:url <strong>action="Logout"</strong> />">
- <s:text name="mainMenu.logout"/>
- </a>
- </ul>
- </body>
-</html></code></pre>
-<hr/>
-
-<p>
- The source for "MainMenu.jsp" also contains a new tag, <strong>
- property</strong>, which we use to customize the page with the
- "fullName" property of the authenticated user.
-</p>
-
-<p>
- 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.
-</p>
-
-<p>
- 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".
-</p>
-
-<h3><a name="Registration.jsp" id="Registration.jsp">Registration page</a>
-</h3>
-
-<p>
- 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.
-</p>
-
-<p>
- 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.
-</p>
-
-<hr />
-<h5>Registration.jsp - head element</h5>
-<pre><code><head>
- <s:if test="<strong>task=='Create'</strong>">
- <title><s:text name="registration.title.create"/></title>
- </s:if>
- <s:if test="<strong>task=='Edit'</strong>">
- <title><s:text name="registration.title.edit"/></title>
- </s:if>
- <link href="<s:url value="/css/mailreader.css"/>" rel="stylesheet"
- type="text/css"/>
-</head></code></pre>
-<hr />
-
-<p>
- 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.
-</p>
-
-<hr/>
-<h5>Note:</h5>
-<blockquote>
- <p><font class="hint">
- <strong>Presention Logic</strong> -
- 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.
- </font></p>
-</blockquote>
-<hr/>
-
-<p>
- 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.
-</p>
-
-<hr/>
-<h5></h5>
-<pre><code><s:if test=<strong>"task == 'Edit'"</strong>>
- <div align="center">
- <h3><s:text name="heading.subscriptions"/></h3>
- </div>
- <!-- ... -->
- </s:if>
-<jsp:include page="Footer.jsp"/>
-</body></html></code></pre>
-<hr/>
-
-<p>
- Otherwise, the page contains just the top portion --
- a data-entry form for managing the user's registration.
-</p>
-
-<h4><a name="iterator" id="iterator">iterator</a></h4>
-
-<p>
- 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
<strong>iterator</strong>
- tag to display the User's Subscriptions.
-</p>
-
-<p>
- 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.
- </p>
-
-<hr/>
-<h5>Using iterator to list the Subscriptions</h5>
-<pre><code><s:iterator value="<strong>user.subscriptions</strong>">
- <tr>
- <td align="left">
- <s:property value="<strong>host</strong>"/>
- </td>
- <td align="left">
- <s:property value="<strong>username</strong>"/>
- </td>
- <td align="center">
- <s:property value="<strong>type</strong>"/>
- </td>
- <td align="center">
- <s:property value="<strong>autoConnect</strong>"/>
- </td>
- <td align="center">
- <a href="<s:url
action="<strong>Subscription_delete</strong>"><s:param
name="<strong>host</strong>" value="host"/></s:url>">
- <s:text name="registration.deleteSubscription"/>
- </a>
- <a href="<s:url
action="<strong>Subscription_edit</strong>"><s:param
name="<strong>host</strong>" value="host"/></s:url>">
- <s:text name="registration.editSubscription"/>
- </a>
- </td>
- </tr>
-</s:iterator></code></pre>
-<hr/>
-
-<p>
- When the iterator renders, it generates a list of Subscriptions for the
current User.
-</p>
-
-<hr />
-
- <div align="center">
- <h3>Current Subscriptions</h3>
- </div>
-
- <table border="1" width="100%">
- <tr>
- <th align="center" width="30%">
- Host Name
- </th>
- <th align="center" width="25%">
- User Name
- </th>
-
- <th align="center" width="10%">
- Server Type
- </th>
- <th align="center" width="10%">
- Auto
- </th>
- <th align="center" width="15%">
- Action
- </th>
- </tr>
- <tr>
- <td align="left">
- mail.hotmail.com
- </td>
- <td align="left">
- user1234
- </td>
- <td align="center">
- pop3
- </td>
-
- <td align="center">
- false
- </td>
- <td align="center">
- <a
href="/struts2-mailreader/Subscription_delete.do?host=mail.hotmail.com">
- Delete
- </a>
-
- <a
href="/struts2-mailreader/Subscription_edit.do?host=mail.hotmail.com">
- Edit
- </a>
- </td>
- </tr>
- <tr>
- <td align="left">
- mail.yahoo.com
- </td>
- <td align="left">
- jquser
- </td>
- <td align="center">
- imap
- </td>
- <td align="center">
- false
- </td>
- <td align="center">
- <a
href="/struts2-mailreader/Subscription_delete.do?host=mail.yahoo.com">
- Delete
- </a>
-
- <a
href="/struts2-mailreader/Subscription_edit.do?host=mail.yahoo.com">
- Edit
- </a>
- </td>
- </tr>
- </table>
- <a href="/struts2-mailreader/Subscription_input.do">Add</a>
-
-<hr />
-
- <p>
- Now look back at the code used to generate this block.
- </p>
- <p>
- Notice anything nifty?
- </p>
- <p>
- How about that the markup between the iterator tag is
- actually <em>simpler</em> than the markup that we would use to render
one row of the
- table?
- </p>
- <p>
- 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.
- </p>
- <p>
- Nice trick!
- </p>
-
-<p>
- The secret to this magic is the <strong>value stack</strong>.
- 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.
-</p>
-
-<p>
- 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.
-</p>
-
-<p>
- 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.)
-</p>
-
-<p>
- 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.
-</p>
-
-<p>
- 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.
-</p>
-
-<p>
- 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.
-</p>
-
-<p>
- 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.
-</p>
-
-<p>
- The Subscription list uses another new tag: the <strong>param</strong> 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.
-</p>
-
-<p>
- 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.
-</p>
-
-<pre><code>pager.legend = Displaying {current} of {count} items matching
{criteria}.
-...
-<s:text name="pager.legend">
- <s:<strong>param</strong> name="current" value="42" />
- <s:<strong>param</strong> name="count" value="314" />
- <s:<strong>param</strong> name="criteria" value="Life, the Universe,
and Everything" />
-</s:text></code></pre>
-
-<p>
- In the case of an "url" tag,
- we can use "param" to create the query string.
- A statement like this:
-</p>
-
-<pre><code>
- <s:url action="Subscription_edit"><s:param name="<strong>host"
value="host</strong>"/></s:url>">
-</code></pre>
-
-<p>
- can render a hyperlink like this:
-</p>
-
-<pre><code>
- <a
href="/struts2-mailreader/Subscription_edit.do?<strong>host=mail.yahoo.com</strong>">Edit</a>
-</code></pre>
-
-<!--
-<p>
- At the foot of the Register page is a link for adding a subscription.
- Let's wind up the tour by following the Add link and then logging off.
- Like the link for creating a Registration, Add points to an "Edit" action,
- namely "EditSubscription".
-</p>
--->
-
-<p>
- If a hyperlink needs more parameters,
- you can use "param" to add as many parameters as needed.
-</p>
-
-<h3>
- <a name="Subscription" id="Subscription">Subscription</a>
-</h3>
-
-<p>
- 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.
-</p>
-
-<hr />
-<h5>mailreader-support.xml Subscription element</h5>
-<pre><code><action name="Subscription_*" method="{1}"
class="mailreader2.Subscription">
- <result name="input">/pages/Subscription.jsp</result>
- <result type="redirect-action">Registration_input</result>
-</action></code></pre>
-<hr />
-
-<p>
- The Edit link specified the Subscription action,
- but also includes the qualifier <strong>_edit</strong>.
- 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 <strong>alias</strong> methods.
-</p>
-
-<hr />
-<h5>Subscription edit alias</h5>
-<pre><code>public String <strong>edit()</strong> {
- <strong>setTask(Constants.EDIT);</strong>>
- return find();
-}
-
-public String find() {
- org.apache.struts.apps.mailreader.dao.Subscription
- sub = findSubscription();
- if (sub == null) {
- return ERROR;
- }
- <strong>setSubscription(sub);</strong>
- return INPUT;
-}</code></pre>
-<hr />
-
-<p>
- 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.
-</p>
-
-<p>
- 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.
-</p>
-
-<p>
- The business logic for the "edit" alias is a simple wrapper
- around the MailReader DAO classes.
-</p>
-
-<hr />
-<h5>MailreaderSupport findSubscription()</h5>
-<pre><code>public Subscription <strong>findSubscription()</strong> {
- return findSubscription(getHost());
-}
-
-public Subscription findSubscription(String host) {
- Subscription subscription;
- subscription = <strong>getUser().findSubscription(host);</strong>
- return subscription;
-}</code></pre>
-<hr />
-
-<p>
- 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.
-</p>
-
-<p>
- 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.
-</p>
-
-<p>
- As a final layer of defense, we also configured a validator for
Subscription,
- to ensure that we are passed a Host parameter.
-</p>
-
-<hr />
-<h5>Subscription-validation.xml</h5>
-<pre><code><!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="<strong>host</strong>">
- <field-validator type="<strong>requiredstring</strong>">
- <message key="error.host.required"/>
- </field-validator>
- </field>
-</validators></code></pre>
-<hr />
-
-<p>
- By keeping routine safety precautions out of the Action class,
- the all-important Action becomes smaller and easier to maintain.
-</p>
-
-<p>
- After setting the relevent Subscription object to the Subscription
property,
- the framework transfers control to the (you guessed it) Subscription page.
-</p>
-
-<hr />
-<h5>Subscription.jsp</h5>
-<pre><code><%@ 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 <strong>action="Subscription_save"</strong> validate="true">
- <strong><s:token /></strong>
- <strong><s:hidden name="task"/></strong>
- <strong><s:label key="username" name="user.username"/></strong>
-
- <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"/>
- <strong><s:select key="subscription.type" list="types"/></strong>
- <strong><s:checkbox key="subscription.autoConnect"/></strong>
- <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></code></pre>
-<hr />
-
-<p>
- As before, we'll discuss the tags and attributes that are new to this page:
- "token", "hidden", "label", "select", and "checkbox".
-</p>
-
-<p>
- The <strong>token</strong> 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.
-</p>
-
-<p>
- The <strong>hidden</strong> 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.
-</p>
-
-<p>
- The <strong>label</strong> 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.
-</p>
-
-<p>
- Saving the best for last, the Subscription form utilizes two more
interesting
- tags, "select" and "checkbox".
-</p>
-
-<p>
- Unsurprisingly, the <strong>select</strong> tag renders a select control,
- but the tag does so without requiring a lot of markup or redtape.
-</p>
-
-<pre><code><s:select key="subscription.type" <strong>list="types"</strong>
/>
-</code></pre>
-
-<p>
- 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".
-</p>
-
-<hr />
-<h5>Subscription-validation.xml</h5>
-<pre><code>public class <strong>Subscription</strong> extends MailreaderSupport
- <strong>implements Preparable</strong> {
-
- private Map types = null;
- public Map <strong>getTypes()</strong> {
- return types;
- }
-
- public void <strong>prepare()</strong> {
- Map m = new LinkedHashMap();
- m.put("imap", "IMAP Protocol");
- m.put("pop3", "POP3 Protocol");
- types = m;
- setHost(getSubscriptionHost());
- }
-
- // ... </code></pre>
-<hr />
-
-<p>
- The default Interceptor stack includes the
<strong>PrepareInterceptor</strong>,
- which observes the Preparable interface.
-</p>
-
-<hr />
-<h5>PrepareInterceptor</h5>
-<pre><code>public class <strong>PrepareInterceptor</strong> extends
AroundInterceptor {
-
- protected void after(ActionInvocation dispatcher, String result) throws
Exception {
- }
-
- protected void before(ActionInvocation invocation) throws Exception {
- Object action = invocation.getAction();
- <strong>if (action instanceof Preparable) {
- ((Preparable) action).prepare();</strong>
- }
- }
-}</code></pre>
-
-<p>
- 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.
-</p>
-
-<h4>
- <a name="Subscription.java" id="Subscription.java">Subscription.java</a>
-</h4>
-
-<p>
- 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.
-</p>
-
-<p>
- 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 <em>anything</em>.
- 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.
-</p>
-
-<p>
- In Struts 1,
- we use the <code>reset</code> 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.
-</p>
-
-<p>
- 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.
-</p>
-
-<hr />
-<h5>Subscription-Subscription_save-validation.xml</h5>
-<pre><code><!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="<strong>host</strong>">
- <field-validator type="<strong>requiredstring</strong>">
- <message key="error.host.required"/>
- </field-validator>
- </field>
-</validators></code></pre>
-<hr />
-
-<p>
- 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.
-</p>
-
-<p>
- If validation succeeds, the <code>save</code> method of Subscription will
fire.
-</p>
-
-<hr />
-<h5>Subscription</h5>
-
-<pre><code>public String <strong>save</strong>() throws Exception {
-
- if (Constants.DELETE.equals(getTask())) {
- <strong>removeSubscription</strong>();
- }
-
- if (Constants.CREATE.equals(getTask())) {
- <strong>copySubscription(</strong>getHost());
- }
-
- saveUser();
- return SUCCESS;
-}</code></pre>
-<hr />
-
-<p>
- The <strong>save</strong> method uses the Task property to handle
- the special cases of deleting and creating,
- and then updates the state of the User object.
-</p>
-
-<p>
- The <strong>removeSubscription</strong> method calls the DAO facade,
- and then updates the application state.
-</p>
-
-<hr />
-<h5>removeSubscription</h5>
-<pre><code>public void <strong>removeSubscription</strong>() throws Exception {
- getUser().removeSubscription(getSubscription());
- getSession().remove(Constants.SUBSCRIPTION_KEY);
-}</code></pre>
-<hr />
-
-<p>
- The <strong>copySubscription</strong> 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.
-</p>
-
-<p>
- 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.
-</p>
-
-<p>
- Another workaround is to declare only the immutable properties on the
Action,
- and then use what we can from the domain object.
-</p>
-
-<p>
- 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.
-</p>
-
-<p>
- 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.
-</p>
-
-<hr />
-<h5>copySubscription</h5>
-<pre><code>public void <strong>copySubscription</strong>(String host) {
- Subscription input = getSubscription();
- Subscription sub = createSubscription(host);
- if (null != sub) {
- <strong>BeanUtils.setValues</strong>(sub, input, null);
- setSubscription(sub);
- setHost(sub.getHost());
- }
-}</code></pre>
-<hr />
-
-<p>
- 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.
-</p>
-
-<h4>Subscription Submit</h4>
-
-<p>
- 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.
-</p>
-
-<p>
- To add the double-submit guard, we can change the actions default
processing
- stack to <code>user-submit</code>.
- 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.
-</p>
-
-<hr />
-<h5>mailreader-support.xml</h5>
-<pre><code><!-- ... -->
-</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>
-}</code></pre>
-<hr />
-
-<p>
- Aftering a successful save,
- the Subscription Action will return "success",
- and the framework will redirect us back to Registration input.
-</p>
-
-<h3>Summary</h3>
-<p>
- 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
- <a
href="http://svn.apache.org/viewvc/struts/struts2/trunk/apps/mailreader/">
- available online</a>
- and in the distribution.
-</p>
-
-<p>
- Enjoy!
-</p>
-</blockquote>
-</body>
-</html>