| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" |
| "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> |
| <!-- |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| --> |
| |
| <html xmlns="http://www.w3.org/1999/xhtml"> |
| <head> |
| <meta name="generator" |
| content="HTML Tidy for Windows (vers 1st July 2003), see www.w3.org"/> |
| <meta http-equiv="Content-Type" content="text/html; charset=us-ascii"/> |
| <link rel="stylesheet" type="text/css" href="base.css"/> |
| |
| <title>A Walking Tour of the Struts MailReader Demonstration |
| Application</title> |
| </head> |
| |
| <body> |
| <blockquote> |
| <h2>A Walking Tour of the Struts MailReader Demonstration Application</h2> |
| |
| <p> |
| <i> |
| This article is meant to introduce a new user to Struts by "walking |
| through" an |
| application. |
| See the <a href="http://struts.apache.org/">Struts Users Guide and |
| Strut's API</a> |
| for more documentation. |
| </i> |
| </p> |
| |
| <p> |
| <i> |
| The MailReader application is based on the 1.3.0 build of Struts. |
| To follow along, you should install the MailReader application on your |
| own |
| development workstation (e.g. localhost). |
| </i> |
| </p> |
| |
| <p> |
| <i> |
| This article 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/struts-action/userGuide/preface.html"> |
| Preface to the Struts User Guide</a>. |
| </i> |
| </p> |
| |
| <hr/> |
| |
| <ul> |
| <li> |
| <a href="#index.jsp">index.jsp</a> |
| |
| <ul> |
| <li><a href="#web.xml">web.xml and |
| ApplicationResources.properties</a></li> |
| |
| <li><a href="#DatabaseServlet.java">DatabaseServlet.java</a></li> |
| </ul> |
| </li> |
| |
| <li> |
| <a href="#Logon.jsp">Logon.jsp</a> |
| |
| <ul> |
| <li><a href="#struts-config.xml">struts-config.xml</a></li> |
| |
| <li><a href="#LogonForm">LogonForm</a></li> |
| |
| <li><a href="#BaseForm">BaseForm</a></li> |
| |
| <li><a href="#LogonAction.java">LogonAction.java</a></li> |
| |
| <li><a href="#struts-config.xml/2">struts-config.xml 2</a></li> |
| </ul> |
| </li> |
| |
| <li> |
| <a href="#MainMenu.jsp">MainMenu.jsp</a> |
| |
| <ul> |
| <li><a href=""></a></li> |
| </ul> |
| </li> |
| |
| <li> |
| <a href="#MainMenu.jsp">EditRegistration.do</a> |
| <ul> |
| <li><a href="#RegistrationAction.java">RegistrationAction.java</a> |
| </li> |
| </ul> |
| <li> |
| <a href="#Registeration.jsp">Registration.jsp</a> |
| |
| <ul> |
| <li><a href="#RegistrationForm">RegistrationForm</a></li> |
| |
| <li><a href="#RegistrationAction.java">RegistrationAction.java</a> |
| </li> |
| |
| <li><a href="#Edit-SubscriptionAction.java">SubscriptionAction.java</a> |
| </li> |
| </ul> |
| </li> |
| |
| <li> |
| <a href="#Subcription.jsp">Subscription.jsp</a> |
| |
| <ul> |
| <li><a href="#SubscriptionForm">SubscriptionForm</a></li> |
| |
| <li><a href="#Save-SubscriptionAction.java">SubscriptionAction</a> |
| </li> |
| </ul> |
| </li> |
| |
| <li><a href="#Summary">Summary</a></li> |
| </ul> |
| <hr/> |
| |
| <p> |
| The Struts App subproject includes four applications: struts-blank, |
| struts-cookbook, struts-examples, and |
| struts-mailreader. |
| This document walks through struts-mailreader, also known as the |
| "MailReader Demonstration Application". |
| </p> |
| |
| <p> |
| The premise of the MailReader is that it is the first iteration of a |
| portal application. |
| This version allows users to register themselves 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 document walks through 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://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/struts-action/userGuide/preface.html"> |
| Preface to the Struts Action User Guide</a> for more about |
| authentification technologies.) |
| </font></p> |
| </blockquote> |
| <hr/> |
| |
| <p> |
| The walkthrough starts with how the initial welcome page is displayed, and |
| then steps through logging in, |
| adding and editing subscriptions, and creating a new registration. |
| </p> |
| |
| <h3><a name="howdy" id="howdy">Howdy</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 |
| welcome page is used by default. |
| </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 Struts Action Framework plugs into a web application via a controller |
| servlet. |
| Like any servlet, the Action servlet 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 MailReader Application</display-name> |
| |
| <!-- Action Servlet Configuration --> |
| <servlet> |
| <servlet-name>action</servlet-name> |
| <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> |
| <init-param> |
| <param-name>config</param-name> |
| <param-value>/WEB-INF/struts-config.xml</param-value> |
| </init-param> |
| <load-on-startup>1</load-on-startup> |
| </servlet> |
| |
| <!-- Servlet Mapping --> |
| <servlet-mapping> |
| <servlet-name>action</servlet-name> |
| <url-pattern>*.do</url-pattern> |
| </servlet-mapping> |
| |
| <!-- <strong>The Welcome File List</strong> --> |
| <welcome-file-list> |
| <welcome-file>index.jsp</welcome-file> |
| </welcome-file-list> |
| |
| <!-- The default error page --> |
| <error-page> |
| <exception-type>java.lang.Exception</exception-type> |
| <location>/Error.jsp</location> |
| </error-page> |
| |
| </web-app></code></pre> |
| <hr/> |
| |
| <p> |
| Among other things, |
| the web.xml specifies the "Welcome File List" for an application. |
| When a web address refers to a directory rather than an individual file, |
| the container consults the Welcome File List for the name of a page to |
| open by default. |
| </p> |
| |
| <p> |
| Albeit, most Struts applications do not refer to physical pages, |
| but to "virtual pages" called <i>actions</i>. |
| The actions are listed in a configuration file. |
| Actions contain code that we want to be run before a page is displayed. |
| An accepted practice in the Struts Action community is to never link |
| directly to server pages, |
| but only to Struts actions. |
| By linking to actions, developers can "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> |
| 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 Struts best practice of navigating through actions |
| rather than pages? |
| </p> |
| |
| <p> |
| One solution is to use a server page to "bootstrap" a Struts action. |
| A Java web application recognizes the idea of "forwarding" from one page |
| to another page (or action). |
| We can register the usual "index.jsp" as the welcome page and have it |
| forward to a "Welcome" action. |
| </p> |
| |
| <hr/> |
| <h5>MailReader's index.jsp</h5> |
| <pre><code><%@ taglib uri="http://struts.apache.org/tags-logic" |
| prefix="logic" %> |
| <logic:redirect <strong>action="/Welcome"</strong>/></code> |
| </pre> |
| <hr/> |
| |
| <p> |
| At the top of the page, we import the "struts-logic" JSP tag library. |
| (For more about the Struts JSP tags, see the <a |
| href="http://struts.apache.org/struts-taglib/">Struts Taglib |
| website</a>.) |
| The page itself consists of a single tag that redirects to the "Welcome" |
| action. |
| The tag inserts the actual web address for the redirect when the page is |
| rendered. |
| But, where does the tag find the actual address to insert? |
| </p> |
| |
| <h4><a name="welcome.do" id="welcome.do">Welcome.do</a></h4> |
| |
| <p> |
| The list of actions, along with other components, |
| are registered through one or more Struts Action configuration files. |
| The configuration files are written as XML documents and processed when |
| the application starts. |
| 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> <!-- Display welcome page --> |
| <action |
| path="/Welcome" |
| <strong>forward="/Welcome.jsp" /></strong></code></pre> |
| <hr/> |
| |
| <p> |
| If someone asked for the Welcome action ("/Welcome.do"), the welcome.jsp |
| page would be displayed in return. |
| </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> <!-- Display welcome page --> |
| <action |
| path="/Welcome" |
| <strong> |
| type="org.apache.struts.apps.mailreader.actions.WelcomeAction"</strong> |
| > |
| <forward |
| name="Success" |
| path="/Welcome.jsp" /> |
| </action></code></pre> |
| <hr/> |
| |
| <p> |
| Here, the <strong>WelcomeAction</strong> Java class executes whenever |
| someone asks for the Welcome action. |
| As it completes, the Action class can select which "result" is displayed. |
| One available result is "Success". |
| Another available result, defined at a global scope, is "Failure". |
| But the Action class doesn't need to know the path to the result, or even |
| if they are server pages. |
| The Action class can select the appropriate result just by using names |
| like "Success" or "Failure". |
| </p> |
| |
| <h4><a name="welcomeaction" id="welcomeaction">WelcomeAction</a></h4> |
| |
| <p> |
| OK ... but why would a WelcomeAction want to choose between Success and |
| Failure? |
| </p> |
| |
| <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 page, the class checks to see if the |
| database is available. |
| The MailReader is also an internationalized application. |
| So, the WelcomeAction checks to see if the message resources are available |
| too. |
| If both resources are available, the class forwards to the "Success" path. |
| Otherwise, it forwards to the "Failure" path so that the appropriate error |
| messages can be displayed. |
| </p> |
| |
| <h4><a name="global-forwards" id="global-forwards">global-forwards</a></h4> |
| |
| <hr/> |
| <h5>MailReader's global-forward element</h5> |
| <pre><code> <!-- ==== Global Forward Definitions ==== --> |
| <strong><global-forwards></strong> |
| <forward |
| name="Logoff" |
| path="/Logoff.do"/> |
| <forward |
| name="Logon" |
| path="/Logon.do"/> |
| <forward |
| name="Failure" |
| path="/Error.jsp" /> |
| </global-forwards></code></pre> |
| <hr/> |
| |
| <p> |
| As mentioned, "Failure" 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 "Failure" result as a Global Forward, so that |
| any action can use it. |
| </p> |
| |
| <h4><a name="MemoryDatabasePlugIn.java" id="MemoryDatabasePlugIn.java">MemoryDatabasePlugIn.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> |
| One section of the Struts configuration is devoted to "PlugIns". |
| When a Struts application loads, it also loads whatever PlugIns are |
| specified in its configuration. |
| The PlugIn interface is quite simple. |
| </p> |
| |
| <hr/> |
| <h5>org.apache.struts.action.PlugIn</h5> |
| <pre><code>public interface PlugIn { |
| |
| void <strong>destroy</strong>(); |
| |
| void <strong>init</strong>(ActionServlet servlet, ModuleConfig config) |
| throws ServletException; |
| }</code></pre> |
| <hr/> |
| |
| <p> |
| You can use PlugIns to do anything that might need to be done when your |
| application loads. |
| The PlugIn is also notified when the application shuts down, so you can |
| release any allocated resources. |
| </p> |
| <hr/> |
| <h5>The Database PlugIn element</h5> |
| <pre><code> <plug-in |
| className="org.apache.struts.apps.mailreader.plugin.<strong> |
| MemoryDatabasePlugIn</strong>"> |
| <set-property |
| property="pathname" |
| value="/WEB-INF/database.xml"/> |
| </plug-in> |
| </code></pre> |
| <hr/> |
| |
| <p> |
| By default, the MailReader application 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 ... |
| and 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 WEB-INF, 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="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></code> |
| </pre> |
| <hr/> |
| |
| <p> |
| The "seed" user element creates a registration record for "John Q. User", |
| with the detail for his hotmail account (or "subscription"). |
| </p> |
| |
| <h4><a name="MessageResources.properties" id="MessageResources.properties">MessageResources.properties</a> |
| </h4> |
| |
| <p> |
| Another section of the Struts configuration loads the message resources |
| for the application. |
| If you change a message in the resource, and then reload the application, |
| the change will appear throughout the application. |
| If you provide message resources for additional locales, you can |
| internationalize your application. |
| </p> |
| |
| <hr/> |
| <h5>The MailReader message-resources element</h5> |
| <pre><code> <message-resources |
| parameter="org.apache.struts.apps.mailreader.resources.ApplicationResources"/></code> |
| </pre> |
| <hr/> |
| |
| <p> |
| The "ApplicationResources" parameter refers to a standard properties text |
| file. |
| The resource might be embedded in a JAR or kept under the WEB-INF/classes |
| folder. |
| MailReader uses the classes folder, which means its working copy of the |
| ApplicationResources can be found at |
| <code> |
| ./WEB-INF/classes/org/apache./struts/apps/mailreader/resources/ApplicationResources"</code> |
| </p> |
| |
| <p> |
| The first time a page needs one of the resources, Struts Action loads and |
| caches the properties file, |
| so that the framework does not need to read the text file a second time. |
| </p> |
| <hr/> |
| <h5>ApplicationResource entries used by the Welcome page</h5> |
| <pre><code><strong>index.heading=</strong>MailReader Demonstration Application |
| Options |
| <strong>index.logon=</strong>Log on to the MailReader Demonstration |
| Application |
| <strong>index.registration=</strong>Register with the MailReader |
| Demonstration Application |
| <strong>index.title=</strong>MailReader Demonstration Application |
| <strong>index.tour=</strong>A Walking Tour of the MailReader Demonstration |
| Application</code></pre> |
| <hr/> |
| |
| <p> |
| The MailReader application uses a second set of message resources for |
| non-text elements. |
| </p> |
| |
| <hr/> |
| <h5>The MailReader <em>alternate</em> message-resources element</h5> |
| <pre><code> <message-resources |
| parameter="org.apache.struts.apps.mailreader.resources.AlternateApplicationResources" |
| key="alternate"/></code></pre> |
| <hr/> |
| |
| <p> |
| The "key" element can be used to access this resource bundle rather than |
| the default bundle. |
| </p> |
| |
| <h3><a name="welcome.jsp" id="welcome.jsp">Welcome page</a></h3> |
| |
| <p> |
| After confirming that the necessary resources exist, the WelcomeAction |
| forwards to the Welcome page. |
| </p> |
| <hr/> |
| <h5>Welcome.jsp</h5> |
| <pre><code><%@ page contentType="text/html;charset=UTF-8" language="java" %> |
| <%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %> |
| <%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %> |
| |
| <html> |
| <head> |
| <title><<strong>bean:message</strong> key="index.title"/></title> |
| <link rel="stylesheet" type="text/css" href="base.css" /> |
| </head> |
| |
| <h3><bean:message key="index.heading"/></h3> |
| <ul> |
| <li><<strong>html:link</strong> action="/EditRegistration"> |
| <bean:message key="index.registration"/></html:link></li> |
| <li><html:link action="/Logon"><bean:message |
| key="index.logon"/></html:link></li> |
| </ul> |
| |
| <h3>Change Language</h3> |
| <ul> |
| <li><html:link action="/Locale?language=en">English</html:link></li> |
| <li><html:link action="/Locale?language=ja" |
| useLocalEncoding="true">Japanese</html:link></li> |
| <li><html:link action="/Locale?language=ru" |
| useLocalEncoding="true">Russian</html:link></li> |
| </ul> |
| |
| <hr /> |
| |
| <p><<strong>html:img</strong> bundle="alternate" |
| pageKey="struts.logo.path" altKey="struts.logo.alt"/></p> |
| |
| <p><html:link action="/Tour"><bean:message |
| key="index.tour"/></html:link></p> |
| |
| </body> |
| </html></code></pre> |
| <hr/> |
| |
| <p> |
| At the top of the Welcome page, there are several directives that load the |
| Struts 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: "bean:message", |
| "html:link", and "html:img". |
| </p> |
| |
| <p> |
| The <strong>bean:message</strong> tag inserts a message from the |
| MessageResources we mentioned in the last section. |
| The MailReader comes with support for three locales: English (the |
| default), Russian, and Japanese. |
| If the Struts Action locale setting is changed for a user, |
| the bean:message tag will render messages from that locale's property |
| bundle instead. |
| </p> |
| |
| <p> |
| The <strong>html:link</strong> tag does double duty. |
| First, you can refer to an action or forward stored in the Struts |
| configuration, |
| and the tag will insert the corresponding path when the page is rendered. |
| This makes it easy to "rewire" an application without touching all the |
| pages. |
| Second, the link tag will "URL encode" the hyperlink to maintain the |
| client session. |
| Your application can 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>html:img</strong> tag renders an img tag. |
| When necessary, the src URI is encoded as it is with the link tag. |
| In this case, the tag inserts the src path from the "alternate" |
| MessageResource bundle, |
| along with the text for the alt element. |
| </p> |
| |
| <p> |
| In the span of a single request for the Welcome page, Struts 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 login in (if you have |
| already registered). |
| Let's follow the Login link first. |
| </p> |
| |
| <h3><a name="Logon.jsp" id="Logon.jsp">Logon Page</a></h3> |
| |
| <p> |
| If you choose the Logon link, and all goes well, the Logon action forwards |
| control to the Logon.jsp page. |
| The Logon page displays a form that accepts a username and password. |
| You can use the default username and password to logon (user and pass) if |
| you like. |
| </p> |
| |
| <p> |
| Note that both the username and password are case sensitive. |
| Better yet, try omitting or misspelling the username and password in |
| various combinations to |
| see how the application reacts. |
| </p> |
| |
| <hr/> |
| <h5>Login.jsp</h5> |
| <pre><code><%@ page contentType="text/html;charset=UTF-8" language="java" %> |
| <%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %> |
| <%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %> |
| |
| <html:xhtml/> |
| <html> |
| <head> |
| <title><bean:message key="logon.title"/></title> |
| </head> |
| |
| <<strong>html:errors</strong>/> |
| |
| <<strong>html:form</strong> action="/SubmitLogon" focus="username" |
| onsubmit="return validateLogonForm(this);"> |
| <table border="0" width="100%"> |
| |
| <tr> |
| <th align="right"> |
| <bean:message key="prompt.username"/>: |
| </th> |
| <td align="left"> |
| <<strong>html:text</strong> property="username" size="16" |
| maxlength="18"/> |
| </td> |
| </tr> |
| |
| <tr> |
| <th align="right"> |
| <bean:message key="prompt.password" bundle="alternate"/>: |
| </th> |
| <td align="left"> |
| <<strong>html:password</strong> property="password" size="16" |
| maxlength="18" |
| redisplay="false"/> |
| </td> |
| </tr> |
| |
| <tr> |
| <td align="right"> |
| <<strong>html:submit</strong> property="Submit" value="Submit"/> |
| </td> |
| <td align="left"> |
| <<strong>html:reset</strong>/> |
| </td> |
| </tr> |
| |
| </table> |
| |
| </html:form> |
| |
| <<strong>html:javascript</strong> formName="LogonForm" |
| dynamicJavascript="true" |
| staticJavascript="false"/> |
| <script language="Javascript1.1" src="StaticJavascript.jsp"></script> |
| |
| <jsp:include page="Footer.jsp" /> |
| </body> |
| </html></code> |
| </pre> |
| <hr/> |
| |
| <p> |
| We saw some of these tags on the Welcome page. Let's focus on the new |
| tags. |
| </p> |
| |
| <p> |
| The first new tag on the logon page is <strong>html:errors</strong>. |
| The credentials you entered are validated and processed by a "LogonAction" |
| class. |
| If the credentials are incorrect, |
| the LogonAction posts an appropriate error message and forwards back to |
| the input page. |
| If the html:errors tag sees that one or more messages were posted, the tag |
| ouputs the messages to the page. |
| The text of the messages can be specified in the MessageResource bundle, |
| making them easy to localize. |
| </p> |
| |
| <p> |
| The second new tag is <strong>html:form</strong>. |
| This tag renders a html form tag. |
| The "action" attribute tells the tag to use "SubmitLogon.do" for the |
| form's action. |
| The "focus" attribute tells the tag to generate a little Javascript into |
| the form that |
| sets its focus to the "username" field. |
| The "onsubmit" attribute tells the form to run a Javascript when the form |
| is submitted. |
| (Just like the corresponding attribute on the standard form tag.) |
| </p> |
| |
| <p> |
| Within the html:form tag, |
| we see five more new tags: "html:text", "html:password", "html:submit", |
| "html:reset", and "html:javascript". |
| </p> |
| |
| <p> |
| The <strong>html:text</strong> tag renders a "input type=text" tag. |
| The "property" attribute becomes the input tag's "name" attribute. |
| </p> |
| |
| <p> |
| The <strong>html:password</strong> tag renders a "input type=password" |
| tag. |
| The "redisplay" attribute tell the tag not to render the password back |
| into the file, if the submit fails. |
| The <strong>html:submit</strong> and <strong>html:reset</strong> tags |
| render buttons of the corresponding types. |
| </p> |
| |
| <p> |
| Following the form is a <strong>html:javascript</strong> tag. |
| This tag works with the Struts Validator component to generate a |
| JavaScript that can validate input |
| before it is submitted to the LogonAction. |
| </p> |
| |
| <hr/> |
| <h5>Tip:</h5> |
| <blockquote> |
| <p><font class="hint"> |
| Most of these tags have many more options than the ones we use in this |
| application. |
| For the complete documentation for each tag, |
| see the Tag Developer Guides in the Struts Taglib documentation |
| bundle. |
| </font></p> |
| </blockquote> |
| <hr/> |
| |
| <p> |
| But, how do these tags know so much? |
| How does the Javascript tag know what scripts to write? |
| How do the text and password tags know what values to redisplay? |
| </p> |
| |
| <p> |
| For the answers, we need to turn back to the Struts Action Framework |
| configuration files, |
| "struts-config.xml" and "validation.xml". |
| </p> |
| |
| <h4><a name="struts-config.xml" id="struts-config.xml">struts-config.xml</a> |
| </h4> |
| |
| <p> |
| The Struts configuration file is parsed by the ActionServlet when the |
| application first load. |
| </p> |
| |
| <h5>web.xml -- Action Servlet Configuration</h5> |
| <pre><code> |
| <servlet> |
| <servlet-name>action</servlet-name> |
| <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> |
| <init-param> |
| <param-name>config</param-name> |
| <param-value><strong>/WEB-INF/struts-config.xml</strong></param-value> |
| </init-param> |
| <load-on-startup>1</load-on-startup> |
| </servlet> |
| </code></pre> |
| |
| <hr/> |
| <h5>The SubmitLogon action element</h5> |
| <pre><code> <!-- Process a user logon --> |
| <action |
| <strong>path="/SubmitLogon"</strong> |
| type="org.apache.struts.apps.mailreader.actions.LogonAction" |
| <strong>name</strong>="LogonForm" |
| <strong>scope</strong>="request" |
| <strong>validate</strong>="request" |
| <strong>input</strong>="Logon"> |
| <<strong>exception</strong> |
| key="expired.password" |
| type="org.apache.struts.apps.mailreader.dao.ExpiredPasswordException" |
| path="/ChangePassword.jsp"/> |
| <forward |
| name="Success" |
| path="/MainMenu.do"/> |
| </action></code></pre> |
| <hr/> |
| |
| <p> |
| We saw the path and type attributes in the Welcome action. |
| Let's look at the new attributes: "name", "scope", "input", and |
| "exception". |
| </p> |
| |
| <h4>The "name" attribute</h4> |
| |
| <p> |
| The <strong>name</strong> attribute specifies something Struts Action |
| calls an "ActionForm". |
| The ActionForm buffers input from a form and delivers it to an Action |
| class as an object. |
| The ActionForm can also be used to validate input. |
| If validation fails, the tags can rewrite the input values from the |
| ActionForm. |
| </p> |
| |
| <p> |
| The ActionForms are defined in the "form-beans" section of struts-config. |
| </p> |
| |
| <p> |
| ActionForms can be "conventional" or "dynamic". |
| MailReader uses dynamic ActionForms. |
| Rather than cobble up an actual JavaBean class, |
| we specify the properties the ActionForm can accept in the configuration |
| file. |
| </p> |
| |
| <hr/> |
| <h5>The form-bean element for LogonForm</h5> |
| <pre><code> <!-- LogonAction form bean --> |
| <form-bean |
| name="LogonForm" |
| <strong>extends="BaseForm"/></strong></code></pre> |
| <hr/> |
| |
| <p> |
| Hmmmm. That doesn't seem to tell us much. |
| The LogonForm "extends" the BaseForm, but doesn't add any attributes. |
| What about the BaseForm element that LogonForm extends? |
| </p> |
| |
| <hr/> |
| <h5>BaseForm - An "abstract" form-bean</h5> |
| <pre><code> <!-- BaseAction form bean (abstract) --> |
| <form-bean |
| name="BaseForm" |
| type="org.apache.struts.validator.DynaValidatorForm"> |
| <strong><form-property</strong> |
| name="username" |
| type="java.lang.String"/> |
| <form-property |
| name="password" |
| type="java.lang.String"/> |
| <form-property |
| name="task" |
| type="java.lang.String" |
| initial="Create"/> |
| </form-bean></code></pre> |
| <hr/> |
| |
| <h5>Note:</h5> |
| <blockquote> |
| <p><font class="hint"> |
| <strong>Extends</strong> - In the Struts configuration file, |
| we can use the "extends" attribute to adopt default settings from |
| another element. |
| Extends makes using XML elements more like object-orientated |
| programming. |
| You can setup a base element, and then only specify the behavior that |
| changes. |
| </font></p> |
| </blockquote> |
| <hr/> |
| |
| <p> |
| Struts creates the ActionForms automatically, regardless of whether the |
| form-bean is conventional or dynamic. |
| The ActionForm lets us specify which properties we want to extract from |
| the incoming request. |
| If a property is not specified by the ActionForm, |
| the value is not automatically transferred from the request, validated, |
| or passed to the Action class. |
| </p> |
| |
| <h4>The "scope" attribute</h4> |
| |
| <p> |
| The <strong>scope</strong> attribute in the "SubmitLogon" action element |
| tells the controller whether to store the ActionForm in the request, |
| rather than in the user's session. |
| </p> |
| |
| <hr/> |
| <h5>Best Practice:</h5> |
| <blockquote> |
| <p><font class="hint"> |
| Use request scope for single-page forms that contain all the |
| properties needed by the Action. |
| There is usually no need to maintain form input across requests. |
| </font></p> |
| </blockquote> |
| <hr/> |
| |
| <h4>The "validate" attribute</h4> |
| |
| <p> |
| The <strong>validate</strong> attribute controls whether the framework |
| will automatically validate |
| the ActionForm. |
| Conventional ActionForms have a stub "validate" method that you can |
| implement. |
| MailReader uses the Validator framework instead, |
| so the validations are specified in another configuration file. |
| </p> |
| |
| <h4>The "input" attribute</h4> |
| |
| <p> |
| If validation fails, Struts looks for the forward specified by the |
| <strong>input</strong> attribute. |
| In this case, invoking the global "Logon" forward sends control back to |
| the Logon page. |
| </p> |
| |
| <h4>The "exception" element</h4> |
| |
| <p> |
| Within the SubmitLogon action element is another new element, <strong> |
| exception</strong>. |
| When a user logons on, it's possible that an "ExpiredPasswordException" |
| will be thrown. |
| Should this happen, Struts will catch the exception and send control to |
| the "ChangePassword" action. |
| </p> |
| |
| <hr/> |
| <h5>Change Password screen</h5> |
| <blockquote> |
| <p> |
| Your password has expired. Please ask the system administrator to |
| change it. <u>Try Again</u> |
| </p> |
| </blockquote> |
| <hr/> |
| |
| <p> |
| OK, it's not the greatest "Change Password" screen -- but, remember, this |
| is still the first iteration! |
| </p> |
| |
| <h4><a name="validations.xml" id="validations.xml">validations.xml</a></h4> |
| |
| <p> |
| In the Logon page, we mentioned that the html:javascript tag confers with |
| the Struts Validator components. |
| The Validator is configured through another XML document, the <strong> |
| validation.xml</strong>. |
| </p> |
| |
| <hr/> |
| <h5>Validator element for LogonForm</h5> |
| <pre><code> <form |
| name="LogonForm"> |
| |
| <field |
| property="<strong>username</strong>" |
| depends="required"> |
| <arg |
| key="prompt.username"/> |
| </field> |
| |
| <field |
| property="<strong>password</strong>" |
| depends="required, minlength,maxlength"> |
| <arg |
| key="prompt.password"/> |
| <arg |
| key="${var:minlength}" |
| name="minlength" |
| resource="false"/> |
| <arg |
| key="${var:maxlength}" |
| name="maxlength" |
| resource="false"/> |
| |
| <var> |
| <var-name> |
| maxlength |
| </var-name> |
| <var-value> |
| 16 |
| </var-value> |
| </var> |
| <var> |
| <var-name> |
| minlength |
| </var-name> |
| <var-value> |
| 3 |
| </var-value> |
| </var> |
| </field> |
| |
| </form></code></pre> |
| <hr/> |
| |
| <p> |
| The field elements correspond to the ActionForm properties. |
| The <strong>username</strong> field element says it depends on the |
| "required" validator. |
| If the username is blank or absent, validation will fail and an error |
| message is automatically generated. |
| </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="LogonAction.java" id="LogonAction.java">LogonAction.java</a></h4> |
| |
| <p> |
| If validation passes, the LogonForm object is passed to the LogonAction as |
| a parameter. |
| The LogonAction interacts with the database to see if the credentials are |
| valid. |
| If so, the user is logged on, and control passes to the "success" forward. |
| Otherwise, control is forwarded to the input page and the list of error |
| messages displayed. |
| </p> |
| |
| <hr/> |
| <h5>LogonAction.java</h5> |
| <pre><code> public final class <strong>LogonAction</strong> extends BaseAction |
| { |
| public ActionForward execute( |
| ActionMapping mapping, |
| ActionForm form, |
| HttpServletRequest request, |
| HttpServletResponse response) |
| throws Exception { |
| |
| // Retrieve user |
| String username = <strong>doGet</strong>(form, USERNAME); |
| String password = doGet(form, PASSWORD); |
| ActionMessages errors = new ActionMessages(); |
| User user = <strong>doGetUser</strong>(username,password,errors); |
| |
| // Report back any errors, and exit if any |
| if (!errors.isEmpty()) { |
| this.saveErrors(request, errors); |
| return (mapping.getInputForward()); |
| } |
| |
| // Cache user object in session to signify logon |
| <strong>doCacheUser</strong>(request,user); |
| |
| // Done |
| return <strong>doFindSuccess</strong>(mapping); |
| } |
| }</code></pre> |
| <hr/> |
| |
| <p> |
| LogonAction extends another Action class, BaseAction. |
| The BaseAction class provides a number of helper methods, all of which are |
| prefixed with "do". |
| The <strong>doCacheUser</strong> method, for example, stores the user |
| object in the session, |
| and logs the event, if the logging level is set to debug. |
| </p> |
| |
| <hr/> |
| <h5>BaseAction.doCacheUser</h5> |
| <pre><code> void <strong>doCacheUser</strong>(HttpServletRequest request, User |
| user) { |
| |
| HttpSession session = request.getSession(); |
| session.setAttribute(Constants.USER_KEY, user); |
| if (log.isDebugEnabled()) { |
| log.debug( |
| "LogonAction: User '" |
| + user.getUsername() |
| + "' logged on in session " |
| + session.getId()); |
| } |
| } |
| </code></pre> |
| <hr/> |
| |
| <p> |
| Because our ActionForm is a DynaActionForm, |
| we can't access or change the properties directly using methods like |
| "getUsername". |
| To make working with DynaActionForm properties easier, |
| BaseAction provides two simple helper methods, <strong>doGet</strong> and |
| <strong>doSet</strong>. |
| These are utility methods that you caould use in any Struts application. |
| </p> |
| |
| <hr/> |
| <h5>BaseAction.doGet and doSet</h5> |
| <pre><code> protected String <strong>doGet</strong>(ActionForm form, String |
| property) { |
| String initial; |
| try { |
| initial = (String) PropertyUtils.getSimpleProperty(form, property); |
| } catch (Throwable t) { |
| initial = null; |
| } |
| String value = null; |
| if ((initial !=null) && (initial.length()>0)) { |
| value = initial.trim(); |
| if (value.length()==0) value = null; |
| } |
| return value; |
| } |
| |
| protected boolean <strong>doSet</strong>(ActionForm form, String property, |
| String value) { |
| try { |
| DynaActionForm dyna = (DynaActionForm) form; |
| dyna.set(property,value); |
| } catch (Throwable t) { |
| return false; |
| } |
| return true; |
| }</code></pre> |
| <hr/> |
| |
| <p> |
| LogonAction uses two other BaseAction helpers: "doGetUser" and |
| "doFindSuccess". |
| </p> |
| |
| <hr/> |
| <h5>BaseAction.doGetUser</h5> |
| <pre><code> User <strong>doGetUser</strong>(UserDatabase database, String |
| username, |
| String password, ActionMessages errors) |
| throws ExpiredPasswordException { |
| |
| User user = null; |
| if (database == null){ |
| errors.add( |
| ActionMessages.GLOBAL_MESSAGE, |
| new ActionMessage("error.database.missing")); |
| } |
| else { |
| user = <strong>database.</strong>findUser(username); |
| if ((user != null) && !user.getPassword().equals(password)) { |
| user = null; |
| } |
| if (user == null) { |
| errors.add( |
| ActionMessages.GLOBAL_MESSAGE, |
| new ActionMessage("error.password.mismatch")); |
| } |
| } |
| |
| return user; |
| } |
| </code></pre> |
| <hr/> |
| |
| <p> |
| The <strong>doGetUser</strong> helper does the real work of the |
| LogonAction. |
| The "doGetUser" method checks the tendered credentials against the |
| database, |
| If there are any issues, like there is not a user by that name, or the |
| password doesn't match, doGetUser passes back the appropriate messages. |
| </p> |
| |
| <p> |
| The doGet user is another "facade" method. |
| A facade simplifies a complicated call to a business method. |
| Here, doGetUser wrapes the business method with the Error Handling needed |
| by the Mail Reader application. |
| </p> |
| |
| <hr/> |
| <h5>BaseAction.doFindSuccess</h5> |
| <pre><code> protected ActionForward <strong>doFindSuccess</strong> |
| (ActionMapping mapping) { |
| if (log.isTraceEnabled()) { |
| log.trace(Constants.LOG_SUCCESS); |
| } |
| return mapping.findForward(Constants.SUCCESS); |
| </code></pre> |
| <hr/> |
| |
| <p> |
| The <strong>doFindSuccess</strong> method is a simple utility but often |
| used. |
| It just checks the mapping for a result named "Success". |
| By using a utility, |
| we can include a logging statement when trace is enabled, |
| and be very sure that everyone knows how to spell "Success". :) |
| </p> |
| |
| <h4><a name="MainMenu.do" id="MainMenu.do">MainMenu.do and MainMenu.jsp</a> |
| </h4> |
| |
| <p> |
| On a successful logon, 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> |
| If you check the address shown by your browser, |
| you will see that it shows "/SubmitLogon.do" not "/MainMenu.do". |
| The Java servlet platform supports the idea of server-side forwards. |
| When control passed from the SubmitLogon action to the MainMenu action, |
| everything occured server-side. |
| All the browser knows is that we are looking at the result of submitting a |
| form to "/SubmitLogon.do", |
| so that's the address that shows. |
| It doesn't know control passed from one action to another. |
| The difference between server-side forwards and client-side redirects is |
| subtle |
| and often confuses new developers. |
| </p> |
| |
| <p> |
| If you change the address to "/MainMenu.do" and press enter, the same page |
| will display again, |
| but only because you are logged on. |
| If you bookmark MainMenu.do, log off, and try to return later, the system |
| will force you to logon first. |
| </p> |
| |
| <p> |
| Let's review the source for the "MainMenu" action mapping, |
| "MainMenuAction.java" class, and the "MainMenu.jsp". |
| </p> |
| |
| <hr/> |
| <h5>Action mapping element for MainMenu</h5> |
| <pre><code> <!-- Display MainMenu --> |
| <action |
| <strong>path="/MainMenu"</strong> |
| type="org.apache.struts.apps.mailreader.actions.MainMenuAction"> |
| <forward |
| name="Success" |
| path="/MainMenu.jsp"/> |
| </action></code></pre> |
| |
| <h5>MainMenuAction.java</h5> |
| <pre><code>public final class MainMenuAction extends BaseAction { |
| |
| public ActionForward <strong>execute</strong> ( |
| ActionMapping mapping, |
| ActionForm form, |
| HttpServletRequest request, |
| HttpServletResponse response) |
| throws Exception { |
| |
| User user = doGetUser(request); |
| if (user==null) return <strong>doFindLogon</strong>(mapping); |
| return <strong>doFindSuccess</strong>(mapping); |
| } |
| |
| }</code></pre> |
| |
| <h5>MainMenu.jsp</h5> |
| <pre><code><%@ page contentType="text/html;charset=UTF-8" language="java" |
| %> |
| <%@ taglib uri="/tags/app" prefix="app" %> |
| <%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %> |
| <%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %> |
| <html> |
| <head> |
| <title><bean:message key="mainMenu.title"/></title> |
| <link rel="stylesheet" type="text/css" href="base.css" /> |
| </head> |
| <h3><bean:message key="mainMenu.heading"/> <<strong> |
| bean:write</strong> name="user" property="fullName" /></h3> |
| <ul> |
| <li><html:link action="/EditRegistration"><bean:message |
| key="mainMenu.registration"/></html:link></li> |
| <li><html:link action="/Logoff"><bean:message |
| key="mainMenu.logoff"/></html:link></li> |
| </ul> |
| </body> |
| </html></code></pre> |
| <hr/> |
| |
| <p> |
| In the MainMenuAction, we find two new helper methods: "doFindLogon" and |
| "doFindSuccess". |
| </p> |
| |
| <p> |
| The <strong>doFindLogon</strong> helper locates the "logon" result, |
| and logs the event if trace logging is enabled. |
| If someone has bookmarked "MainMenu.do" and tries to return without |
| logging in, |
| the MainMenuAction sends the request to Logon.do instead. |
| </p> |
| |
| <hr/> |
| <h5>BaseAction.doFindLogon</h5> |
| <pre><code> protected ActionForward <strong>doFindLogon</strong>(ActionMapping |
| mapping) { |
| if (log.isTraceEnabled()) { |
| log.trace(Constants.LOG_LOGON); |
| } |
| <strong>return mapping.findForward(Constants.LOGON);</strong> |
| } |
| </code></pre> |
| |
| <p> |
| Likewise, <strong>doFindSuccess</strong> locates the "success" result, |
| and logs the event if trace logging is enabled. |
| </p> |
| |
| <hr/> |
| <h5>BaseAction.doFindSuccess</h5> |
| <pre><code> protected ActionForward <strong>doFindSuccess</strong> |
| (ActionMapping mapping) { |
| if (log.isTraceEnabled()) { |
| log.trace(Constants.LOG_SUCCESS); |
| } |
| <strong>return mapping.findForward(Constants.SUCCESS);</strong> |
| }</code></pre> |
| <hr/> |
| |
| <p> |
| The source for MainMenu.jsp also contains a new tag, <strong> |
| bean:write</strong>. |
| When control passed through the LogonAction, |
| it retrieved a "user" object from the database and stored a reference in |
| "session" scope. |
| This object is a JavaBean with several properties. |
| One property is "fullName". |
| The bean:write tag can find the User bean and print the fullName property |
| for us. |
| </p> |
| |
| <p> |
| Since the MainMenuAction verifies that the User object exists, |
| the MainMenu page can refer to the object without ado. |
| The application does not reveal the location of any server page on the |
| browser address bar, |
| but if you did happen to know the address for the server page, and you did |
| enter it directly, |
| the tag would throw an exception, and the framework would transfer to the |
| standard error page. |
| </p> |
| |
| <hr/> |
| <h5>Error.jsp</h5> |
| <pre><code> An unexpected error has occured |
| |
| Cannot find bean: "user" in any scope |
| |
| <u>MailReader Demonstration Application</u></code></pre> |
| <hr/> |
| |
| <p> |
| The MainMenu action doesn't have an exception handler of its own, |
| so it falls back to the standard handler in the MailReader web.xml. |
| </p> |
| |
| <hr/> |
| <h5>error-page element in web.xml</h5> |
| <pre><code> <!-- The default error page --> |
| <error-page> |
| <exception-type>java.lang.Exception</exception-type> |
| <location>/Error.jsp</location> |
| </error-page></code></pre> |
| <hr/> |
| |
| <p> |
| Meanwhile, The MainMenu page offers two links. |
| One link is to the "Logoff" action. |
| The other is to "EditRegistration". |
| Hmmmm, back on the Welcome page, we also linked to the EditRegistration |
| action to create an account. |
| Which is it? Insert or Update? |
| </p> |
| |
| <h4><a name="EditRegistration.do" id="MainMenu.do">EditRegistration.do</a> |
| </h4> |
| |
| <p> |
| If you scan the struts-config for an EditRegisration action, you won't |
| find one. |
| Instead, you will find a "/Edit*" action. |
| The asterisk is a wildcard character that lets you apply a common action |
| configuration |
| to any request that beings with "Edit". |
| </p> |
| |
| <hr/> |
| <h5>The Wilcard Edit* action and the BaseAction element it extends</h5> |
| <pre><code> <action |
| path="/Edit<strong>*</strong>" |
| extends="//BaseAction" |
| <strong>parameter</strong>="Edit" |
| validate="false"/> |
| |
| <!-- "Abstract" mapping to use as base --> |
| <action path="//BaseAction" |
| input="Input" |
| type="org.apache.struts.apps.mailreader.actions.<strong>{1}</strong> |
| Action" |
| name="<strong>{1}</strong>Form" |
| scope="request"> |
| <forward |
| name="Success" |
| path="/"<strong>{1}</strong>.jsp"/> |
| <forward |
| name="Input" |
| path="/"<strong>{1}</strong>.jsp"/> |
| </action></code></pre> |
| <hr/> |
| |
| <p> |
| The <strong>Edit*</strong> action extends the "BaseAction" element (as do |
| other Wilcard actions), |
| and provides two attibutes of its own: "validate" and "parameter". |
| Validate we've seen before, but what about "parameter"? |
| </p> |
| |
| <p> |
| The Action class underlying the EditRegistration action extends a standard |
| Struts class called |
| "MappingDispatchAction". |
| Usually, an Action has one entry point, the "execute" method. |
| Dispatch Actions let you define multiple entry points to an Action. |
| People find Dispatch Actions handy for related actions, like editing or |
| saving the same entity. |
| (In our case, a Registration.) |
| </p> |
| |
| <p> |
| A <strong>MappingDispatchAction</strong> uses the parameter attribute to |
| determine |
| which method to use as the entry point. |
| Each dispatch entry point is a separate method, but each method must same |
| signature as execute. |
| In this case, we're telling the framework to call the "Edit" method when |
| this action is invoked. |
| </p> |
| |
| <p> |
| OK, but how do we know which Action class to call? The "Edit*" action |
| mapping doesn't specify a type attribute. |
| </p> |
| |
| <p> |
| If you look at the BaseAction mapping, |
| you'll see that it does specify a type attribute, but the class name isn't |
| valid. |
| It contains a "{1}" token instead of the fully qualified class name. |
| </p> |
| |
| <p> |
| Since the type attribute will be used with a Wildcard action ("/Edit*"), |
| we can use the {1} to represent the first Wildcard replaced in the path. |
| In the case of EditRegistration, the "wild" text would be "Registration", |
| so that's what is plugged into the type statement. |
| Then the EditRegistration action is called, |
| the type becomes "org.apache.struts.apps.mailreader.actions.<strong> |
| Registration</strong>Action". |
| </p> |
| |
| <p> |
| The same thing will happen for the other markers. |
| The ActionForm name becomes "RegistrationForm". |
| The name of the "Success" server page becomes "Registration.jsp". |
| </p> |
| |
| <p> |
| The <strong>RegistrationForm</strong> is a DynaActionForm, much like the |
| LoginForm. |
| </p> |
| |
| <hr/> |
| <h5>The RegistrationForm form-bean element</h5> |
| <pre><code> <!-- RegistrationAction form bean --> |
| <form-bean |
| name="RegistrationForm" |
| <strong>extends="BaseForm"</strong>> |
| <form-property |
| name="fromAddress" |
| type="java.lang.String"/> |
| <form-property |
| name="fullName" |
| type="java.lang.String"/> |
| <form-property |
| name="password2" |
| type="java.lang.String"/> |
| <form-property |
| name="replyToAddress" |
| type="java.lang.String"/> |
| </form-bean></code></pre> |
| <hr/> |
| |
| <p> |
| The <strong>RegistrationForm</strong> extends the BaseForm and adds |
| properties peculiar to the Registration |
| action. |
| When either the Welcome page or MainMenu page link to "<strong> |
| Edit</strong>Registration", |
| the framework creates the RegistrationForm, autopopulates the form-bean |
| properties from any matching attributes |
| in the request, and invokes the <strong>Edit</strong> method of the |
| RegistrationAction class. |
| </p> |
| |
| <hr/> |
| <h5>RegistrationAction.Edit</h5> |
| <pre><code> public ActionForward <strong>Edit</strong>( |
| ActionMapping mapping, |
| ActionForm form, |
| HttpServletRequest request, |
| HttpServletResponse response) |
| throws Exception { |
| |
| final String method = Constants.EDIT; |
| <strong>doLogProcess</strong>(mapping,method); |
| |
| HttpSession session = request.getSession(); |
| User user = <strong>doGetUser</strong>(session); |
| boolean updating = (user!=null); |
| if (updating) { |
| <strong>doPopulate</strong>(form,user); |
| } |
| |
| <strong>doSaveToken</strong>(request); |
| return doFindSuccess(mapping); |
| } |
| </code></pre> |
| <hr/> |
| |
| <p> |
| The <strong>RegistrationAction.Edit</strong> method does most of its work |
| by calling various helper methods, |
| including "doLogProcess", "doGetUser", "doPopulate", and "doSaveToken". |
| </p> |
| |
| <hr/> |
| <h5>BaseAction.doLogProcess</h5> |
| <pre><code> protected void <strong>doLogProcess</strong>(ActionMapping |
| mapping, String method) { |
| if (log.isDebugEnabled()) { |
| StringBuffer sb = new StringBuffer(128); |
| sb.append(" "); |
| sb.append(mapping.getPath()); |
| sb.append(":"); |
| sb.append(Constants.LOG_PROCESSING); |
| sb.append(method); |
| log.debug(sb.toString()); |
| } |
| }</code></pre> |
| <hr/> |
| |
| <p> |
| MailReader uses <a href="http://jakarta.apache.org/commons/logging/">Commons |
| Logging</a> from Apache Jakarta. |
| Commons Logging is an API utility that can be used with several different |
| logging systems. |
| The <strong>doLogProcess</strong> helper is just a wrapper around a |
| logging statement |
| that MailReader uses in several places. |
| All of the MailReader's Dispatch methods call this helper to log when |
| control reaches the method. |
| </p> |
| |
| <hr/> |
| <h5>BaseAction.doGetUser</h5> |
| <pre><code> protected User <strong>doGetUser</strong>(HttpSession session) { |
| return (User) session.getAttribute(Constants.USER_KEY); |
| } |
| </code></pre> |
| <hr/> |
| |
| <p> |
| Most of the MailReader actions utilize the User object, |
| and so BaseAction encapsulates obtaining the User into the very simple |
| <strong>doGetUser</strong> helper. |
| </p> |
| |
| <hr/> |
| <h5>RegistrationAction.doPopulate</h5> |
| <pre><code> private void <strong>doPopulate</strong>(ActionForm form, User |
| user) throws ServletException { |
| |
| final String title = Constants.EDIT; |
| |
| if (log.isTraceEnabled()) { |
| log.trace(Constants.LOG_POPULATE_FORM + user); |
| } |
| |
| try { |
| PropertyUtils.copyProperties(form, user); |
| DynaActionForm dyna = (DynaActionForm) form; |
| <strong>dyna.set(TASK,title);</strong> |
| dyna.set(PASSWORD,null); |
| dyna.set(PASSWORD2,null); |
| } catch (InvocationTargetException e) { |
| Throwable t = e.getTargetException(); |
| if (t == null) |
| t = e; |
| log.error(LOG_REGISTRATION_POPULATE, t); |
| throw new ServletException(LOG_REGISTRATION_POPULATE, t); |
| } catch (Throwable t) { |
| log.error(LOG_REGISTRATION_POPULATE, t); |
| throw new ServletException(LOG_REGISTRATION_POPULATE, t); |
| } |
| }</code></pre> |
| <hr/> |
| |
| <p> |
| The <strong>doPopulate</strong> helper lives in RegistrationAction rather |
| than BaseAction, |
| since it involves the RegistrationForm, which only the RegistrationAction |
| uses. |
| The main job of doPopulate is to transfer the input values from the |
| ActionForm to the User object. |
| The constants, like "PASSWORD", are defined on RegisrationAction as public |
| constants. |
| These constants document the property names specified in the Struts |
| configuration for the dynamic |
| RegistrationForm. |
| </p> |
| |
| <p> |
| An interesting point is the line that sets the "TASK" to "Edit". |
| This value is used by the server page to adjust the page title and form |
| layout. |
| When we defined the RegistrationForm, we set the default value for TASK to |
| be "Create". |
| When EditRegistration is called from the Welcome page, we don't have a |
| User object yet, |
| and so the TASK remains at "Create". |
| When EditRegistration is called from the MainMenu page, we do have a User |
| object, |
| and so the TASK is set to "Edit". |
| </p> |
| |
| <hr/> |
| <h5>BaseAction.doSaveToken</h5> |
| <pre><code> protected void doSaveToken(HttpServletRequest request) { |
| if (log.isTraceEnabled()) { |
| log.trace(Constants.LOG_TOKEN); |
| } |
| saveToken(request); |
| }</code></pre> |
| <hr/> |
| |
| <p> |
| A common problem with designing web applications is that response times |
| can vary and users are impatient. |
| 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 verifying a |
| credit card transaction, it's easier for a double submit to occur. |
| </p> |
| |
| <p> |
| To forestall double submits, 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 a form has been submitted twice or out of sequence. |
| </p> |
| |
| <p> |
| The <strong>doSaveToken</strong> helper, logs the event for tracking, |
| and then calls the framework method. |
| Later, when we Save the registration, we will check to see if the token is |
| valid. |
| </p> |
| |
| <p> |
| After the token has been saved, |
| Edit calls "doFindSuccess and" control flows to the Registration page. |
| </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 Registation.jsp makes good use of logic tags, |
| to make it appears as though there are two distinct pages. |
| For example, if you are editing the form (task == "Edit"), |
| the page inserts your username from the RegistrationForm bean. |
| If you are new user (task == "Create"), the page creates an empty field, |
| so you can pick your username. |
| </p> |
| |
| <hr/> |
| <h5>Note:</h5> |
| <blockquote> |
| <p><font class="hint"> |
| <strong>Presention Logic</strong> - |
| The Struts logic tags are a convenient way to express presentation |
| logic within your pages. |
| Customized pages help to prevent user error, |
| and dynamic customization reduces the number of JSPs 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><logic:equal name="RegistrationForm" <strong> |
| property="task"</strong> |
| scope="request" <strong>value="Edit"</strong>> |
| <h3><bean:message key="heading.subscriptions"/></h3> |
| <!-- ... --> |
| <html:link action="/EditSubscription"> |
| <bean:message key="registration.addSubscription"/> |
| </html:link> |
| </logic:equal></code></pre> |
| <hr/> |
| |
| <p> |
| Otherwise, the page contains just the top portion -- |
| a blank data-entry form for creating the user's registration. |
| </p> |
| |
| <h4><a name="logic:iterate" id="logic:iterate">logic:iterate</a></h4> |
| |
| <p> |
| Beside making the usual conditional tests, |
| you can also use logic tags to forward control to other actions, |
| to redirect control to another path, and to iterate over collections. |
| The Registration page includes a good example of using the logic:iterate |
| 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 iterate tag, you can code it the way it sounds. |
| </p> |
| |
| <hr/> |
| <h5>Using logic:iterate to list the Subscriptions</h5> |
| <pre><code> <logic:iterate <strong>name</strong>="user" <strong> |
| property</strong>="subscriptions" <strong>id</strong>="subscription"> |
| <tr> |
| <td align="left"> |
| <bean:write name="subscription" property="host"/> |
| </td> |
| <td align="left"> |
| <bean:write name="subscription" property="username"/> |
| </td> |
| <td align="center"> |
| <bean:write name="subscription" property="type"/> |
| </td> |
| <td align="center"> |
| <bean:write name="subscription" property="autoConnect"/> |
| </td> |
| <td align="center"> |
| <html:link action="/DeleteSubscription" |
| <strong>paramName</strong>="subscription" <strong>paramProperty</strong> |
| ="host" <strong>paramId</strong>="host"> |
| <bean:message key="registration.deleteSubscription"/> |
| </html:link> |
| |
| <html:link action="/EditSubscription" |
| paramName="subscription" paramProperty="host" paramId="host"> |
| <bean:message key="registration.editSubscription"/> |
| </html:link> |
| </td> |
| </tr> |
| </logic:iterate></code></pre> |
| <hr/> |
| |
| <p> |
| The three parameters to the iterate tag (name, property, and id) tell it |
| to |
| </p> |
| |
| <ol> |
| <li> |
| <em>name</em> - Check this context for an attribute (e.g. object) |
| named <strong>user</strong>, |
| </li> |
| |
| <li> |
| <em>property</em> - Snag the property of user named <strong> |
| subscriptions</strong>, |
| </li> |
| |
| <li> |
| <em>id</em> - In the block to iterate, |
| use <strong>subscription</strong> (singular) as the name for each |
| member of the collection. |
| </li> |
| </ol> |
| |
| <p> |
| Next to each entry in the subscription list are links to Delete and Edit |
| commands. |
| These links use the same name/property/id trinity as the interator, |
| except that the attributes are used to create a hyperlink with a single |
| parameter. |
| (Multiple parameters are possible too, but if the code is well-factored, |
| one should be sufficient.) |
| </p> |
| |
| <p> |
| Given a subscription to "mail.yahoo.com", |
| the command links would translate to HTML links like these: |
| </p> |
| |
| <hr/> |
| <h5>The Delete and Edit links for mail.yahoo.com</h5> |
| <pre><code> <a |
| href="/struts-mailreader/DeleteSubscription.do?host=mail.yahoo.com">Delete</a> |
| |
| <a |
| href="/struts-mailreader/EditSubscription.do?host=mail.yahoo.com">Edit</a></code> |
| </pre> |
| <hr/> |
| |
| <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> |
| |
| <h4> |
| <a name="SubscriptionAction.java" id="SubscriptionAction.java">SubscriptionAction.java</a> |
| </h4> |
| |
| <p> |
| The EditSubscription link shares the Wildcard "/Edit*" mapping we saw with |
| EditRegistration. |
| As before, in the case of "Edit<em>Subscription</em>", |
| the "{1}Form" attribute maps to <strong>SubscriptionForm</strong>. |
| </p> |
| |
| <hr/> |
| <h5>The SubscriptionAction form-bean element</h5> |
| <pre><code> <form-bean |
| name="SubscriptionForm" |
| extends="BaseForm"> |
| <form-property |
| name="autoConnect" |
| <strong>type="java.lang.Boolean" |
| initial="FALSE" |
| reset="true"</strong>/> |
| <form-property |
| name="host" |
| type="java.lang.String" /> |
| <form-property |
| name="type" |
| type="java.lang.String" /> |
| </form-bean></code></pre> |
| <hr/> |
| |
| <p> |
| The other DynaActionForms we've seen used only String properties. |
| SubscriptionForm is different in that it uses a Boolean type for the |
| "autoConnect" property. |
| On the HTML form, the autoConnect field is represented by a checkbox, |
| and checkboxes need to be handled differently that other controls. |
| </p> |
| |
| <hr/> |
| <h5>Tip:</h5> |
| <blockquote> |
| <p class="hint"> |
| <strong>Checkboxes</strong> - |
| The HTML checkbox is a tricky control. |
| The problem is that, according to the W3C specification, a value is |
| only guaranteed to be sent |
| if the control is checked. |
| If the control is not checked, then the control may be omitted from |
| the request, as if it was on on the page. |
| This can cause a problem with session-scope checkboxes. |
| Once you set the checkbox to true, the control can't set it to false |
| again, |
| because if you uncheck the box, nothing is sent, and so the control |
| stays checked. |
| </p> |
| |
| <p class="hint"> |
| The simple solution is to set the initial value for a checkbox control |
| to false before the form is populated. |
| If the checkbox is checked, it will return a value, and the checkbox |
| will represent true. |
| If the checkbox is unchecked, it will not return a value, and the |
| checkbox will remain unchecked ("false"). |
| </p> |
| </blockquote> |
| <hr/> |
| |
| <p> |
| To be sure the autoConnect checkbox is handled correctly, |
| the SubscriptionForm initializes the property to FALSE, |
| and enables "reset" so that before autopopulation the property is set back |
| to FALSE. |
| </p> |
| |
| <p> |
| The SubscriptionAction Edit method should look familiar, but it also has a |
| few twists of its own. |
| </p> |
| |
| <hr/> |
| <h5>SubscriptionAction.Edit</h5> |
| <pre><code>public ActionForward Edit( |
| ActionMapping mapping, |
| ActionForm form, |
| HttpServletRequest request, |
| HttpServletResponse response) |
| throws Exception { |
| |
| final String method = Constants.EDIT; |
| doLogProcess(mapping,method); |
| |
| HttpSession session = request.getSession(); |
| User user = doGetUser(session); |
| <strong>if (user==null) return doFindLogon(mapping);</strong> |
| |
| // Retrieve the subscription, if there is one |
| Subscription subscription; |
| <strong>String host = doGet(form,HOST); |
| boolean updating = (host!=null);</strong> |
| if (updating) { |
| subscription = <strong>doFindSubscription</strong>(user,host); |
| if (subscription==null) return <strong>doFindFailure</strong>(mapping); |
| session.setAttribute(Constants.SUBSCRIPTION_KEY, subscription); |
| <strong>doPopulate</strong>(form,subscription); |
| doSet(form,TASK,method); |
| } |
| |
| return doFindSuccess(mapping); |
| }</code></pre> |
| <hr/> |
| |
| <p> |
| In RegistrationAction.Edit, we looked for the user object to decide if we |
| were updating or inserting. |
| In SubscriptionAction.Edit, the user object is required (and we trot off |
| to the Login page if it is missing). |
| This could happen because a session expired, or because someone bookmarked |
| a page. |
| </p> |
| |
| <p> |
| To decide if we are inserting or updating a subscription, |
| we look to see if the <strong>host</strong> is set to the ActionForm. |
| If it is an update, we fetch the Subscription from the database. |
| </p> |
| |
| <hr/> |
| <h5>SubscriptionAction.doFindSubscription</h5> |
| <pre><code> private Subscription <strong>doFindSubscription</strong>(User |
| user, String host) { |
| |
| Subscription subscription; |
| |
| try { |
| subscription = user.findSubscription(host); |
| } |
| catch (NullPointerException e) { |
| subscription = null; |
| } |
| |
| if ((subscription == null) && (log.isTraceEnabled())) { |
| log.trace( |
| " No subscription for user " |
| + user.getUsername() |
| + " and host " |
| + host); |
| } |
| |
| return subscription; |
| }</code></pre> |
| <hr/> |
| |
| <p> |
| If we can't find the subscription, |
| we use <strong>doFindFailure</strong> to forward to the Failure result |
| (Error.jsp). |
| </p> |
| |
| <hr/> |
| <h5>BaseAction.doFindFailure</h5> |
| <pre><code> protected ActionForward <strong>doFindFailure</strong> |
| (ActionMapping mapping) { |
| if (log.isTraceEnabled()) { |
| log.trace(Constants.LOG_FAILURE); |
| } |
| return (mapping.findForward(Constants.FAILURE)); |
| } |
| </code></pre> |
| <hr/> |
| |
| <p> |
| In the normal course, the subscription should always be found, |
| since we selected the host from a system-generated list. |
| If the subscription is not found, |
| it would be because the database disappeared or the request is being |
| spoofed. |
| </p> |
| |
| <p> |
| Like the RegisterAction, the <strong>doPopulate</strong> method transfers |
| data from the form to the domain |
| object. In this case, a Subscription object. |
| </p> |
| |
| <hr/> |
| <h5>SubscriptionAction.doPopulate</h5> |
| <pre><code> private void <strong>doPopulate</strong>(ActionForm form, |
| Subscription subscription) throws ServletException { |
| |
| final String title = Constants.EDIT; |
| |
| if (log.isTraceEnabled()) { |
| log.trace(Constants.LOG_POPULATE_FORM + subscription.getHost()); |
| } |
| |
| try { |
| <strong>PropertyUtils.copyProperties(form, subscription);</strong> |
| doSet(form,TASK,title); |
| } catch (InvocationTargetException e) { |
| Throwable t = e.getTargetException(); |
| if (t == null) t = e; |
| log.error(LOG_SUBSCRIPTION_POPULATE, t); |
| throw new ServletException(LOG_SUBSCRIPTION_POPULATE, t); |
| } catch (Throwable t) { |
| log.error(LOG_SUBSCRIPTION_POPULATE, t); |
| throw new ServletException(LOG_SUBSCRIPTION_POPULATE, t); |
| } |
| }</code></pre> |
| <hr/> |
| |
| <p> |
| Most of the code in "doPopulate" is window dressing for the call to |
| <strong>PropertyUtils.copyProperties</strong>, which does the heavy |
| lifting. |
| </p> |
| |
| <p> |
| But before turning to our final JSP, a word about our database model ... |
| </p> |
| |
| <h4> |
| <a name="User.java" id="User.java">User.java</a> and <a |
| name="Subscription.java" id="Subscription.java">Subscription.java</a> |
| </h4> |
| |
| <p> |
| If you're used to working with relational databases, |
| the links between the user and subscription objects may be confusing. |
| A conventional relational database would create two distinct tables, |
| one for the users and another for the subscriptions, |
| and link them together with a user ID. |
| The MailReader application implements a different model, a hierarchical |
| database. |
| Here a "table" of subscriptions is stored within each user object, |
| something like the way a filing system stores documents within folders. |
| </p> |
| |
| <p> |
| Development teams often use frameworks like <a |
| href="http://www.objectstyle.org/cayenne/">Cayenne</a> |
| to map a relational database to a hierarchy of objects, |
| like the one used by MailReader. |
| For simplicity, the MailReader doesn't use a conventional database, but |
| saves its data as an XML file. |
| While the MailReader is running, the database is kept in main memory, and |
| written to back to disk when changed. |
| </p> |
| |
| <p> |
| In addition to the usual getters and setters, |
| the user object also has two methods for working with subscription |
| objects. |
| The <strong>findSubscription</strong> method takes a hostname and returns |
| the subscription object for that host. |
| The <strong>getSubscriptions</strong> method returns an array of all the |
| subscriptions for the user |
| (ready-made for the iterate tag!). |
| Besides the fields needed to manage the SubscriptionForm data, |
| the object also maintains a runtime link to its own user object. |
| </p> |
| |
| <p> |
| To create a new subscription, |
| SubscriptionAction.java simply creates a new subscription object, |
| and sets its user to the object found in the request, |
| and then forwards control to its input form, Subscription.jsp. |
| </p> |
| |
| <h3><a name="subcription.jsp" id="subcription.jsp">Subscription.jsp</a></h3> |
| |
| <p> |
| Saving the best for last, Subscription.jsp utilizes two interesting Struts |
| custom form tags, |
| "html:options" and "html:checkbox". |
| |
| <p> |
| In Registration.jsp, the Struts iteration tag was used to write a list of |
| subscriptions. |
| Another place where iterations and collections are handy is the option |
| list for a HTML select tag. |
| Since this is such a common situation, Struts offers a html:options |
| (plural) tag |
| that can take an array of objects as a parameter. |
| The tag then iterates over the members of the array (beans) to place each |
| one inside an standard option tag. |
| So given a block like</p> |
| |
| <pre><code><html:select property="type"> |
| <html:options |
| collection="serverTypes" |
| property="value" |
| labelProperty="label" /> |
| </html:select></code></pre> |
| |
| <p>The tag outputs a block like</p> |
| |
| <pre><code><select name="type"> |
| <option value="imap" selected>IMAP Protocol</option> |
| <option value="pop3">POP3 Protocol</option> |
| </select></code></pre> |
| |
| <p> |
| Here, one collection contained both the labels and the values, from |
| properties of the same name. |
| Options can also use a second array for the labels, if they do not match |
| the values. |
| Options can use a Collection, Iterator, or Map for the source of the list. |
| </p> |
| |
| <p> |
| Unlike other data, the serverTypes array is not fetched from the database. |
| Instead, it is loaded by a Struts plugin. |
| The <strong>DigestingPlugin</strong> parses an XML document using a given |
| set of Digester rules. |
| The MailReader uses a set of rules for "LabelValueBeans" to create a list |
| of server types. |
| </p> |
| |
| <hr/> |
| <h5>Tip:</h5> |
| <blockquote> |
| <p><font class="hint"> |
| <strong>LabelValueBeans</strong> - |
| Many developers find the LabelValueBeans useful, |
| so the class is available in the Struts Action distribution as |
| [org.apache.struts.util.LabelValueBean]. |
| </font></p> |
| </blockquote> |
| <hr/> |
| |
| <p> |
| The plugin stores the list is stored in application scope. |
| Since the Struts custom tags, like standard JSP tags, search the scopes in |
| succession, |
| the tag finds the list in application scope and uses it to write out the |
| options. |
| </p> |
| |
| |
| <h4><a name="SubscriptionForm.java" id="SubscriptionForm.java">SubscriptionForm.java</a> |
| </h4> |
| |
| <p> |
| Back in Subscription.jsp, we have one more block to cover. |
| Although the same basic form can be used to created, edit, or delete a |
| subscription, |
| people might expect the buttons to be labeled differently in each case. |
| Like the Registration page, the Subscription page handles customization |
| by using a logic tag to output a different set of buttons for each case. |
| Changing buttons doesn't really change the way the Subscription page |
| works, |
| but customizing the buttons does make things less confusing for the user. |
| </p> |
| |
| <pre><code><logic:equal |
| name="SubscriptionForm" |
| <strong>property="task"</strong> |
| scope="request" |
| value="Create"> |
| <html:submit> |
| <b><bean:message key="button.save"/><br/></b> </html:submit> |
| </logic:equal></code></pre> |
| |
| <p> |
| In the case of a request to delete a subscription, |
| the submit button is labeled "Confirm", since this view is meant to give |
| the user a last chance to cancel, |
| before sending that task along to SaveSubscriptionAction.java. |
| </p> |
| |
| <p> |
| The actual task property is placed into the form as a hidden field, |
| and SaveSubscriptionAction uses that property to execute the appropriate |
| task. |
| </p> |
| |
| <h4><a name="SubscriptionAction.java" id="SubscriptionAction.java">SubscriptionAction.java</a> |
| </h4> |
| |
| <p> |
| Our final stop has the job of finishing what SubscriptionAction.Edit |
| started. |
| After the usual logic and error checking, |
| The SubscriptionAction.Save method either deletes or updates |
| the subscription object being handled by this request, |
| and cleans up the bean, just to be tidy. |
| By now, you should be very comfortable reading through the source on your |
| own, to pickup the finer points. |
| </p> |
| |
| <hr/> |
| <h5>SubscriptionAction.Save</h5> |
| <pre><code> public ActionForward <strong>Save</strong>( |
| ActionMapping mapping, |
| ActionForm form, |
| HttpServletRequest request, |
| HttpServletResponse response) |
| throws Exception { |
| |
| final String method = Constants.SAVE; |
| doLogProcess(mapping,method); |
| |
| User user = doGetUser(request); |
| if (user == null) { |
| return doFindLogon(mapping); |
| } |
| |
| HttpSession session = request.getSession(); |
| if (isCancelled(request)) { |
| <strong>doCancel</strong>(session,method,Constants.SUBSCRIPTION_KEY); |
| return doFindSuccess(mapping); |
| } |
| |
| String action = doGet(form,TASK); |
| Subscription subscription = <strong>doGetSubscription</strong>(request); |
| boolean isDelete = action.equals(Constants.DELETE); |
| if (isDelete) { |
| return <strong>doRemoveSubscription</strong> |
| (mapping,session,user,subscription); |
| } |
| |
| if (subscription==null) { |
| subscription = <strong>user.createSubscription</strong>(doGet(form,HOST)); |
| session.setAttribute(Constants.SUBSCRIPTION_KEY,subscription); |
| } |
| |
| doPopulate(subscription,form); |
| <strong>doSaveUser</strong>(user); |
| session.removeAttribute(Constants.SUBSCRIPTION_KEY); |
| |
| return doFindSuccess(mapping); |
| }</code></pre> |
| <hr/> |
| |
| <p> |
| This concludes our tour. |
| To review, you may wish to trace the path a new user takes |
| when they register with the application for the first time. |
| You should also read over each of the .java and JSP files carefully, |
| since we only covered the high points here. |
| </p> |
| |
| <h3><a name="Review" id="Review">Review</a></h3> |
| |
| <ul> |
| <li>Struts uses a single controller servlet to route HTTP requests.</li> |
| |
| <li>The requests are routed to action objects according to path (or |
| URI).</li> |
| |
| <li>Each request is handled as a separate thread</li> |
| |
| <li>There is only one object for each action (URI), so your action objects |
| must be multi-thread safe.</li> |
| |
| <li>The configuration of action objects are loaded from a XML resource |
| file, rather than hardcoded.</li> |
| |
| <li>Action objects can respond to the request, or ask the controller to |
| forward the request to another object or to another page, such as an |
| input form.</li> |
| |
| <li>A library of custom tags works with the rest of the framework to |
| enhance use of JavaServer Pages.</li> |
| |
| <li>The Struts form tag can work closely with an action objects via a |
| Struts ActionFormBean to retain the state of a data-entry form, and |
| validate the data entered.</li> |
| |
| <li>ActionForm beans can be automatically created by the JSP form or |
| controller servlet.</li> |
| |
| <li>Struts supports a message resource for loading constants strings. |
| Alternate message resources can be provided to internationalize an |
| application.</li> |
| </ul> |
| <hr/> |
| </blockquote> |
| </body> |
| </html> |