This post originated from an RSS feed registered with Java Buzz
by John Topley.
Original Post: Jakarta Struts De-Mystified Part 2
Feed Title: John Topley's Weblog
Feed URL: http://johntopley.com/posts.atom
Feed Description: John Topley's Weblog - some articles on Ruby on Rails development.
Last time I introduced this series of articles and the Web Forum application, and I explained what would and would not be covered. This time I'm going to cover the persistence and business object layers, and we'll roll our sleeves up and cut some code.
Persistence Layer
The persistence layer in the Web Forum application is deliberately 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.
My DAOs have the appropriate SQL statements embedded in them, although this probably isn't good practice because database administrators like such things to be externalised so they can tune them. I have also created an abstract superclass for all my DAOs, 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:
Field
Type
Nullable
Key
Extra
PostID
int(10) unsigned
Primary
Auto increment
Subject
text
Yes
ReplyCount
int(10) unsigned
UserID
int(10) unsigned
CreationDate
datetime
Message
text
ParentID
int(10) unsigned
—The most interesting feature of this table is the ParentID column. This 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 getting updated to the current date and time whenever a reply was added to that topic.
This is the schema for the users table:
Field
Type
Nullable
Key
Extra
UserID
int(10) unsigned
Primary
Auto increment
Username
varchar(8)
Password
varchar(16)
Forenames
varchar(64)
Surname
varchar(64)
CreationDate
date
The MySQL SQL script to create 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 creating 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, as 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:
A section of the Web Forum class model. Click to view the entire model.
—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 corresponding to the columns in the posts table, and overloaded constructors which 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 a new user. In fact, I made the getUsers method deprecated after I discovered that I'd coded it but didn't actually call it from anywhere! The User class has attributes mirroring the columns in the users table, as well as a convenience getDisplayName method that returns the user's forenames and surname with a space character in the middle.
Project Structure
The organisation of the source code folder tree is shown on the left.
The source code folder tree.
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 then they can 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 out 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:
—All this page does is transfer control to the Struts framework, because if we're going to use a framework then 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, which 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 for us to a Struts logical URL. More about that in a moment.
One thing to note about the Web Forum application is that I'm using the Servlet 2.3 specification syntax for referencing 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:
—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 easily move resources around 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 Forwards to be configured 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 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 Action Mappings, which again, are logical URLs. The Struts convention is that Action Mappings 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:
To recap where we've got to, 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 Action Mapping 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:
—The mapping parameter is an ActionMapping object representing the ActionMapping that invoked this Action. The form parameter is used if an HTML form is associated with the request. This topic will be covered later in this series. 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 where 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 being passed as parameters to methods.
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());
—A new PostsDAO object is instantiated and its getTopics method 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. 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 a String parameter that is 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 that are local in scope to a single Action Mapping. 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 Action Mapping to hold a reference to the JSP that displays the list of topics:
—After lots of indirection, we finally have a physical path to a page! Although topics.jsp is included with the source code downloads so you can see the list of topics, I'll leave the explanation of how it works until next time.
Configuring The MySQL Data Source
Before the application can be run using JDeveloper, a data source for the MySQL database needs to be created, using the steps given below:
Copy mysql-connector-java-3.0.11-stable-bin.jar to <JDEVHOME>/jdk/jre/lib/ext.
Add a global data source named WebForumDS to <JDEVHOME>/jdev/systemX.X.X.X.XXXX/oc4j-config/data-sources.xml as shown below:
This installment has been quite a lot longer than I'd anticipated, but we've covered a lot of important ground. Next time we'll take a look at how the Topics page works and we'll add the code that allows the user to click on a topic to view that topic and any replies to it.