Article

First Steps with Jakarta Struts

Page: 1 2 3 Next

The Persistence Layer

The persistence layer in the Web Forum application is very simple. There are two database tables: one for posts and another for users. The posts table stores topics and their replies. The users table stores the details of the registered users of the application. These tables are accessed using data access objects (DAOs), which are just Java objects that perform simple object-relational mapping. In other words, they take a Java object and persist its contents into a table, and vice-versa.

The DAOs have the appropriate SQL statements embedded into them, although this probably isn't good practice: database administrators like such things to be externalised so they can be tuned. I have also created for all the DAOs an abstract superclass, which contains utility methods that obtain a JDBC connection from a data source and close database resources, etc.

This is the schema for the posts table:

1471_table1

The most interesting feature of this table is the ParentID column, which links replies back to their parent topic. The Subject column is null when a post is a reply and not a topic. I'm storing the number of replies to each topic in a ReplyCount column. Technically, this is redundant because it could be calculated, but I'm storing it because MySQL doesn't currently support nested SQL SELECT statements.

The CreationDate column originally auto-updated, but I changed this because the date and time of the original topic were being updated to the current date and time whenever a reply was added to that topic.

Here's the schema for the users table:

1471_table2

The MySQL SQL script that creates these tables is in src/sql/create_tables.sql. This script can be run from a query window in MySQL Control Centre. Note that it assumes the existence of a database named webforum. Also, because we're not building the functionality to create new topics in this article, you'll have to manually insert some test data so that you have some topics to display.

Business Object Layer

I didn't have to do much thinking to come up with the business objects in the application: they're pretty obvious. There are classes for individual posts and users, as well as collections of posts and users. The class model -- excluding the Struts classes -- is shown below:

1471_classmodel

Some of these classes, such as UserCookie, will be covered later in the series. As can be seen from the class model, a Posts class contains a collection of Post objects at runtime, and features methods for retrieving this collection and for adding a new post. The Post class itself has attributes that correspond to the columns in the posts table, and overloaded constructors that are invoked depending upon whether the post is a topic or a reply. Accessors and mutators (getters and setters) are not shown on the class model.

The Users class contains a collection of User objects at runtime, and features methods for retrieving this collection and for adding new users. In fact, I deprecated the getUsers method after I discovered that I'd coded it, but didn't actually call it from anywhere! The User class has attributes that mirror the columns in the users table, as well as a convenient getDisplayName method that returns the user's first names and surname with a space character in the middle.

Project Structure

The organisation of the source code folder tree is shown below:

1471_projectstructure

Java source code files go under src, and the Java package hierarchy is rooted at com.johntopley.webforum. The public_html folder corresponds to the root of the Web application.

I always put JSPs under pages because this allows them to be protected by the Web container using J2EE declarative security. Anything in public_html and its sub-folders really should be regarded as public.

I also like to separate the Struts configuration files into a config folder, although usually you'll see them stored directly under WEB-INF.

The application entry point is public_html/index.jsp, which is declared as a welcome file in the web.xml Web application deployment descriptor. Let's take a look at index.jsp:

<%@ taglib uri=http://jakarta.apache.org/struts/tags-logic  
   prefix="logic" %>  
<logic:redirect forward="ViewTopics" />

All this page does is transfer control to the Struts framework, because if we're going to use a framework, we want to be using it as soon as possible. Struts ships with a number of JSP tag libraries (taglibs), and here we're using the logic taglib. This handles the conditional generation of output text, looping over object collections and application flow management. In this case, the redirect tag performs an HTTP redirect to a Struts logical URL. More about that in a moment.

One thing to note about the Web Forum application is that we're using the Servlet 2.3 specification syntax to reference the taglibs using URIs, rather than referring to TLD files in the web.xml file. This is documented in section 5.4.3 of The Struts User's Guide.

The Heart of Struts

The heart of Struts is the config/struts-config.xml file. This file defines the flow of the application and tells Struts which classes to use for what. The Struts ActionServlet is a controller class that reads this file and receives all incoming requests for the Web application. The ActionServlet needs to be configured as a servlet in the web.xml file:

<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/config/struts-config.xml</param-value>  
 </init-param>  
 .  
 .  
 .

The main part of the struts-config.xml file covered by this article is:

<struts-config>  
 <global-forwards>  
   <forward name="ViewTopics" path="/ViewTopics.do"/>  
 </global-forwards>  
 
 <action-mappings>  
   <action  
     path="/ViewTopics"  
     type="com.johntopley.webforum.controller.action.ViewTopicsAction"  
     scope="request">  
     <forward  
       name="Topics"  
       path="/WEB-INF/pages/topics.jsp"  
     />  
   </action>  
 </action-mappings>  
</struts-config>

Struts introduces a layer of indirection into Web applications because it uses logical URLs. This means that the address you see in the browser's address bar does not correspond to the physical location of that resource on the Web server. This allows developers to move resources around easily without breaking things. The Struts name for the association of a logical name with a resource is an ActionForward, often just called a Forward. The Struts configuration file contains a global-forwards section that allows the configuration of Forwards that are available throughout a Struts application. These are effectively the application's entry points.

Another key Struts concept is the Action class. Actions are simply Java servlets, so anything that a servlet can do, an Action class can do. Actions are used to process requests for specific URLs. Generally, they should act as a thin layer around the business objects layer, which does the real work.

Actions are referred to by ActionMappings, which, again, are logical URLs. The Struts convention is that ActionMappings end with .do. The web.xml file needs to be configured so that the Struts ActionServlet is used to process any URL matching the pattern *.do, as shown here:

<servlet-mapping>  
 <servlet-name>action</servlet-name>  
 <url-pattern>*.do</url-pattern>  
</servlet-mapping>

Flow Of Events

Let's recap where we've got to so far. The index.jsp page redirects to the ViewTopics global ActionForward, and this passes control to the /ViewTopics Action. Struts knows which Action class to invoke because the type attribute in the ActionMapping gives the fully-qualified name of the associated Java class, which must inherit from org.apache.struts.action.Action, and must have an execute method with the following signature:

public ActionForward execute(ActionMapping mapping,  
                            ActionForm form,  
                            HttpServletRequest request,  
                            HttpServletResponse response)  
 throws Exception

The mapping parameter is an ActionMapping object that represents the ActionMapping that invoked this Action. The form parameter is used if an HTML form is associated with the request. The request and response parameters highlight the fact that an Action class is just a specialisation of a servlet.

The Web Forum application uses a BaseAction abstract class that inherits from the org.apache.struts.action.Action class mentioned earlier. The other Action classes within the application inherit from BaseAction, so it serves as an extension point at which functionality common to all Action classes can be added if required. This should be considered good practice.

Important: Action classes should not use instance variables, as they are not thread-safe. State should be shared by storing values in the request, session, or servlet context objects, depending on their scope.

This is the execute method in ViewTopicsAction.java:

public ActionForward execute(ActionMapping mapping,  
                            ActionForm form,  
                            HttpServletRequest request,  
                            HttpServletResponse response)  
 throws Exception  
{  
 request.setAttribute(KeyConstants.POST_LIST_KEY,  
   new PostsDAO().getTopics());  
 
 return mapping.findForward(ForwardConstants.TOPICS_PAGE);  
}

A new PostsDAO object is instantiated and its getTopics method is called. This method uses the following SQL statement to query the posts table:

SELECT p.PostID, p.Subject, p.ReplyCount, p.UserID, p.CreationDate  
FROM Posts p  
WHERE p.ParentID = 0  
ORDER BY p.CreationDate DESC

The SQL WHERE clause ensures that only topics are selected -- not replies. The getTopics method returns an instance of the Posts class, i.e., an ordered collection of Post objects. This instance is stored in the HTTP request under the key referred to by the KeyConstants.POST_LIST_KEY constant. The JSP that displays the list of topics will use this Posts object stored in the request. Finally, the findForward method of the ActionMapping class is invoked. This takes, as a String parameter, the name of a Struts Forward to pass control to. The ForwardConstants class contains all of the Forward names used within the Web Forum.

As well as global ActionForwards, Struts also has local ActionForwards. These are simply Forwards whose scope is local to a single ActionMapping. In other words, they are not globally visible within the application.

Important: Struts gives precedence to local ActionForwards over global ActionForwards.

A local Forward is used within the /ViewTopics ActionMapping to hold a reference to the JSP that displays the list of topics:

<forward name="Topics" path="/WEB-INF/pages/topics.jsp"/>

We finally have a physical path to a page! topics.jsp is included with the source code downloads for this article (linked above), so you can see the list of topics.

Building the Application Using Apache Ant

If you're not familiar with the Apache Ant build tool, spend a few moments reading Apache Ant Demystified, which should get you up to speed quickly. The build file for the Web Forum application shouldn't look too unfamiliar, although it does use some more of Ant's built-in tasks. Another key difference is that, because this application is rather more than a simple "Hello World" app, we have to deal with the classpath. The good news is that Ant makes this easy too!

If you liked this article, share the love:
Print-Friendly Version Suggest an Article

Sponsored Links