![]() | ||||
| Home | Software | Courses | For Programmers | Contact Us |
A simple Struts applicationIn this tutorial we'll see how to build, compile and deploy a simple Struts application. You can download the complete source code (and Windows compile and deploy batch files). The webapp specsI'm sure you've seen pages that register a "new user". The user is essentially asked to specify a userid and password (twice). We'll implement just such a page. I don't mean to disappoint you, but you should know right away that:
I've picked this example simply to illustrate the basics of writing, compiling and deploying a Struts webapp. OK. Now we understand each other, on to our specs:
The webapp designRemember the MVC pattern we discussed in the first tutorial? We'll try to apply that pattern in the design of our application:
7 steps to Struts heavenBuilding your Struts webapp is essentially a 7 step procedure:
We'll follow this recipe fairly closely although not in sequence, to make the exposition clearer. Data Access and PersistenceWe have just two pieces of data that we need to store - the userid and password. So our model needs a function like: public void save(String userid, String pwd) We also want to retrieve the userid for display in the "you're registered" page. It isn't obvious why such functionality should go into the model, but this is a common pattern in Struts. Accept this for now. This means we need a function like: public String getUserid() Building your application around interfaces is usually a good idea. The following interface might specify all the functionality we need: (listing for RegistrationModel)
package net.thinksquared.struts;
public interface RegistrationModel{
public static final int OK = 0;
public static final int ERROR_USER_EXISTS = 1;
public static final int ERROR_UNKNOWN = 2;
public int save(String userid,String pwd);
public String getUserid();
}
The (listing for DummyRegistrationModel)
package net.thinksquared.struts;
import java.util.HashMap;
public class DummyRegistrationModel implements RegistrationModel{
private String _userid = null;
private static HashMap dbase = new HashMap();
public int save(String userid, String pwd){
try{
//critical section:
synchronized(dbase){
if(dbase.containsKey(userid)) return ERROR_USER_EXISTS;
dbase.put(userid,pwd);
_userid = userid;
return OK;
}
}catch(Exception e){
return ERROR_UNKNOWN;
}
}
public String getUserid(){ return _userid; }
}
I've put the portion of code that checks if the user exists and saves userid-password pair, into a synchronized block because we need these operations to be atomic. Notice also that we've used Form ValidationWe need to check that the given userid and passwords aren't too short or too long. We also have to check that the two passwords keyed in match. The code listed below does this. Study the code before reading the commentary following it. (listing for
RegistrationForm)
package net.thinksquared.struts;
import javax.servlet.http.*;
import org.apache.struts.action.*;
public final class RegistrationForm extends ActionForm{
private final int MIN_USERID_LENGTH = 5;
private final int MAX_USERID_LENGTH = 15;
private final int MIN_PWD_LENGTH = 8;
private final int MAX_PWD_LENGTH = 20;
private String _userid = null;
private String _pwd = null;
private String _pwd2 = null;
public String getUserid(){ return _userid; }
public void setUserid(String userid){ _userid = userid; }
public String getPassword(){ return _pwd; }
public void setPassword(String pwd){ _pwd = pwd; }
public String getPassword2(){ return _pwd2; }
public void setPassword2(String pwd2){ _pwd2 = pwd2; }
public void reset(ActionMapping mapping, HttpServletRequest request){
_userid = _pwd = _pwd2 = null;
}
public ActionErrors validate(ActionMapping mapping,
HttpServletRequest request){
ActionErrors errors = new ActionErrors();
if( _userid == null ){
errors.add("userid",
new ActionMessage("registration.error.userid.missing"));
}else if( _userid.length() < MIN_USERID_LENGTH ){
errors.add("userid",
new ActionMessage("registration.error.userid.short"));
}else if( _userid.length() > MAX_USERID_LENGTH ){
errors.add("userid",
new ActionMessage("registration.error.userid.long"));
}else if( !_pwd.equals(_pwd2)){
errors.add("password",
new ActionMessage("registration.error.password.mismatch"));
}else if( _pwd.length() < MIN_PWD_LENGTH ){
errors.add("password",
new ActionMessage("registration.error.password.short"));
}else if( _pwd.length() > MAX_PWD_LENGTH ){
errors.add("password",
new ActionMessage("registration.error.password.long"));
}
return errors;
}
}
When the user fills in the web form and clicks "Submit", the form is processed by Struts, which then populates an instance of Struts knows which Once the If errors.add("password", new ActionMessage("registration.error.password.long"));
The "password" label is a reference used by the JSP page indicating what error to display. For example, the following Struts tag on the JSP page: <html:errors property="password" /> would cause all error messages labelled "password" to be displayed on the web page. The references to " Lastly, Processing Business LogicOnce the form has been validated, the data contained within it needs further processing - we need to save the data, checking to make sure that the userid is avaliable. We have to prompt the user if there's an error and display a "you're registered" page if everything's hunky dory. This business logic is performed within a subclass of Struts' (listing for
RegistrationAction)
package net.thinksquared.struts;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.struts.action.*;
import org.apache.struts.util.*;
import org.apache.commons.beanutils.*;
public final class RegistrationAction extends Action{
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception{
ActionMessages errors = new ActionMessages();
String userid = (String) PropertyUtils.getSimpleProperty(form,"userid");
String pwd = (String) PropertyUtils.getSimpleProperty(form,"password");
RegistrationModel model = new DummyRegistrationModel();
int err = model.save(userid,pwd);
if( err == RegistrationModel.ERROR_USER_EXISTS ){
errors.add("userid",
new ActionMessage("registration.error.userid.exists"));
}else if( err == RegistrationModel.ERROR_UNKNOWN ){
errors.add("userid",
new ActionMessage("registration.error.data.unknown"));
}
if(err == RegistrationModel.OK){
request.setAttribute("registration-successful",model);
return mapping.findForward("RegistrationSuccessful");
}else{
saveErrors(request,errors);
return (new ActionForward(mapping.getInput()));
}
}
}
The first thing to note is that all the action goes in the Unlike the We instantiate an
new ActionForward(mapping.getInput())
mapping.findForward("RegistrationSuccessful")
The first line instantiates a new The second line simply looks up the Similar to We have to use a function to "register" the user errors with Struts: saveErrors(request,errors); indicates to Struts that it should hold errors against the given If there are no errors, we're home free and can display the "you're registered" page. Take a closer look at the code that does this:
if(err == RegistrationModel.OK){
request.setAttribute("registration-successful",model);
return mapping.findForward("RegistrationSuccessful");
}
We know that the "you're registered" page is referenced by " "You have successfully registered with the user ID xxxxx". How will the page know what the userid is? Well, that JSP page will contain a tag: <bean:write name="registration-successful" property="userid" /> When Struts processes this line, it looks up the " Struts calls this request.setAttribute("registration-successful",model);
does - it sets up the request's attribute " The ViewIn our design we made the decision to use just one JSP page to both receive data from the user and to display the "you're registered page". The JSP for this is: (listing for index.jsp)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="/tags/struts-bean" prefix="bean" %>
<%@ taglib uri="/tags/struts-html" prefix="html" %>
<%@ taglib uri="/tags/struts-logic" prefix="logic" %>
<html:html>
<head>
<title><bean:message key="registration.jsp.title"/></title>
<style type="text/css">
.error { color: red; font-weight:bold }
</style>
</head>
<body>
<h1><bean:message key="registration.jsp.heading"/></h1>
<logic:present name="registration-successful" scope="request">
<h2>
<bean:message key="registration.jsp.registration-successful"/>
<bean:write name="registration-successful" property="userid" />
</h2>
</logic:present>
<logic:notPresent name="registration-successful" scope="request">
<html:form action="Registration.do" focus="userid">
<dl>
<dt><bean:message key="registration.jsp.prompt.userid"/></dt>
<dd>
<html:text property="userid" size="20" />
<span class="error"><html:errors property="userid" /></span>
</dd>
<dt><bean:message key="registration.jsp.prompt.password"/></dt>
<dd>
<html:text property="password" size="20" />
<span class="error"><html:errors property="password" /></span>
</dd>
<dt><bean:message key="registration.jsp.prompt.password2"/></dt>
<dd><html:text property="password2" size="20" /></dd>
<dt><html:submit property="submit" value="Submit"/>
<html:reset/>
</dt>
</dl>
</html:form>
</logic:notPresent>
</body>
</html:html>
The first line describes the type of output Struts should produce. The next 3 lines: <%@ taglib uri="/tags/struts-html" prefix="html" %> <%@ taglib uri="/tags/struts-bean" prefix="bean" %> <%@ taglib uri="/tags/struts-logic" prefix="logic" %> are analogous to " These Struts custom tags are commands to perform substitutions, based on data contained in one of two places:
We can see examples of (1) when the JSP looks for errors to display: <html:errors property="password" /> This displays errors that have been saved with the property "password". An example of (2) is: <bean:message key="registration.jsp.heading"/> which displays the the value of the key " Another example of (2) is the
<logic:present name="registration-successful" scope="request">
<h2>
<bean:message key="registration.jsp.registration-successful"/>
<bean:write name="registration-successful" property="userid" />
</h2>
</logic:present>
The Such attributes have scope. In this code: <logic:present name="registration-successful" scope="request"> the scope is limited to the request itself. Where was this attribute created and how did it come to have this narrow scope? We see the answer in request.setAttribute("registration-successful",model);
One other thing about attributes - they may either be simple or complex. Simple attributes have just Complex attributes are really objects that have <bean:write name="registration-successful" property="userid" /> When Struts looks up the value of " Lastly, you can see that the form is submitted to " The properties fileOur single JSP file has no text in it. All text is referenced through property keys, stored in a properties file ( we can specify more than one ) that Struts loads at startup. For simple applications, this is useful and simplifies internationalization. We've called this file (listing for Application.properties)
;---------------------------------------------------- ;---------- file: Application.properties ---------- ;---------- Resources for registration.jsp ---------- ;---------------------------------------------------- registration.jsp.title=Register New User registration.jsp.heading=Register New User registration.jsp.prompt.userid=Please enter a userid (between 5 to 15 characters): registration.jsp.prompt.password=Please enter a password (between 8 to 20 characters): registration.jsp.prompt.password2=Please re-enter the password: registration.jsp.registration-successful=You are successfully registered with the userid ;---------- validation and error messages------------ registration.error.userid.missing=Please key in a userid. registration.error.userid.exists=Sorry, this userid is taken! Please try another. registration.error.userid.short=Your userid is too short. registration.error.userid.long=Your userid is too long. registration.error.password.short=Your password is too short. registration.error.password.long=Your password is too long. registration.error.password.mismatch=The passwords you keyed in don't match. Please try again. registration.error.data.unknown=Error saving userid and password. I hope you can see how by factoring out text from JSP we can carry out internationalization painlessly - no need for multiple JSPs just create a new properties file. We'll tackle internationalization in a later tutorial. Putting it all togetherFinally! We're now in a position to put together our application. Let's think of the things we need to specify:
That's quite a shopping list! You probably know by now that there are just 2 configuration files involved:
The first two questions are handled by (listing for
web.xml)
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
<web-app>
<display-name>Struts Blank Application</display-name>
<!-- Standard Action Servlet Configuration (with debugging) -->
<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>
<init-param>
<param-name>debug</param-name>
<param-value>2</param-value>
</init-param>
<init-param>
<param-name>detail</param-name>
<param-value>2</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<!-- Standard Action Servlet Mapping -->
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<!-- The Usual Welcome File List -->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!-- Struts Tag Library Descriptors -->
<taglib>
<taglib-uri>/tags/struts-bean</taglib-uri>
<taglib-location>/WEB-INF/struts-bean.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/tags/struts-html</taglib-uri>
<taglib-location>/WEB-INF/struts-html.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/tags/struts-logic</taglib-uri>
<taglib-location>/WEB-INF/struts-logic.tld</taglib-location>
</taglib>
</web-app>
As you can see, the "welcome file list" tag specifies the start page of our webapp:
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
The custom tags's URIs are specified in a "
<taglib>
<taglib-uri>/tags/struts-html</taglib-uri>
<taglib-location>/WEB-INF/struts-html.tld</taglib-location>
</taglib>
When we later deploy the webapp, we'll have to copy the So, it stands to reason that if we wanted to change the name of our start page, or if we wanted to add more custom tags, we'd have to edit the The rest of our shopping list revolves around (listing for
struts-config.xml)
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
<form-beans>
<form-bean name="RegistrationForm"
type="net.thinksquared.struts.RegistrationForm"/>
</form-beans>
<action-mappings>
<action path="/Registration"
type="net.thinksquared.struts.RegistrationAction"
name="RegistrationForm"
scope="request"
validate="true"
input="/index.jsp">
<forward name="RegistrationSuccessful" path="/index.jsp"/>
</action>
</action-mappings>
<message-resources parameter="Application"/>
</struts-config>
Struts finds the <message-resources parameter="Application"/> Property files are usually located in the classpath, along with the <message-resources parameter="net.thinksquared.struts.Application"/> We tell Struts about our form bean,
<form-beans>
<form-bean name="RegistrationForm"
type="net.thinksquared.struts.RegistrationForm"/>
</form-beans>
The Lastly, we specify the flow of control for each page in the "
<action path="/Registration"
type="net.thinksquared.struts.RegistrationAction"
name="RegistrationForm"
scope="request"
validate="true"
input="/index.jsp">
<forward name="RegistrationSuccessful" path="/index.jsp"/>
</action>
In this code fragment above, forms in Further, the flow of control ends at just one destination: the JSP page referred to as " <forward name="RegistrationSuccessful" path="/index.jsp"/> Recall that the name " return mapping.findForward("RegistrationSuccessful");
The "mapping" object represents action mappings found in CompilingBefore you can compile the webapp, you need to put the Struts jar files in your development " Compile with the
javac -deprecation
-d deploy/WEB-INF/classes
-classpath lib/struts.jar;lib/commons-beanutils.jar;lib/servlet-api.jar
./src/*.java
The compilation process ends with a WAR (Web Archive file), which is just a Jar file but with a
Once your script has done this, go into the deploy directory and issue the jar cvf registration.war . and you'll have a DeployingDeploying the WAR file in most servlet containers is simple:
You may find it helpful to automate these steps with a batch file. In SummaryWe've learnt how to write and deploy a simple Struts application. I strongly recommend running the webapp yourself. | ||||
| Copyright 2005 Thinksquared, all rights reserved. Java is a trademark of Sun Microsystems. | ||||