SE 452 Lecture Notes


Week 8: Introduction to Struts



Administrative [1/16]
Model View Controller [2/16]
Model 2 [3/16]
Struts [4/16]
Simple Struts App [5/16]
Setting up the app [6/16]
The ActionForm [7/16]
The Action [8/16]
struts-config.xml [9/16]
Look how clean my view pages are :) [10/16]
Struts Pros and Cons [11/16]
Let's look at a more real-world example [12/16]
JSP code and configuration [13/16]
Java code [14/16]
build.xml [15/16]
Homework and Reading [11/16]

Administrative [1/16]




Model View Controller [2/16]




Model 2 [3/16]



Struts [4/16]



Simple Struts App [5/16]


Struts from 30,000 feet would look like this:

  1. The Struts class ActionServlet controls navigation
  2. Another Struts class, the Action is used to access business classes
  3. When the ActionServlet receives a request from the container it uses the URI to determine which Action it will use to handle the request
  4. An Action can validate input, access the BL, or query a data source
  5. All data in the request is given to the Action object in the form of a ActionForm class which the ActionServlet creates based on the attributes in the request object
  6. The Action class, upon completeing its business logic, returns the servlet an ActionForward object, a logical representation of the path, to the next page
  7. All of these classes used by the servlet are bundled together in an ActionMapping object, The ActionServlet knows which ActionMapping to use based on the URI path of the request
  8. A struts-config.xml file is used to map all of these details

We'll look at a simple struts application, and then we'll see how each piece fits the MVC design pattern. Keep in the back of your mind this list of struts elements as they relate to MVC.


Setting up the app [6/16]


The files that are packaged in a war(web archive, click this link for more info)file. To open a war file simply place it in your webapps directory and the next time you start Tomcat it will expand. It's just a Jar file in a certain structure.

We'll do more on creating WARs through ant for our Struts apps in a bit. For now, let's just use this one register-complete.war

This WAR will also load the required Struts jar file to our lib and all the Struts TLDs. If you would rather install from scratch go here. http://jakarta.apache.org/struts/installation.html

Once deployed we can view our application here: http://localhost:8080/register-complete/register.jsp


The ActionForm [7/16]


package app; import org.apache.struts.action.*; public class RegisterForm extends ActionForm { protected String username; protected String password1; protected String password2; public String getUsername () {return this.username;}; public String getPassword1() {return this.password1;}; public String getPassword2() {return this.password2;}; public void setUsername (String username) {this.username = username;}; public void setPassword1(String password) {this.password1 = password;}; public void setPassword2(String password) {this.password2 = password;}; }

The Action [8/16]


package registerapp; import org.apache.struts.action.*; import javax.servlet.http.*; import java.io.*; public class RegisterAction extends Action { public ActionForward perform (ActionMapping mapping, ActionForm form, HttpServletRequest req, HttpServletResponse res) { // (1) Cast the form to the RegisterForm RegisterForm rf = (RegisterForm) form; String username = rf.getUsername(); String password1 = rf.getPassword1(); String password2 = rf.getPassword2(); // (2) Apply business logic if (password1.equals(password2)) { try { // (3) Return ActionForward for success UserDirectory.getInstance().setUser(username,password1); return mapping.findForward("success"); } catch (UserDirectoryException e) { return mapping.findForward("failure"); } } // (4) Return ActionForward for failure return mapping.findForward("failure"); } }

struts-config.xml [9/16]


<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.0//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_0.dtd"> <struts-config> <form-beans> <form-bean name="registerForm" type="app.RegisterForm"/> </form-beans> <action-mappings> <action path="/register" type="app.RegisterAction" name="registerForm"> <forward name="success" path="/success.html"/> <forward name="failure" path="/failure.html"/> </action> </action-mappings> </struts-config>

Look how clean my view pages are :) [10/16]


register.jsp <%@ taglib uri="/WEB-INF/struts-form.tld" prefix="form" %> <form:form action="register.do"> UserName:<form:text property="username"/><br> enter password:<form:password property="password1"/><br> re-enter password:<form:password property="password2"/><br> <form:submit value="Register"/> </form:form> failure.html <HTML> <HEAD> <TITLE>FAILURE</TITLE> </HEAD> <BODY> Registration failed! <P><A href="register.jsp">try again?</A></P> </BODY> </HTML> success.html <HTML> <HEAD> <TITLE>SUCCESS</TITLE> </HEAD> <BODY> Registration succeeded! <P><A href="register.jsp">try another?</A></P> </BODY> </HTML>

Struts Pros and Cons [11/16]



Let's look at a more real-world example [12/16]



JSP code and configuration [13/16]


index.jsp <%@ taglib uri="/tags/struts-logic" prefix="logic" %> <logic:forward name="welcome"/> <%-- Redirect default requests to Welcome action. --%> Welcome.jsp <%@ taglib uri="/tags/struts-bean" prefix="bean" %> <%@ taglib uri="/tags/struts-html" prefix="html" %> <%@ taglib uri="/tags/struts-logic" prefix="logic" %> <HTML> <HEAD> <TITLE>Welcome!</TITLE> <html:base/> </HEAD> <BODY> <logic:present name="user"> <H3>Welcome <bean:write name="user" property="username"/>!</H3> </logic:present> <logic:notPresent scope="session" name="user"> <H3>Welcome World!</H3> </logic:notPresent> <html:errors/> <UL> <LI><html:link forward="logon">Sign in</html:link></LI> <logic:present name="user"> <LI><html:link forward="logoff">Sign out</html:link></LI> </logic:present> </UL> <IMG src='struts-power.gif' alt='Powered by Struts'> </BODY> </HTML> <%-- If user is logged in, display "Welcome ${username}!" Else display "Welcome World!" Display link to log in page; maintain session id if needed. If user is logged in, display a link to the sign-out page. Note: Only the minimum required html or Struts custom tags are used in this example. --%> struts-config.xml <?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.0//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_0.dtd"> <struts-config> <!-- ======================================== Form Bean Definitions --> <form-beans> <form-bean name="logonForm" type="app.LogonForm"/> </form-beans> <!-- =================================== Global Forward Definitions --> <global-forwards> <forward name="logoff" path="/Logoff.do"/> <forward name="logon" path="/Logon.do"/> <forward name="welcome" path="/Welcome.do"/> </global-forwards> <!-- =================================== Action Mapping Definitions --> <action-mappings> <action path="/Welcome" type="org.apache.struts.actions.ForwardAction" parameter="/pages/Welcome.jsp"/> <action path="/Logon" type="org.apache.struts.actions.ForwardAction" parameter="/pages/Logon.jsp"/> <action path="/LogonSubmit" type="app.LogonAction" name="logonForm" scope="request" validate="true" input="/pages/Logon.jsp"> <forward name="success" path="/pages/Welcome.jsp"/> </action> <action path="/Logoff" type="app.LogoffAction"> <forward name="success" path="/pages/Welcome.jsp"/> </action> </action-mappings> </struts-config> Logon.jsp <%@ taglib uri="/tags/struts-html" prefix="html" %> <HTML> <HEAD> <TITLE>Sign in, Please!</TITLE> </HEAD> <BODY> <html:errors/> <html:form action="/LogonSubmit" focus="username"> <TABLE border="0" width="100%"> <TR> <TH align="right">Username:</TH> <TD align="left"><html:text property="username"/></TD> </TR> <TR> <TH align="right">Password:</TH> <TD align="left"><html:password property="password"/></TD> </TR> <TR> <TD align="right"><html:submit/></TD> <TD align="left"><html:reset/></TD> </TR> </TABLE> </html:form> </BODY> </HTML> <%-- Allow user to submit username and password to logon action. --%>

Java code [14/16]


package app; import javax.servlet.http.HttpServletRequest; import org.apache.struts.action.ActionError; import org.apache.struts.action.ActionErrors; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionMapping; public final class LogonForm extends ActionForm { private String password = null; private String username = null; public String getPassword() { return (this.password); } public void setPassword(String password) { this.password = password; } public String getUsername() { return (this.username); } public void setUsername(String username) { this.username = username; } /* * over-ridden from super class if, not required is scope is * set to request in config, good for object re-use */ public void reset(ActionMapping mapping, HttpServletRequest request) { setPassword(null); setUsername(null); } /* * over-ridden from super class if, configuration in mapping is * set to validate=true this method will be called * * sets to a key we have in a configuration file, html:errors will * look up the key and print the error */ public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) { ActionErrors errors = new ActionErrors(); if ((username == null) || (username.length() < 1)) errors.add("username", new ActionError("error.username.required")); if ((password == null) || (password.length() < 1)) errors.add("password", new ActionError("error.password.required")); return errors; } } package app; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpServletResponse; import org.apache.struts.action.Action; import org.apache.struts.action.ActionError; import org.apache.struts.action.ActionErrors; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import org.apache.struts.action.ActionServlet; public final class LogonAction extends Action { /** * Validate credentials with business tier. * * @param username The username credential * @param password The password credential * @returns true if credentials can be validated * @exception UserDirectoryException if cannot access directory */ public boolean isUserLogon(String username, String password) throws UserDirectoryException { return (UserDirectory.getInstance().isValidPassword(username,password)); // return true; } /** * Login the user. * The event is logged if the debug level is >= Constants.DEBUG. * * @param mapping The ActionMapping used to select this instance * @param actionForm The ActionForm bean for this request (if any) * @param request The HTTP request we are processing * @param response The HTTP response we are creating * * @exception IOException if an input/output error occurs * @exception ServletException if a servlet exception occurs */ public ActionForward perform(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // Obtain username and password from web tier String username = ((LogonForm) form).getUsername(); String password = ((LogonForm) form).getPassword(); // Validate credentials with business tier boolean validated = false; try { validated = isUserLogon(username,password); } catch (UserDirectoryException ude) { // couldn't connect to user directory ActionErrors errors = new ActionErrors(); errors.add(ActionErrors.GLOBAL_ERROR, new ActionError("error.logon.connect")); saveErrors(request,errors); // return to input page return (new ActionForward(mapping.getInput())); } if (!validated) { // credentials don't match ActionErrors errors = new ActionErrors(); errors.add(ActionErrors.GLOBAL_ERROR, new ActionError("error.logon.invalid")); saveErrors(request,errors); // return to input page return (new ActionForward(mapping.getInput())); } // Save our logged-in user in the session, // because we use it again later. HttpSession session = request.getSession(); session.setAttribute(Constants.USER_KEY, form); // Log this event, if appropriate if (servlet.getDebug() >= Constants.DEBUG) { StringBuffer message = new StringBuffer("LogonAction: User '"); message.append(username); message.append("' logged on in session "); message.append(session.getId()); servlet.log(message.toString()); } // Return success return (mapping.findForward(Constants.SUCCESS)); } } // End LogonAction

build.xml [15/16]


<!-- basedir-based on the current position of build.xml, default - target dir --> <project name="logon" basedir="../" default="project"> <!-- Change this to compile against a different version --> <property name="compile.classpath" value="compile.classpath_1_0"/> <!-- Local system paths --> <property name="jdbc20ext.jar" value="/javasoft/lib/jdbc2_0-stdext.jar"/> <property name="servlet.jar" value="/javasoft/lib/servlet.jar"/> <!-- Project settings --> <property name="project.title" value="logon"/> <property name="project.version" value="1.4"/> <property name="project.dist" value="logon"/> <!-- Path settings --> <property name="dist.path" value="/projects/lib"/> <property name="doc.path" value="./doc/api"/> <property name="doc.src" value="./src/java"/> <!-- classpath for Struts 1.0 --> <path id="compile.classpath_1_0"> <pathelement path ="lib/struts_1_0_2.jar"/> <pathelement path ="classes"/> <pathelement path ="${classpath}"/> </path> <!-- classpath for Struts 1.1 --> <path id="compile.classpath_1_1"> <pathelement path ="lib/commons-beanutils.jar"/> <pathelement path ="lib/commons-digester.jar"/> <pathelement path ="lib/struts_1_1_b1.jar"/> <pathelement path ="classes"/> <pathelement path ="${classpath}"/> </path> <!-- Check timestamp on files, if file not stale won't recompile --> <target name="prepare"> <tstamp/> </target> <!-- Copy any resource or configuration files --> <target name="resources"> <copy todir="classes" includeEmptyDirs="no"> <fileset dir="src/java"> <patternset> <include name="**/*.conf"/> <include name="**/*.properties"/> <include name="**/*.xml"/> </patternset> </fileset> </copy> </target> <!-- Normal build of application --> <target name="compile" depends="prepare,resources"> <!-- Remove "jikes" value if you must use javac instead --> <property name="build.compiler" value="jikes"/> <javac srcdir="src" destdir="classes"> <classpath refid="${compile.classpath}"/> </javac> </target> <!-- Remove classes directory for clean build --> <target name="clean" description="Prepare for clean build"> <delete dir="classes"/> <mkdir dir="classes"/> </target> <!-- Build Javadoc documentation --> <target name="javadoc" description="Generate JavaDoc API docs"> <delete dir="./doc/api"/> <mkdir dir="./doc/api"/> <javadoc sourcepath="./src/java" destdir="./doc/api" classpath="${servlet.jar}:${jdbc20ext.jar}" packagenames="app.*" author="true" private="true" version="true" windowtitle="Jakarta Struts Logon API Documentation" doctitle="&lt;h1&gt;${project.title} Documentation (Version ${project.version})&lt;/h1&gt;" bottom="Copyright &#169; 2001-2002 - Apache Software Foundation"> <classpath refid="${compile.classpath}"/> </javadoc> </target> <!-- Build binary distribution --> <target name="dist" description="Create binary distribution"> <mkdir dir="${dist.path}"/> <jar jarfile="${dist.path}/${project.dist}.jar" basedir="./classes" manifest="../META-INF/MANIFEST.MF"/> <war warfile="${dist.path}/${project.dist}.war" basedir="../" excludes="./web.xml,../META-INF/MANIFEST.MF" webxml="web.xml" manifest="../META-INF/MANIFEST.MF"/> </target> <!-- Build entire project --> <target name="project" depends="clean,prepare,compile,javadoc,dist"/> </project>

Homework and Reading [16/16]