STRUTS 2


introduction | struts webapp basics | struts webapp features | practicalities

Last updated: February 2008

Since 2001 Struts has become the de facto standard in the Java webapp development world. Struts 2 is a major upgrade of Struts. The new version takes a lot of ideas and code from another Web application framework called WebWork from OpenSymphony.




introduction

For the Struts 2 version of WebOfContacts these technologies were used:
  • Java 6 (a.k.a. 1.6)
  • Struts 2.0.11
  • Hibernate 3.2
  • Tomcat 6.0.14
  • Sitemesh 2.3
  • MySQL 5.0.45
  • JUnit 3
  • Ant 1.7
  • Eclipse 3.4
If you want to download the .war and run this application on your own machine, then you can as Struts 2, Hibernate, and Sitemesh are included as jars in the webapp. However you will already need to have a version of Java at 5 or greater, a recent version of Tomcat, and set up the database contactsdb. Then download the WAR (make sure to retain the .war extension) from this site into your Tomcat's webapps directory, and view the application in your browser at http://localhost:8080/WebOfContactsStruts2/ (log in as admin/admin). The full source is available for download as an Eclipse project.



struts webapp basics

As in Struts 1, the flow control of the application is defined in an XML file. For Struts 2 it is called struts.xml. JSPs call actions defined in the struts.xml, which in turn load JSPs. For example, there is a link in leftnav.jsp to /contact-list.action. The corresponding action in struts.xml looks similar to this:
  <action name="contact-list" 
      class="com.oranda.webofcontacts.struts2.actions.ContactAction" 
      method="list">
	<result name="success">/WEB-INF/jsp/listContacts.jsp</result>
  </action>
This means that the method list() inside the class ContactAction will be called, and that if it returns SUCCESS, the page listContacts.jsp will be displayed.

ContactAction does not have an execute() method, but it is quite common in Struts 2 as in Struts 1 to have this as a central "default" method for the action. However, you no longer need to pass in lots of parameters (request, response, action form, etc). execute takes no parameters. Objects are accessed through dependency injection. For instance, if you need to access the request, you have the action implement ServletRequestAware interface and make the request an instance variable along with a setter.

Another way in which the architecture has been simplified in Struts 2 is that there is no longer any need to have an ActionForm duplicating each domain object. For instance, consider the central entity in our application: Contact. This is passed directly into ContactAction by the framework calling setContact(). Inside the JSP, the contact is accessed by calling the getContact() method in ContactAction via tags.

Struts 2 has a new tag library which is imported like this

<%@ taglib prefix="s" uri="/struts-tags" %>

and used for the form controls like this:

<s:textfield required="true" label="First Name" size="12"
      name="contact.firstName" value="%{contact.firstName}"/>

For iteration and conditional control, you can also use the Struts 2 tags; however, you can still stick with JSTL tags if you are more comfortable with them and package the right JARs and TLDs. The file listContacts.jsp shows a mix of Struts 2 and JSTL tags. The Struts 2 tags are being promoted as better because they have a more powerful expression language: OGNL rather than EL.

Two possible solutions for page composition with Struts are Tiles and Sitemesh. They are used to assemble pages and give an application a consistent look with header, footer, navigation, etc. included throughout. For the WebOfContacts application, Sitemesh was selected. Sitemesh is also from OpenSymphony.

The general layout of a page is specified in pageLayout.jsp. It includes the stylesheet code and HTML markup to include files like the header, like this:

    <table id="page" cellpadding="0" cellspacing="0">
    	<tr>
		    <td colspan="2" id="header"> 
        		<%@ include file="/includes/header.jsp" %> 
        	</td>
    ...

Sitemesh recognizes a special file called decorators.xml where you can specify the files which define the layout: in this case just one, pageLayout.jsp.

    <decorator name="pageLayout" page="pageLayout.jsp">
        <pattern>/*</pattern>
    </decorator>
The /* pattern means that all pages are served according to this general template. The JSPs in the application do not need to be changed -- Sitemesh will parse them and effectively merge them with the pageLayout.jsp file. This is done by putting <decorator:head> and <decorator:body> tags in the pageLayout.jsp which are replaced by the real HTML generated by the application JSPs.

As in Struts 1, there are different ways you can organize the architecture of your application. It is extremely common in Struts 2 to use Spring, access the data tier via dependency injection, and use Spring's OpenSessionInView filter to ensure that a Hibernate session is kept open for the length of a request. However, the Struts 2 version of WebOfContacts does not use Spring; Spring is dealt with in other versions.

In WebOfContacts Struts 2 version, there is a HibernateAction which all the main actions subclass rather than subclassing Struts's own ActionSupport directly. HibernateAction is responsible for opening and closing the session, and beginning and committing transactions.

There is a thin service layer whose classes have access to a Hibernate session and whose methods assume they are inside a valid Hibernate transaction. A single UI action can potentially call several service methods within a single transaction. Here is an example of a service method in ContactServiceImpl:

public List<Contact> getAllContacts() {
	return getSession().createQuery(
			"from Contact order by priority desc").list();
}

Inside the quotes is Hibernate's generic HQL code which is translated into SQL by the Hiberate framework for MySQL.




struts webapp features

Struts 2 provides good support for form validation. In the same package as ContactAction.java there is a file ContactAction-validation.xml which is automatically picked up by the framework to find the rules which validate the contact form. Struts provides several types of validators out-of-the-box; beyond saying that a field is simply required, you can for example specify a regular expression that the entered value must match. For example here is a sample rule in ContactAction-validation.xml for phone numbers:

  <field name="contact.phoneNums">
    <field-validator type="regex">
    <param name="expression"><![CDATA[^[0-9 \-]+$]]></param>
    <message>Phone number format not valid</message>
    </field-validator>
  </field>

The Struts tags include a tag for file upload which is used in the web page like this:

  <s:file label="Photo" name="photo" size="50" accept="image/*"
      value="%{contact.photoUri}"/>

Within ContactAction which handles the form submit, there is a setter for the photo like this:

    public void setPhoto(File photoFile) {
        this.photoFile = photoFile;
    }

This means the file is now available to the action on a form submit, and it can copy the file to the filesystem using a Struts utility class, FileUtils:

	FileUtils.copyFile(this.photoFile, newPhotoFile);

The file is copied to a resource directory under the web application root, whose location on the filesystem is found calling code like this:

ServletActionContext.getServletContext().getRealPath(imageDir);

Before an action is called, the Struts 2 framework will call a stack of interceptors to do preprocessing. Like an action, an interceptor is just a Java class, but it handles a cross-cutting concern like logging or form validation. In struts.xml you can define a default interceptor stack which is run for every action but also override it for particular actions. For example, take the contact-list action defined above. The full code for this action in struts.xml is actually:

<action name="contact-list" 
           class="com.oranda.webofcontacts.struts2.actions.ContactAction" 
           method="list">
  <interceptor-ref name="loginStackBasic"/>
  <result name="success">/WEB-INF/jsp/listContacts.jsp</result>
</action>

This interceptor-ref tag here specifies that the interceptor stack used should be loginStackBasic. The loginStackBasic stack has been defined in struts.xml elsewhere and it consists of a list of standard interceptors which include login functionality but omit form validation (hence "basic").

Struts 2 comes packaged with some standard interceptors and some standard interceptor stacks defined in struts-default.xml. Whether through fault or design, authentication (login) is not one of the packaged interceptors. So the LoginInterceptor used in WebOfContacts is adapted from code by a 3rd party, Mark Menard.

The way the login interceptor operates is this: whenever an action is called, the intercept() method of LoginInterceptor is first called. This checks to see if a user handle is in the HTTP session. If it is, then invocation.invoke () calls the next interceptor in the chain and execution proceeds normally. If not, then the database is consulted. If the database query returns success, then the handle is put in session and execution proceeds normally. If the database query does not find the user, then the browser is directed to a login page (defined as the global result login in struts.xml).

Of course, an interceptor is not the only to implement authentication in Struts 2. A lot of web applications use form-based authentication or Spring's Acegi security plugin. Note that form-based authentication requires container-specific configuration: e.g. specifying the data source for your USERS table in Tomcat's server.xml.




practicalities

How can the webapp be tested? The service layer is independent of the webapp container, and so it can be tested independently -- see the folder testsrc and the package com.oranda.webofcontacts.struts2.service. The JUnit tests do need to do the work of wrapping calls in Hibernate sessions/transactions as the Struts actions are not doing it.

In this small application the UI does not have its own test cases. The StrutsTestCase software does not work with Struts 2 yet and that has left some people in doubt when migrating from Struts 1 to Struts 2. Struts heavyweight Ted Husted recommends Selenium as a tool which runs tests directly in a browser.

How usable is the framework? The architecture for Struts 2 is cleaner than its predecessor's meaning your code can be simplified. However, the typical developer will still run into the usual issues of having to track down JARs that shouldn't be missing, deal with conflicting classpaths and JNDI contexts in tools like Eclipse and Tomcat, and interpret error messages and stack traces that do not necessarily point to the real problem.

The main Struts documentation is organized as a Wiki and is very useful. It does not yet answer every question and bug a developer may run into while developing an application such as this one.