by Sandeep Desai (http://www.thedesai.net)
Apache Struts is a Model 2 Framework it implements the FrontController pattern i.e. all servlet requests go through Struts ActionServlet
Struts ActionServlet intercepts request and forwards to RequestProcesser.process()
Messages Description
1.1.1
retrieve and return the ActionForm bean associated with the
mapping, creating one if necessary
1.1.2
populates the ActionForm bean with the input fields of the
HTML form
1.1.3
validates the input field values and creates error messages if
validation errors
1.1.4
acquires an UserAction instance to process the request,
calling the overwritten execute method of UserAction
1.1.4.1
retrieves data from the UserActionForm bean by getProperties
methods
1.1.4.2
calls business services through the BusinessDelegate
1.1.4.3
populates a value object bean (optional)
1.1.4.4
forwards to the specified destination in struts-config.xml
2
the HelperBean
3 and / or from the ActionForm bean
<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-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<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>
Basic WEB-INF/struts-config.xml file
<?xml version="1.0"
encoding="windows-1252" ?>
<!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>
<action-mappings>
<action
path="/userRegistration"
type="net.thedesai.mule.view.FirstAction">
<forward
name="success" path="/firstSuccess.jsp"/>
</action>
<action
path="/page1" forward="unknown"/>
</action-mappings>
<message-resources
parameter="hibernate.ApplicationResources"/>
</struts-config>
WEB-INF/struts-config.xml
<!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="userRegistrationForm"
type="net.thedesai.mule.view.UserRegistrationForm"/>
<form-bean
name="userRegistrationMultiForm"
type="net.thedesai.mule.view.UserRegistrationMultiForm"/>
<form-bean
name="userRegistrationValidationForm"
type="net.thedesai.mule.view.UserRegistrationValidationForm"/>
</form-beans>
<global-exceptions>
<exception
type="net.thedesai.mule.view.PhoneException"
key="userRegistration.phone.exception617"
path="/userRegistrationException.jsp"/>
</global-exceptions>
<global-forwards>
<!-- Default
forward to "Welcome" action -->
<!-- Demonstrates
using index.jsp to forward -->
<forward
name="welcome" path="/Welcome.do"/>
</global-forwards>
<action-mappings>
<!-- action
attributes
attribute:
specifies name of ActionForm if no attribute then name becomes attribute
className:
custom config object
include: JSP
that will handle this request (mutually exclusive with input)
input:
inputView of an action, i.e. JSP that has Form on validation failure redirect
back to form
name:
identifies the ActionForm
path:
incoming request path that this action maps to
parameter:
similar to servlet init-parameters
roles: J2EE
security roles
type: Action Handler
class name
scope:
request of session scope for the ActionForm
Unknown:
Default action map to handle unknown URL
validate:
call validate on ActionForm note Action.execute() not called if validation
fails
-->
<action
path="/userRegistrationFirst"
type="net.thedesai.mule.view.UserRegistrationAction">
<forward
name="success" path="/regSuccess1.jsp"/>
</action>
<action
path="/userRegistration"
type="net.thedesai.mule.view.UserRegistrationAction"
name="userRegistrationForm" attribute="user"
input="/userRegistration.jsp">
<exception
type="net.thedesai.mule.view.PhoneException"
key="userRegistration.phone.exception781"
path="/userRegistrationException.jsp"/>
<forward
name="success" path="/regSuccess2.jsp"/>
<forward
name="failure" path="/regFailure.jsp"
redirect="false"/>
</action>
<action
path="/userRegValidation"
forward="/userRegistrationValidation.jsp"/>
<action
path="/userRegistrationValidation"
type="net.thedesai.mule.view.UserRegistrationAction"
name="userRegistrationValidationForm"
attribute="user" scope="session"
input="/userRegistrationValidation.jsp">
<exception
type="net.thedesai.mule.view.PhoneException"
key="userRegistration.phone.exception781"
path="/userRegistrationException.jsp"/>
<forward
name="success" path="/regSuccess2.jsp"/>
<forward
name="failure" path="/regFailure.jsp"
redirect="false"/>
</action>
<action
path="/Welcome" forward="/welcome.jsp"/>
<action
path="/displayAllUsers"
type="net.thedesai.mule.view.DisplayAllUsersAction">
<forward name="success"
path="/userRegistrationList.jsp"
redirect="false"/>
</action>
<action
path="/userRegWizard"
forward="/userRegistrationMultiPage1.jsp"/>
<action
path="/userRegistrationMultiPage1"
type="net.thedesai.mule.view.UserRegistrationMultiAction"
name="userRegistrationMultiForm" attribute="user"
scope="session"
input="/userRegistrationPage1.jsp">
<exception
type="net.thedesai.mule.view.UserRegistrationException"
key="userRegistration.exception"
path="/userRegistrationException.jsp"/>
<forward
name="success" path="/userRegistrationMultiPage2.jsp"/>
</action>
<action
path="/userRegistrationMultiPage2"
type="net.thedesai.mule.view.UserRegistrationMultiAction"
name="userRegistrationMultiForm" attribute="user"
scope="session"
input="/userRegistrationMultiPage2.jsp">
<exception
type="net.thedesai.mule.view.UserRegistrationException"
key="userRegistration.exception"
path="/userRegistrationException.jsp"/>
<forward
name="success" path="/regSuccess2.jsp"/>
</action>
</action-mappings>
<message-resources
parameter="application"/>
<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property
property="pathnames"
value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"/>
</plug-in>
</struts-config>
public class UserRegistrationForm extends ActionForm {
public
UserRegistrationForm() { }
public void
setName(String name) { this.name = name;
}
public String
getName() { return name; }
// TODO How to get
get Struts to handle this exception
public void
setPhone(String phone) throws PhoneException {
if
(phone.startsWith("781"))
throw new
PhoneException();
this.phone =
phone;
}
public String
getPhone() { return phone; }
private String name;
private String phone;
public void reset(ActionMapping
ActionMapping, HttpServletRequest HttpServletRequest) {
name = null;
phone =
null;
}
public ActionErrors validate(ActionMapping
mapping,
HttpServletRequest request) {
ActionErrors
errors = new ActionErrors();
if (name == null
|| name.trim().equals("")) {
errors.add("name", new
ActionError("userRegistration.name.problem"));
}
return errors;
}
}
ActionForms can be strongly typed, Struts will convert String and String Arrays into primitive and primitive arrays
Struts converts request parameters into a HashMap and then uses common BeanUtils to populate the ActionForm with the request parameters.
Recommend keeping ActionForm property as string to avoid problem where property is integer and user enters “foo” the ActionForm will get it as 0
ActionForms should not touch the Application Model objects if there is a one to one relationship between form DTO and model DTO use BeanUtils.copyProperties to move and convert data
For application with common form elements implement a super ActionForm or use mapped back ActionForms (mapped back means you use a Map for properties)
In the example below address would be an Address JavaBean class with setState() method
similarly Phones would be an array Phone[] phones;
For user <bean:write name="user" property="name"/>
For user state <bean:write name="user" property="address.state"/>
For user home phone <bean:write name="user" property="phones[0].number"/>
<logic:iterate id=”lineitem” indexId=”index” name=”adminUsersForm” property=”usersList”>
public class FooForm extends ActionForm() {
private Map
dynamicProps = new HashMap();
public Object
getDynamicProps(String key) {
return
dynamicProps.get(key);
}
public void setDynamicProps(String key, Object value) {
return
dynamicProps.put(key,value);
}
}
<html:form..>
<html:text property=”dynamicProps(bar)”>
</html:form>
//Access in Action as
userForm.getDynamicProps(“foo”);
<?xml version="1.0" encoding="ISO-8859-1"
?>
<!DOCTYPE form-validation PUBLIC
"-//Apache
Software Foundation//DTD Commons Validator Rules Configuration 1.1.3//EN"
"http://jakarta.apache.org/commons/dtds/validator_1_1_3.dtd">
<form-validation>
<!--
This is a minimal
Validator form file with a couple of examples.
-->
<global>
<!-- An
example global constant
<constant>
<constant-name>postalCode</constant-name>
<constant-value>^\d{5}\d*$</constant-value>
</constant>
end example-->
</global>
<formset>
<!-- minlength
rule defined in validation-rules.xml -->
<form
name="user" >
<field
property="name" depends="required,minlength,maxlength">
<arg
key="userRegistration.name" position="0"/>
<var>
<var-name>min-length</var-name>
<var-value>5</var-value>
</var>
<var>
<var-name>max-length</var-name>
<var-value>20</var-value>
</var>
</field>
</form>
<!-- An
example form -->
<form
name="logonForm">
<field
property="username" depends="required">
<arg
key="logonForm.username"/>
</field>
<field
property="password" depends="required,mask">
<arg
key="logonForm.password"/>
<var>
<var-name>mask</var-name>
<var-value>^[0-9a-zA-Z]*$</var-value>
</var>
</field>
</form>
</formset>
<!-- An example
formset for another locale -->
<formset
language="fr">
<constant>
<constant-name>postalCode</constant-name>
<constant-value>^[0-9a-zA-Z]*$</constant-value>
</constant>
<!-- An
example form -->
<form
name="logonForm">
<field
property="username" depends="required">
<arg
key="logonForm.username"/>
</field>
<field property="password"
depends="required,mask">
<arg
key="logonForm.password"/>
<var>
<var-name>mask</var-name>
<var-value>^[0-9a-zA-Z]*$</var-value>
</var>
</field>
</form>
</formset>
</form-validation>
Do the following in Struts Action execute()
Struts tags predate JSTL so use JSTL where appropriate
Add the <taglib> to web.xml
<%@ taglib uri=”struts-html” prefix=”html” %>
<%@ taglib uri=”struts-bean” prefix=”bean” %>
Don’t link JSPs from JSP use an Action forward
<html:link forward=”fooForward”>Foo</html:link>
Avoid using page and href attribute use forward or action
Can add parameters by
<html:link page=”foo.do” paramId=”boolProp”
paramName=”myBean” paramProperty=”nested.boolProp”>Foo</html:link>
will call myBean.getNested().boolProp()
<a href=”/mystrutsapp/foo.d?boolProp=”false”>
Can all pass HashMap
<% HashMap ... pageContext.setAttribute(“newValues”,
newValues); %>
<html:link action=”/foo” name=”newValues”>
<html:base target=”/foo/bar.jsp”> will convert to http://localhost:8080/strutsApp/foo/bar.jsp
<html:button property=”action”>Submit</html:button>
<html:cancel>Cancel</html:cancel>
<html:cancel><bean:write
key=”form.cancel”/></html:cancel>
<html:checkbox property=”booleanProperty”/>
html:errors will display validation errors
<html:errors/> For formatting message add errors.header
etc to resource bundle
<html:errors property=”someFieldName”/> use this to display error next to field
<html:file ...> upload file
<html:form action=”/UserUpdate” method=”post”> ... </html:form>
<html:hidden property=”foo”>
<html:html> renders html content
<html:image> image button specify image using src or page
attribute, supports localization
<html:img> renders <img> element
<html:javascript/> renders Javascript validation methods
<html:messages> display collection of messages, can use
for display validation error message next to field
<html:messages id=”message” message=”true”>
<li><%=message%></li>
</html:messages>
<html:multibox> manage array of checkboxes, works with
array of strings, if string in array then checkbox selected
<html:select property=”pizzaSize”> //combo box, can select
multiple value
<html:option
value=”Small”>Small<html:option>
<html:option
value=”Medium” key=”pizza.medium”/>
<html:select>
<html:password>
<html:radio> radio button
<html:reset> reset button
<html:rewrite> Useful client side Javascript based URL
link similar to <html:link>
<html:submit> submit button
<html:text> text
input field
<html:textarea> text area field takes rows and cols
attribute
<html:xhtml>
<%@ taglib uri=”struts-bean” prefix=”bean” %>
<bean:include id=”>
<bean:cookie> (Use JSTL)
<bean:header> request header (Use JSTL
<jsp:getProperty>
<bean:parameter> request parameter
<bean:define> (use JSTL core:set)
<bean:include> (Use jsp:include)
<bean:message> (get message from resource bundle)
<bean:page id=”reqObj” property=”request”> create
scriptlet variables, get request property and store in reqObj
<bean:resource>
<bean:size id=”count” name=”emps”> count items stored in
array, collection or map and store in the count object
<bean:struts id=”uForm” formBean=”userForm”> copy struts
object (ActionForms, ActionForward or an ActionMapping) into page scoped
scriptlet variable
<bean:write name=”userReg” property=”phone”
scope=”request”>
<%@ taglib uri=”struts-logic” prefix=”logic” %>
<logic:empty name=”fooBean”> (use JST EL empty operator)
if scriptlet variable is null an empty string, collection or map
<logic:notEmpty>
<logic:equal name=”bean” property=”fooProp” value=”<%=
foo%>”> set value <logic:equal>
logic: notEqual,lessThan, greaterThan, lessEqual, greaterEqual
<logic:forward name=”foo”> (forward to ActionForward )
references in
struts-config.xml -> <global-forwards> <forward name=”foo”
path=”/foo.jsp”/> <global-forwards>
<logic:redirect> does a response.sendRedirect() avoid
using this tag
iterator over collection, enumeration, iterator, map or array
print 5th to 10th (offset, length are
optional attributes)
<logic:iterate id=”emp” name=”dept” property=”employees”
scope=”request” offset=”5” length=”10>
<bean:write
name=”emp” property=”phone”/>
</logic:iterate>
String equals and not equals
<logic:match header=”User-Agent”
value=”Mozilla”>Mozilla</logic:match>
<logic:notMatch>
<logic:present> check to see if headers, request
parameters, cookies, JavaBeans or JavaBean properties are present and not
equals to null
Common Attributes (Bold are required)
property |
Associates field with property from corresponding ActionForm |
accessKey |
Similar to mnemonic in Swing |
alt |
alternative text |
disabled |
|
indexed |
indexed property name, works only with logic:iterate tag |
onblur,onchange,onclick, ondblclick,onfocus,onkeydown, onekypress,onkeyup,onkeydown, onmousemove,onmouseout,onmouseover, onmouseup |
allows you to write event handler when element loses focus |
style |
in line CSS style |
styleClass |
CSS class |
sytleId |
style id |
tabIndex |
tab order |
title |
tooltip for input |
titleKey |
tooltip for input from resource bundle |
value |
label for butto or default value for input |
MockStrutsTestCase methods
getMoc
public class TestLoginAction extends MockStrutsTestCase {
public TestLoginAction(String testName) { super(testName); }
public void setUp() { super.setUp(); }
public void tearDown() { super.tearDown(); }
public void testSuccessfulLogin() {
setRequestPathInfo("/login");
addRequestParameter("username","deryl");
addRequestParameter("password","radar");
actionPerform();
verifyForward("success");
assertEquals("deryl",(String) getSession().getAttribute("authentication"));
verifyNoActionErrors();
}
}
Links
Credits
· Struts Sequence diagram, submitted diagrams by Jean-Michel Garnier on October 02. Based on an article by Jean-Michel Garnier in the http://rollerjm.free.fr web site. Copyright (c) 1999-2002 The Apache Software Foundation. All rights reserved.