John Topley’s Weblog

Jakarta Struts Demystified Part 2

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:

FieldTypeNullableKeyExtra
PostIDint(10) unsignedPrimaryAuto increment
SubjecttextYes
ReplyCountint(10) unsigned
UserIDint(10) unsigned
CreationDatedatetime
Messagetext
ParentIDint(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:

FieldTypeNullableKeyExtra
UserIDint(10) unsignedPrimaryAuto increment
Usernamevarchar(8)
Passwordvarchar(16)
Forenamesvarchar(64)
Surnamevarchar(64)
CreationDatedate

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.
A diagram of the Web Forum class 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 below:

The source code folder tree.
A picture of the source code folder tree shown in Windows Explorer

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:

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

—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:

<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 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:

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

Flow Of Events

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:

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

—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());
  return mapping.findForward(ForwardConstants.TOPICS_PAGE);
}

—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:

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

—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:

  1. Copy mysql-connector-java-3.0.11-stable-bin.jar to <JDEVHOME>/jdk/jre/lib/ext.
  2. Add a global data source named WebForumDS to <JDEVHOME>/jdev/systemX.X.X.X.XXXX/oc4j-config/data-sources.xml as shown below:
<data-source
  name="WebForumDS"
  location="jdbc/WebForumCoreDS"
  ejb-location="jdbc/WebForumDS"
  xa-location="jdbc/xa/WebForumDS"
  inactivity-timeout="30"
  class="com.evermind.sql.DriverManagerDataSource"
  connection-driver="com.mysql.jdbc.Driver"
  username="username"
  password="password"
  url="jdbc:mysql://localhost:3306/webforum?autoReconnect=true"
/>

Source Code Downloads

To reduce the download sizes, I've split the Struts JAR files that go into WEB-INF/lib into a separate download.


Next Time

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.

Comments

There are 16 comments on this post. Comments are closed.

  • avatar max
    25 July 2004 at 19:23

    hi john, when i try to compile the application i get: E:xxxTempStruts Web ForumWebForumpublic_htmlWEB-INFpagestopics.jsp Error(9): " http://jakarta.apache.org/struts/tags-bean " is not a registered TLD namespace. Error(9): Unable to load taghandler class: http://jakarta.apache.org/struts/tags-bean Error(10): " http://jakarta.apache.org/struts/tags-html " is not a registered TLD namespace. Error(10): Unable to load taghandler class: http://jakarta.apache.org/struts/tags-html Error(11): " http://jakarta.apache.org/struts/tags-logic " is not a registered TLD namespace. Error(11): Unable to load taghandler class: http://jakarta.apache.org/struts/tags-logic E:xxxTempStruts Web ForumWebForumpublic_htmlindex.jsp Error(15): " http://jakarta.apache.org/struts/tags-logic " is not a registered TLD namespace. Error(15): Unable to load taghandler class: http://jakarta.apache.org/struts/tags-logic i've just opened the jws-file. adding the JSTL-library (by selecting it under "project properties") didn't help ... can you please do?

  • avatar John Topley
    26 July 2004 at 16:52

    The project doesn't use JSTL. Did you download the JAR files into WEB-INF/lib?

  • avatar max
    27 July 2004 at 20:59

    thank you, john, after copying these archives into WEB-INF/lib compilation succeeds. could you please point me to an step-by-step description on how to establish the jdbc-connection (in order to resolve "com.mysql.jdbc.Driver' not found" on application startup)?

  • avatar John Topley
    29 July 2004 at 07:54

    Max, Are you using JDeveloper?

  • avatar max
    29 July 2004 at 14:45

    yes, i do.

  • avatar John Topley
    29 July 2004 at 16:06

    Following the instructions in the Configuring The MySQL Data Source section should do it.

  • avatar James Tuan
    07 August 2004 at 10:48

    I can't seem to unzip it. I use Winzip32.exe and got an application error. Please help. thanks, JT

  • avatar John Topley
    07 August 2004 at 13:24

    James, Can you be a bit more specific? The Zip files unzip fine at this end.

  • avatar Dave
    03 September 2004 at 13:20

    hi john, I have some trouble configuring the data source in the struts configuration file. I wrote into the struts-config.xml these lines: and I receive the exception: java.lang.NoClassDefFoundError: org/apache/struts/legacy/GenericDataSource I'm using Tomcat as servlet container. thanks, regards. Davide

  • avatar Dave
    03 September 2004 at 13:26

    ops! Lost my params! data-sources data-source set-property property="name" value="jdbc/WebForumDS"/ set-property property="location" value="webforum"/ set-property property="ejb-location" value="jdbc/WebForumDS"/ set-property property="autoCommit" value="true"/ set-property property="description" value="MySql DataSource"/ set-property property="driverClass" value="org.gjt.mm.mysql.Driver"/ set-property property="user" value="root"/ set-property property="password" value=""/ set-property property="url" value="jdbc:mysql://127.0.0.1:3306/webforum"/ /data-source /data-sources

  • avatar John Topley
    03 September 2004 at 17:58

    Dave, I've never used the data source section of the struts-config.xml file - I always use a separate data-sources.xml file as detailed in the tutorial. So unfortunately I can't offer you any insight into why it's not working. I'm sure they could help you over at the Struts mailing list.

  • avatar stephen c.
    24 September 2004 at 15:28

    Hi John, Fantastic start to the Struts tutorial!! I've noticed the next couple of months don't contain the continuation..... I hope you're planning to continue with the remainder sometime soon? Cheers, SC.

  • avatar John Topley
    24 September 2004 at 19:04

    Thanks Stephen. Please bear with me - each article in the series takes me a long time to write. Look out for part 3 soon!

  • avatar David B
    13 November 2004 at 20:58

    A few comments about the datasource config. 1) In order to use a datasource with a named address with Tomcat, the sql driver package must be located in $CATALINA_HOME/common/lib (here it says why http://jakarta.apache.org/tomcat/tomcat-5.0-doc/jndi-resources-howto.html #JDBC%20Data%20Sources ) 2) If you get an exception that says something like "name jdbc is not bound in this context", modify John's class BaseDAO so the constructor reads like this: public BaseDAO() throws NamingException { Context initContext = new InitialContext(); Context envContext = (Context) initContext.lookup(ENV_DATASOURCE); dataSource = (DataSource) envContext.lookup(JNDI_DATASOURCE); } and add private static final String ENV_DATASOURCE = "java:comp/env"; 3) Define the datasource using Tomcat administration web app (the web app that has a burgundy login page) and preferably not in struts-config.xml as there is a possibility that the datasource manager might become deprecated as per the Struts official documentation. Hope this helps.

  • avatar John Topley
    14 November 2004 at 09:05

    Thanks David. I'm actually working on modifying the app to get it to work with Tomcat myself, as part of another project, but I'll write about that soon.

  • avatar Xavier VAN AUSLOOS
    27 November 2004 at 13:50

    Good job Thanks a lot

If we're going to use a framework then we want to be using it as soon as possible.


Archives

  • Jan
  • Feb
  • Mar
  • Apr
  • May
  • Jun
  • Jul
  • Aug
  • Sep
  • Oct
  • Nov
  • Dec
  • 2019
  • 2018
  • 2017
  • 2016
  • 2015
  • 2014

More Archives


Sign In