Struts, Hibernate and Spring: A guide to creating a dynamic web application

By: Christopher Moeller

Abstract: A tutorial which uses the Struts, Hibernate and Spring frameworks to create a complete website registration page.

Note: This tutorial does not necessarily require the use of an IDE, but explanations (in green) will be provided that assume the use of the JBuilder IDE (version 2006 or prior). This document assumes basic knowledge of Java and web applications.

    I. Creating a project

Using JBuilder, complete the following steps to setup a project in the IDE:

  1. Create a new project in JBuilder with a unique name. (e.g. StrtusTutorial.jpx).
  2. Set the server for the project in the 'Project Properties' to Tomcat 5.5.
  3. Create a new web module with a unique name (e.g. StrutsTutorial) -- by default, this will determine the name of the WAR file that will be eventually created. Then, choose 'Servlet 2.4' and 'JSP 2.0' standards (Step 2 of 3). Next, make sure to check 'Struts 1.2' and 'Validation' (Step 3 of 3). Finally, click finish.
 

Now, let's take stock of our project. You should have a directory under your project with a name that matches your web module name. In that directory you will have only a WEB-INF folder. Within that folder you will have several (xml and tdl) files, like so:

<Web Module>

WEB-INF

strtus-config.xml

validation.xml

validation-rules.xml

web.xml

struts-bean.tld

struts-html.tld

struts-logic.tld

struts-nested.tld

struts-tiles.tld

The .tld files are located within the struts JAR files. JBuilder automatically copies them to your web module's WEB-INF folder after you selected the Struts framework during the web module wizard. If you are not using JBuilder or an IDE, you will need to download Struts 1.2, and then open the struts.jar using WinZip (or other unarchiving tool), find the.tld files (usually under Struts.jar/META-INF/tlds), and extract them to your <web module>/WEB-INF folder.

The xml files are provided below:

    struts-config.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN" "http://struts.apache.org/dtds/struts-config_1_2.dtd">

<struts-config>
<message-resources parameter="ApplicationResources" />
<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>

Note: The struts-config.xml file is the configuration file that defines the flow in Struts web applications. This configuration file defines data sources (parameters needed to use JDBC objects), form beans (mappings/definitions for beans that hold information that has been submitted by the user from a web form), forwards (mappings that provide an easy way to send the user to a specified page depending on business logic) and actions (mapping to an object that handles business logic).

    validation.xml

<?xml version="1.0" encoding="UTF-8"?>
<!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 />

Note: The validator.xml defines specific validations which apply to form beans.

    validation-rules.xml

<!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">
<!--
$Header: /home/cvs/jakarta-struts/conf/share/validator-rules.xml,v 1.52 2004/07/25 12:00:20 niallp Exp $
$Revision: 1.52 $
$Date: 2004/07/25 12:00:20 $

This file contains the default Struts Validator pluggable validator
definitions. It should be placed somewhere under /WEB-INF and
referenced in the struts-config.xml under the plug-in element
for the ValidatorPlugIn.

<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property property="pathnames" value="/WEB-INF/validator-rules.xml,
/WEB-INF/validation.xml"/>
</plug-in>

These are the default error messages associated with
each validator defined in this file. They should be
added to your projects ApplicationResources.properties
file or you can associate new ones by modifying the
pluggable validators msg attributes in this file.

# Struts Validator Error Messages
errors.required={0} is required.
errors.minlength={0} can not be less than {1} characters.
errors.maxlength={0} can not be greater than {1} characters.
errors.invalid={0} is invalid.

errors.byte={0} must be a byte.
errors.short={0} must be a short.
errors.integer={0} must be an integer.
errors.long={0} must be a long.
errors.float={0} must be a float.
errors.double={0} must be a double.

errors.date={0} is not a date.
errors.range={0} is not in the range {1} through {2}.
errors.creditcard={0} is an invalid credit card number.
errors.email={0} is an invalid e-mail address.

Note: Starting in Struts 1.2.0 the default javascript definitions have
been consolidated to commons-validator. The default can be overridden
by supplying a <javascript> element with a CDATA section, just as
in struts 1.1.

-->

<form-validation>

<global>

<validator name="required"
classname="org.apache.struts.validator.FieldChecks"
method="validateRequired"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionMessages,
javax.servlet.http.HttpServletRequest"
msg="errors.required"/>

<validator name="requiredif"
classname="org.apache.struts.validator.FieldChecks"
method="validateRequiredIf"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionMessages,
org.apache.commons.validator.Validator,
javax.servlet.http.HttpServletRequest"
msg="errors.required"/>

<validator name="validwhen"
msg="errors.required"
classname="org.apache.struts.validator.validwhen.ValidWhen"
method="validateValidWhen"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionMessages,
org.apache.commons.validator.Validator,
javax.servlet.http.HttpServletRequest"/>


<validator name="minlength"
classname="org.apache.struts.validator.FieldChecks"
method="validateMinLength"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionMessages,
javax.servlet.http.HttpServletRequest"
depends=""
msg="errors.minlength"
jsFunction="org.apache.commons.validator.javascript.validateMinLength"/>


<validator name="maxlength"
classname="org.apache.struts.validator.FieldChecks"
method="validateMaxLength"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionMessages,
javax.servlet.http.HttpServletRequest"
depends=""
msg="errors.maxlength"
jsFunction="org.apache.commons.validator.javascript.validateMaxLength"/>

<validator name="mask"
classname="org.apache.struts.validator.FieldChecks"
method="validateMask"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionMessages,
javax.servlet.http.HttpServletRequest"
depends=""
msg="errors.invalid"/>


<validator name="byte"
classname="org.apache.struts.validator.FieldChecks"
method="validateByte"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionMessages,
javax.servlet.http.HttpServletRequest"
depends=""
msg="errors.byte"
jsFunctionName="ByteValidations"/>


<validator name="short"
classname="org.apache.struts.validator.FieldChecks"
method="validateShort"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionMessages,
javax.servlet.http.HttpServletRequest"
depends=""
msg="errors.short"
jsFunctionName="ShortValidations"/>


<validator name="integer"
classname="org.apache.struts.validator.FieldChecks"
method="validateInteger"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionMessages,
javax.servlet.http.HttpServletRequest"
depends=""
msg="errors.integer"
jsFunctionName="IntegerValidations"/>

<validator name="long"
classname="org.apache.struts.validator.FieldChecks"
method="validateLong"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionMessages,
javax.servlet.http.HttpServletRequest"
depends=""
msg="errors.long"/>


<validator name="float"
classname="org.apache.struts.validator.FieldChecks"
method="validateFloat"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionMessages,
javax.servlet.http.HttpServletRequest"
depends=""
msg="errors.float"
jsFunctionName="FloatValidations"/>

<validator name="double"
classname="org.apache.struts.validator.FieldChecks"
method="validateDouble"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionMessages,
javax.servlet.http.HttpServletRequest"
depends=""
msg="errors.double"/>


<validator name="date"
classname="org.apache.struts.validator.FieldChecks"
method="validateDate"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionMessages,
javax.servlet.http.HttpServletRequest"
depends=""
msg="errors.date"
jsFunctionName="DateValidations"/>


<validator name="intRange"
classname="org.apache.struts.validator.FieldChecks"
method="validateIntRange"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionMessages,
javax.servlet.http.HttpServletRequest"
depends="integer"
msg="errors.range"/>


<validator name="floatRange"
classname="org.apache.struts.validator.FieldChecks"
method="validateFloatRange"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionMessages,
javax.servlet.http.HttpServletRequest"
depends="float"
msg="errors.range"/>


<validator name="creditCard"
classname="org.apache.struts.validator.FieldChecks"
method="validateCreditCard"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionMessages,
javax.servlet.http.HttpServletRequest"
depends=""
msg="errors.creditcard"/>


<validator name="email"
classname="org.apache.struts.validator.FieldChecks"
method="validateEmail"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionMessages,
javax.servlet.http.HttpServletRequest"
depends=""
msg="errors.email"/>

<validator name="url"
classname="org.apache.struts.validator.FieldChecks"
method="validateUrl"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionMessages,
javax.servlet.http.HttpServletRequest"
depends=""
msg="errors.url"/>

<!--
This simply allows struts to include the validateUtilities into a page, it should
not be used as a validation rule.
-->
<validator name="includeJavaScriptUtilities"
classname=""
method=""
methodParams=""
depends=""
msg=""
jsFunction="org.apache.commons.validator.javascript.validateUtilities"/>

</global>

</form-validation>

Note: The validator-rules.xml defines standard server-side form validation rules. More on this later.

    web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4">
<display-name>StrutsTutorial</display-name>
<jsp-config>
<taglib>
<taglib-uri>/WEB-INF/struts-bean.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-bean.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/WEB-INF/struts-html.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-html.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/WEB-INF/struts-logic.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-logic.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/WEB-INF/struts-tiles.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-tiles.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/WEB-INF/struts-nested.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-nested.tld</taglib-location>
</taglib>
</jsp-config>
<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>
<init-param>
<param-name>debug</param-name>
<param-value>2</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>

Note: The web.xml provides general deployment and configuration information for web applications.

    II. Creating an ApplicationResource.properties file.

Before moving on, let create and populate an ApplicationResources.property file. This file does not go in the WEB-INF folder, but instead goes in the root of your source tree. The information below is also included at the top of the validator-rules.xml, except for the last lines (in bold).

    ApplicationResources.properties

# Struts Validator Error Messages
errors.required={0} is required.
errors.minlength={0} can not be less than {1} characters.
errors.maxlength={0} can not be greater than {1} characters.
errors.invalid={0} is invalid.

errors.byte={0} must be a byte.
errors.short={0} must be a short.
errors.integer={0} must be an integer.
errors.long={0} must be a long.
errors.float={0} must be a float.
errors.double={0} must be a double.

errors.date={0} is not a date.
errors.range={0} is not in the range {1} through {2}.
errors.creditcard={0} is an invalid credit card number.
errors.email={0} is an invalid e-mail address.

errors.passwords.different=Passwords do not match.
errors.email.found=E-mail address is already registered.

label.email=E-mail
label.password=Password
label.verify_password=Verify password

This file is used to store all String literals that are used in your application. Why is this useful? If you always use this file, you will always know exactly where you need to go if you need to modify or add a String. Let's say your application has 60 JSPs. It would be a pain to have to modify a common error message in each and every JSP in your application, rather than simply modifying a single error message text in the ApplicationResources.properties file. In addition, this file can be extended to support different languages easily. Basically what you would do is have a bunch of copies of this file all named slightly differently to designate each particular region (e.g. ApplicationResources_es.properties for Spanish), and then you would translate the value parts of the name/value pairs into the particular language.

ApplicationResources_es.properties:

# Struts Validator Error Messages
errors.required={0} se requiere.
<etc.>

(Note: I may have butchered the grammer for how this error would be presented in Spanish -- this is just an loose example)

Note: In this tutorial, we will be making changes only to the struts-config.xml and validator.xml files. This validator-rules.xml already has all of the validation rules defined, so there will be no reason to make any changes to this file. We will talk more about validation and its purpose in Struts web applications later. The web.xml should not require any additional modification (for this tutorial), although you should make sure that the value in the <display-name> element matches the name of your web application.

    III. Implementing a registration page.

A. Creating the registration JSP

    The goal of this section is to create a 'registration' JSP that will take in an e-mail and password (and a verification password, to make sure that the user did not accidentally make a typo) from the user in order to 'register' with our website. In a later section, we will store that user information in a database using a class we will create called an 'Action'. This action class will also validate the data submitted by the user.

  • Create a new JSP called register.jsp and replace the entire text in with the following:

    <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
    <html:html>
    <head><title>Register</title></head>
    <html:errors/>
    <body>
    <%-- Form display --%>
    <html:form action="registerAction.do">
    <table>
    <tr><td>E-mail:</td> <td><html:text property="email"/></td></tr>
    <tr><td>Password:</td> <td><html:password property="password"/></td></tr>
    <tr><td>Verify password:</td> <td><html:password property="verify_password"/></td></tr>
    <tr><td><br/></td></tr>
    <tr><td><html:submit value="Submit" property="Submit"/></td></tr>
    </table>
    </html:form>
    </body>
    </html:html>

    Now let's review the Struts related contents and make sense of the various tags we have referenced.

    <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>

    The above is our reference to the struts-html.tld tag library that we placed in our WEB-INF directory. In this case, it will allow us to create Struts forms to capture user input.

    <html:html>

    The above is used to render an <html> element with appropriate language attributes if there is a current Locale available in the user's session. This is not actually required in order to successfully run the JSP, but it is free and easy, so why not always use it?

    <html:errors/>

    This is a quick and dirty way to display any validation errors. We will improve on it later.

    <html:form action="registerAction.do">

    The above tag represents an input form which is associated with a bean whose properties correspond to the various fields of the form. Note: the bean in this case will be called 'RegisterAction(.java)', but our reference is called 'registerAction.do', which is an action mapping that we will define in the struts-config.xml. The struts-config.xml will provide all of the mapping and configuration information which will ultimately allow our action class (RegisterAction) to receive the form input information from the user to perform validation and database manipulation.

    <html:text property="email"/>
    <html:password property="password"/>
    <html:password property="verify_password"/>

    The above tags provide actual input fields on the JSP page. The <html:password...> tag is used to hide the password text while the user types the value into the input field. We provide a 'verify_password' input field in order to help the user make sure that they do not make any mistakes when typing in the value into the field. Later, we will use the action class we create to perform a simple custom validation method which will help us compare the 'password' with the 'verify_password' to make sure that they match.

    <html:submit value="Submit" property="Submit"/>

    This final tag provides a button which the user clicks when they are ready to submit the information within the form.

    B. Creating the registeration action class

Here is our basic RegisterAction class. It will not do anything yet, except return the user to the registration page

package com.tutorial.struts.action;

import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionForm;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.Action;

public class RegisterAction extends Action
{


public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response)
{
String forward = "register";
// todo: Fill in the logic to be able to read, validate, and store the form data.
return mapping.findForward(forward);
}


}

    C. Updating the struts-config.xml with a form-beans and action-mappings elements

Here is an updated struts-config.xml with the action mapping for the RegisterAction class. It also has a section that defines our form bean, which holds the values which are submitted from the registration JSP. We will go over both sections in detail below:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN" "http://struts.apache.org/dtds/struts-config_1_2.dtd">

<struts-config>

<form-beans>
<form-bean name="registerActionForm" type="org.apache.struts.validator.DynaValidatorActionForm">
<form-property name="email" type="java.lang.String" />
<form-property name="password" type="java.lang.String" />
<form-property name="verify_password" type="java.lang.String" />
</form-bean>
</form-beans>

<action-mappings>
<action input="/register.jsp" name="registerActionForm" path="/registerAction" scope="session" type="com.tutorial.struts.action.RegisterAction" validate="yes">
<forward name="register" path="/register.jsp" />
<forward name="login" path="/login.jsp" />
</action>
</action-mappings>


<message-resources parameter="ApplicationResources" />
<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>

<action-mappings>
...
</action-mappings>

All action elements will be defined between the action-mappings element above.

<action input="/register.jsp" name="registerActionForm" path="/registerAction" scope="session" type="com.tutorial.struts.action.RegisterAction" validate="yes">
<forward name="register" path="/register.jsp" />
<forward name="login" path="/login.jsp" />
</action>

Our first action has a few parts. Firstly, the action element itself is comprised of many properties:

input="/register.jsp"

The input property defines the JSP that provides the form 'input' information.

name="registerActionForm"

The name property above references only our form bean name property: <form-bean name="registerActionForm"... So, it is important that both names match exactly.

path="/registerAction"

?

scope="session"

This whole registration process should be considered a single session, so we indicate above that the scope is per session.

type="com.tutorial.struts.action.RegisterAction"

The type relates directly to the name of our action class using full package delineation.

validate="yes"

The validate element turns on validation for our action. We will discuss validation in more detail below.

<forward name="register" path="/register.jsp" />
<forward name="login" path="/login.jsp" />

These two forward elements handle two possible results from a form submission: success or failure. If the registration 'successful' then the user is sent to the 'login' page (which we will create later), or if the registration 'fails', we send the user back to the registration page to resolve any registration problems.

Now let's review the form beans section:

<form-beans>
...
</form-beans>

All form-bean elements will be defined between the form-beans element above.

<form-bean name="registerActionForm" type="org.apache.struts.validator.DynaValidatorActionForm">
<form-property name="email" type="java.lang.String" />
<form-property name="password" type="java.lang.String" />
<form-property name="vpassword" type="java.lang.String" />
</form-bean>

The form-bean element has a name (remember that the 'name' here matches the 'name' in the action mapping), and a type. For the type we have two choices. Either we could define a bean with a set of properties that relate to the fields in our registration JSP (e.g. create a class that has three String properties for email, password, and verify password with getters and setters for each), or we can do as we have done above and use an existing helper class to provide this for us. We use the DynaValidatorActionForm, because it provides us with standard validation for free. We define the properties for that ActionForm class right in the xml as form-properties. If you do not need to provide custom form beans, using this helper class can reduce code clutter, and make the project easier to maintain.

At this point, let's test to make sure that everything is 'working' as it should. To do this simply compile the project and then place a breakpoint on the return statement in RegisterAction.java. If you are using JBuilder, simply right-click on the register.jsp and choose 'WebDebug'. If you are not using an IDE, simply build the WAR and test that it runs inthe web server without generating any exceptions. When the JSP loads, just click the 'Submit' button -- the execution should stop at the breakpoint. If you continue, you should be able to go back to the register.jsp and click submit again to repeat. Once you have verified that the application runs successfully, continue below.

    IV. Setting up the database and creating the a table for registered users.

Note: You can use basically any database you want, but this tutorial assumes the use of MySQL for the backend. If you use another database, you will need to substitute vender-specific values in the appropriate places.

The first step is to download, install and start the MySQL Server. Next, download the MySQL JDBC driver. Once the server is installed and running, use the MySQL console to create a database named 'tutorial'. Here are the steps to do this:

  1. Open a console and navigate to .../MySQLServer/bin
  2. Run mysql.exe
  3. Enter the following commands in the mysql console:

mysql>CREATE DATABASE tutorial;
mysql>exit

Once the database is created, you will need to create a table to hold the registration records which are submitted from our registration process.

Using JBuilder, complete the following steps to configure the IDE with the MySQL JDBC driver:

  1. Enterprise | Enterprise Setup | Database Drivers | Add...
  2. New... | Type 'MySQL' for the name, set the location to be 'User Home' | Add...
  3. Navigate to the location of the MySQL JDBC driver and select it (e.g. .../mysql-connector-java-3.1.13-bin.jar).
  4. OK | OK | OK
  5. Choose File | Save All (just in case you have not already)
  6. Exit and restart JBuilder

Now, take a moment to configure the project for MySQL:

  1. Project | Project Properties | Paths | Required Libraries
  2. Add... | select the 'MySQL' from the 'User Home' section
  3. OK | OK

Next, we use the Database Pilot to configure a connection to the MySQL server. This allows us to test the connection and later to add and modify tables using the GUI, or by SQL directly.:

  1. Tools | Database Pilot
  2. View | Options... | Drivers | and verify that 'com.mysql.jdbc.Driver' appears in the 'Drivers" listbox. If it doesn't, click Add... | and enter 'com.mysql.jdbc.Driver' in the 'Driver class' dialog. | OK | OK
  3. File | New... | select 'com.mysql.jdbc.Driver' from the Driver combobox | and paste in the following for the URL: jdbc:mysql://localhost/tutorial | OK
  4. File | Apply (if available)

Finally, we open the connection we just created to create our 'users' table.

  1. Double-click the 'mysql://localhost/tutorial' in the treeview under 'Database URLs' in the Database Pilot
  2. Enter the database username and password (e.g. root/<no password>) | OK
  3. Next, go to the 'Enter SQL' tab and paste in the following:

    CREATE TABLE users (id INT NOT NULL UNIQUE PRIMARY KEY, email TEXT NOT NULL, password TEXT NOT NULL)

  4. File | Apply (if available)
  5. File | Exit
 

If you are not using an IDE, then you will just need to remember to add a reference to the MySQL JDBC driver jar on the web server classpath.

So, the SQL for the table creation is provided below. You will need to create the table either manually from the mysql console, or by using a MySQL GUI client:

CREATE TABLE users (id INT NOT NULL UNIQUE PRIMARY KEY, email TEXT NOT NULL, password TEXT NOT NULL)

    V. Creating the user class

We now need to create a Java class that will be used to store user information.

    User.java:

package com.tutorial.beans;

public class User
{
private long id;
private String email;
private String password;

void User() { }
public void setId(long id) { this.id = id; }
public long getId() { return id; }
public String getEmail() { return email; }
public String getPassword() { return password; }
public void setEmail(String email) { this.email = email; }
public void setPassword(String password) { this.password = password; }
public String toString() { return ""; }
public boolean equals(Object o) { return false; }
public int hashCode() { return 0; }
}

    VI. Configuring Hibernate

The main configuration for Hibernate is located within the hibernate.cfg.xml, which should be located in the root of the source tree. If this file has not been created, create it now, and paste in the following information:

    hibernate.cfg.xml:

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<!-- This (Hibernate) xml file defines Hibernate properties and JDBC connection information, as well as the mapping xml files -->
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost/tutorial</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password"></property>
<property name="hibernate.connection.pool_size">10</property>
<property name="show_sql">true</property>
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
<property name="hibernate.cache.use_query_cache">true</property>
<!-- Set this to make batch updates larger: -->
<property name="hibernate.jdbc.batch_size">20</property>
<property name="hibernate.cglib.use_reflection_optimizer">true</property>
<!-- Mapping files -->
<mapping resource="user.hbm.xml"/>
</session-factory>
</hibernate-configuration>

You may wonder, how does our hibernate.cfg.xml get read in by our application? The answer is through implementation of a hibernate plug-in class. A default class can be used, but it is often useful to have access to a custom implementation.We shall use the following simple implementation:

    HibernatePlugin.java:

package com.codesoup.archangel.hibernate;

import java.net.URL;
import javax.servlet.ServletException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.action.ActionServlet;
import org.apache.struts.action.PlugIn;
import org.apache.struts.config.ModuleConfig;
import org.hibernate.cfg.*;
import org.hibernate.*;

public class HibernatePlugin implements PlugIn
{
private Configuration config;
private SessionFactory factory;
private String path = "/hibernate.cfg.xml";
private static Class hpclass = HibernatePlugin.class;
public static final String KEY_NAME = hpclass.getName();
private static Log log = LogFactory.getLog(hpclass);

public void setPath(String path) { this.path = path; }

public void init(ActionServlet servlet, ModuleConfig modConfig) throws ServletException
{
try
{
URL url = HibernatePlugin.class.getResource(path);
config = new Configuration().configure(url);
factory = config.buildSessionFactory();
servlet.getServletContext().setAttribute(KEY_NAME, factory);
}
catch (MappingException e)
{
log.error("Mapping errors:", e);
String[] messages = e.getMessages();
for (int i = 0; i < messages.length; i++)
log.error(messages[i], e);
throw new ServletException();
}
catch (HibernateException e)
{
log.error("Hibernate errors:", e);
String[] errors = e.getMessages();
for (int i = 0; i < errors.length; i++)
log.error(errors[i], e);

throw new ServletException();
}
}

public void destroy()
{
try { factory.close(); }
catch (HibernateException e) { log.error("Unable to close factory", e); }
}
}

Now, the above plug-in class is loaded by Struts through the struts-config.xml (see new section in bold):

    struts-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN" "http://struts.apache.org/dtds/struts-config_1_2.dtd">
<struts-config>
<form-beans>
<form-bean name="registerActionForm" type="org.apache.struts.validator.DynaValidatorActionForm">
<form-property name="email" type="java.lang.String" />
<form-property name="password" type="java.lang.String" />
<form-property name="verify_password" type="java.lang.String" />
</form-bean>
</form-beans>
<action-mappings>
<action input="/register1.jsp" name="registerActionForm" path="/registerAction" scope="session" type="com.tutorial.struts.action.RegisterAction" validate="yes">
<forward name="register" path="/register1.jsp" />
<forward name="login" path="/login.jsp" />
</action>
</action-mappings>
<message-resources parameter="ApplicationResources" />
<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property property="pathnames" value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml" />
</plug-in>
<plug-in className="com.tutorial.hibernate.HibernatePlugin">
<set-property property="path" value="/hibernate.cfg.xml" />
</plug-in>

</struts-config>

Now, let us create the file which Hibernate uses to map the properties in the user class to the corresponding columns in the database. This file will also be placed in the root of the source folder.

    user.hbm.xml:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- This (Hibernate) xml file maps all of the properties in the User class to the cooresponding columns in the database -->
<hibernate-mapping>
<class name="com.tutorial.beans.User" table="users">
<id name="id" type="long" column="id" >
<generator class="increment"/>
</id>
<property name="email">
<column name="email"/>
</property>
<property name="password">
<column name="password"/>
</property>
</class>
</hibernate-mapping>

In order to make the xml files [or other file types] appear in JBuilder (within the Project Pane), open Project Properties | Build | Resource | scroll down and select 'xml' | and select the 'Copy' radio button | OK. Double-clicking on the file in the Project Pane will open it in the Editor.

    VII. Adding Struts, Hibernate and Spring libraries

For development purposes, it is a good idea to add all required libraries and their dependancies to the classpath, even if they are currently not explicitly needed by your application. Look in the extracted download directory for each of the third-party frameworks and add references to all of the necessary JARs. How will you know exactly what to add? Well, the first rule is more is better than less. Take Hibernate for example, there should be a hibernate3.jar sitting in the main Hibernate folder, so make sure to grab that. Then you will need all of the libraries that hibernate3.jar depends on. Luckly they provide all of those in the .../hibernate/lib folder, so grab all of those files as well.

At this point, it would be a good idea to take a moment to compile and perform a test run of the application. If all of the pieces are in place, and all of the required libraries are available, the project should work without throwing any exceptions.

Later we will work on adding custom code to our RegisterAction class to make it use the values submitted by the client using the web form to perform custom validation, and then if all goes well, post the new registeree through a database transaction using Spring.

    Troubleshooting:

Issue:

You receive the following message either during compilation, or later when Tomcat is starting up:

log4j:WARN No appenders could be found for logger (org.apache.commons.digester.Digester.sax).

Solution:

There are at least two ways:

a. If you want to modify Tomcat in general to resolve this issue (this will affect all webapps running in Tomcat):

The solution is the following:

Place both log4j.jar and the Jakarta commons-logging.jar here:

$TOMCAT_HOME/common/lib directory.

Next, create your log4j properties file here:

$TOMCAT_HOME/common/classes/log4j.properties

b. If you want to resolve this issue only for your particular application (e.g. if you are using Tomcat inside JBuilder):

The solution is the following:

Make sure that a version of log4j.jar and the Jakarta commons-logging.jar are on your classpath:

Next, place a log4j properties file in the root of your source tree.

Here is a sample log4j.properties file, which you can use right away. Note that the log4j.rootLogger is set to debug. This will help you troubleshoot subtle problems immediately, though you may wich to change this value to info, once your application is working and stable.

### standard log4j.properties file to control logging output ###

### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### set log levels - for more verbose logging change 'info' to 'debug' ###

log4j.rootLogger=debug, stdout

log4j.logger.org.hibernate=info
#log4j.logger.org.hibernate=debug

### log HQL query parser activity
#log4j.logger.org.hibernate.hql.ast.AST=debug

### log just the SQL
log4j.logger.org.hibernate.SQL=debug

### log JDBC bind parameters ###
#log4j.logger.org.hibernate.type=info
log4j.logger.org.hibernate.type=debug

### log schema export/update ###
#log4j.logger.org.hibernate.tool.hbm2ddl=info
log4j.logger.org.hibernate.tool.hbm2ddl=debug

### log HQL parse trees
#log4j.logger.org.hibernate.hql=debug

### log cache activity ###
log4j.logger.org.hibernate.cache=debug
#log4j.logger.org.hibernate.cache=info

### log transaction activity
#log4j.logger.org.hibernate.transaction=debug

### log JDBC resource acquisition
#log4j.logger.org.hibernate.jdbc=debug

### enable the following line if you want to track down connection ###
### leakages when using DriverManagerConnectionProvider ###
#log4j.logger.org.hibernate.connection.DriverManagerConnectionProvider=trace

Issue:

If you run your application in JBuilder and receive the following log message [Note: you must enable logging to see this message -- see above]:

07:53:32,637 ERROR HibernatePlugin:36 - Mapping errors:
org.hibernate.MappingException: Could not read mappings from resource: user.hbm.xml

Solution:

After compiling your project, look in your module directory for your User class (e.g. .../<StrutsTutorial>/<WEB-INF>/classes/com/tutorial/beans/User.class). If you don't see it, then in JBuilder right-click on your web module | Properties | Content | and uncheck 'Only include module specific Java classes'. Then just recompile your project and retest.

    VIII. Configuring Spring

The first thing you will need to create is an xml file that will provide a definition to allow Spring to create a User bean for us. These xml file allow us to define a simple and consistent way of creating a namespace of JavaBeans objects which will be managed by a Spring BeanFactory. This is handy because then it is quite easy to save our new user into the database (i.e. no SQL needed).

So, create the following file in the root of your source tree:

    user.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<!-- This (Spring) xml file defines the bean used with the Spring bean factory object -->
<beans>
<bean id="user" class="com.tutorial.beans.User">
</bean>
</beans>

Now let's take a look at an updated version of our RegisterAction.java class, which is provided in its entirty below:

    RegisterAction.java:

package com.tutorial.struts.action;

import javax.servlet.http.*;
import org.apache.commons.logging.*;
import org.apache.commons.validator.*;
import org.apache.struts.*;
import org.apache.struts.action.*;
import org.apache.struts.validator.*;
import org.hibernate.*;
import org.hibernate.criterion.*;
import org.springframework.beans.factory.*;
import org.springframework.beans.factory.xml.*;
import org.springframework.core.io.*;
import com.tutorial.beans.*;
import com.tutorial.hibernate.*;

public class RegisterAction extends Action
{

private static Class thisClass = RegisterAction.class;
private static Log log = LogFactory.getLog(thisClass);


/*
Method checks to see if password and verify password match, if not return false
*/
private boolean isValidPassword(HttpServletRequest request, String password, String verify_password)
{

boolean valid = false;
if (password.matches(verify_password)) { valid = true; }
return valid;

}


/*
Method checks database for email and returns false if it already exists
*/
private boolean isUniqueEmail(Session session, HttpServletRequest request, String email)
{

boolean result = true; // assume that it is
Criteria criteria = session.createCriteria(User.class);
criteria.add(Expression.eq("email", email));
criteria.setMaxResults(1);
User duplicate = (User)criteria.uniqueResult();
if (duplicate != null)
{

if (duplicate.getEmail().matches(email))
{

result = false; // set to false when we find the same email in the database

}

}
return result;

}


/*
Method registers new user
*/
public void registerUser(Session session, HttpServletRequest request, DynaValidatorActionForm dform)
{

boolean registered = false;
Resource res = new ClassPathResource("user.xml");
BeanFactory factory = new XmlBeanFactory(res);
User user = (User) factory.getBean("user");
user.setEmail((String)dform.get("email"));
user.setPassword((String)dform.get("password"));
org.hibernate.Transaction tx = session.beginTransaction();
session.save(user);
tx.commit();

}



/**
* Save the specified error messages keys into the appropriate request
* attribute for use by the <html:errors> tag, if any messages
* are required. Otherwise, ensure that the request attribute is not
* created.
*
* @param request The servlet request we are processing
* @param errors Error messages object
*/
protected void saveErrors(HttpServletRequest request, ActionMessages errors)
{

// Remove any error messages attribute if none are required
if ((errors == null) || errors.isEmpty())
{

request.removeAttribute(Globals.ERROR_KEY);
return;

}
// Save the error messages we need
request.setAttribute(Globals.ERROR_KEY, errors);

}

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

String forward = "register";
SessionFactory sessionFactory = (SessionFactory) servlet.getServletContext().getAttribute(HibernatePlugin.KEY_NAME);
Session session = sessionFactory.openSession();
DynaValidatorActionForm dform = (DynaValidatorActionForm) form;
ActionMessages errors = dform.validate(mapping, request);
if (errors.isEmpty())
{

try
{

if (!isValidPassword(request, (String) dform.get("password"), (String) dform.get("verify_password")))
{

errors.add(Globals.ERROR_KEY, new ActionMessage("errors.passwords.different"));

}
else if (!isUniqueEmail(session, request, (String) dform.get("email")))
{

errors.add(Globals.ERROR_KEY, new ActionMessage("errors.email.found"));

}
else
{

registerUser(session, request, dform);
forward = "success";

}

}
catch (Exception e) { e.printStackTrace(); }
finally
{

session.flush();
session.close();

}

}
saveErrors(request, errors);
return mapping.findForward(forward);

}


}

Let's look at a few methods in this class:

RegisterAction.execute(...):

This method first creates a Hibernate session using the plug-in class we provided. The session object is what we will use to create custom queries against the database. Next, we create a DynaValidatorActionForm class and cast the ActionForm object that is passed into this method into it. Then we use the DynaValidatorActionForm to create an instance of our ActionMessages class, which we use to keep track of any validation errors. We check to see if there are any errors already existing and if so, we forward the user back to the register page to resolve those problems. If there are no errors, then we need to perform two other customer validations before we register this new user by adding them to the database. The first thing we need to check is if the 'password' field matches the 'validate_password' field, we do this with isValidPassword(...). If it not valid, we add an error message 'errors.passwords.different' that refers back to the ApplicationResources.properties. So if the password was valid, we then check to make sure that the submitted email is unique (i.e. there should never be two users with the same e-mail) by calling isUniqueEmail(...). If it is not a unique e-mail, then we add an error called 'errors.email.found' that again refers back to the ApplicationResources.properties. Then, if both the password is valid, and then e-mail is unique, then we call registerUser(...), which adds the user to the database and sets the forward to be the 'success' jsp. If any exceptions occur, then we make sure to call session.flush() and session.close() to clean up properly. Finally we call saveErrors(...) to save any errors (as sessions attributes) and then forward to the appropriate jsp (which is back to the 'register' jsp by default).

RegisterAction.isValidPassword(...):

This method simply compares the password with the verify_password and returns true if they match.

RegisterAction.isUniqueEmail(...)

This method checks the database for the submitted email address, returning false if it finds it.

RegisterAction.registerUser(...)

This method uses the Spring BeanFactory object to create a 'user' bean. It then sets the properties for the bean to the submitted values and then sends the information to the database.

Now, at this point, you should be able to run the application, open the register.jsp in a browser, add and e-mail address, password, (matching) verify password and click 'submit' to add the row to the users table in the database. If you have a way to view the database data directly, you should see a new row with your submitted information.

In JBuilder, you can open the Database Pilot to view the row you added (Tools | Database Pilot | expand 'mysql://localhost/tutorial' | expand 'Tables' | expand 'users' | and click on the 'Data' tab.

    IX. Validation

I promised earlier to return to the subject of validation, so here we go. Nearly all of the elements are in place to start implementing validation, we just need to provide a more detailed validation.xml. Lets look at an updated version:

    validation.xml:

<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE form-validation PUBLIC
"-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.0//EN"
"http://jakarta.apache.org/commons/dtds/validator_1_0.dtd">
<form-validation>
<formset>
<form name="/registerAction">
<field
property="email"
depends="required,email">
<arg0 key="label.email"/>
</field>
<field
property="password"
depends="required,minlength,maxlength">
<arg0 key="label.password"/>
<arg1
name="minlength"
key="${var:minlength}"
resource="false"/>
<var>
<var-name>minlength</var-name>
<var-value>8</var-value>
</var>
<arg2
name="maxlength"
key="${var:maxlength}"
resource="false"/>
<var>
<var-name>maxlength</var-name>
<var-value>36</var-value>
</var>
</field>
<field
property="verify_password"
depends="required,minlength,maxlength">
<arg0 key="label.verify_password"/>
<arg1
name="minlength"
key="${var:minlength}"
resource="false"/>
<var>
<var-name>minlength</var-name>
<var-value>8</var-value>
</var>
<arg2
name="maxlength"
key="${var:maxlength}"
resource="false"/>
<var>
<var-name>maxlength</var-name>
<var-value>36</var-value>
</var>
</field>
</form>
</formset>
</form-validation>

Let's look at this file in more detail:

<form-validation>
<formset>

<form name="/registerAction">

...

</form>

</formset>
</form-validation>

The above form tag (in bold) indicates which JSP form is being validated. You can add more form tags under the formset tag as you create new actions.

<field
property="email"
depends="required,email">
<arg0 key="label.email"/>
</field>

The above field tag is used to describe how the e-mail field should be validated. The depends attribute lists which particular rules found in the validator-rules.xml should be enforced for this field. In this case, we see that required and email rules are used. Let take a moment and examine the relevant part of the associated validator-rules.xml:

<validator name="required"
classname="org.apache.struts.validator.FieldChecks"
method="validateRequired"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionMessages,
javax.servlet.http.HttpServletRequest"
msg="errors.required"/>

All we need to know here is that when we reference 'required' in the depends attribute, it refers to the validator-rules.xml, which defines a class, method, method parameters for performing a 'required' validation on the field. The msg attribute refers back to the ApplicationResources.properties file to find the text value associated with the name 'errors.required'. Same thing when we reference 'email' in the depends attribute, only that rule defines a different class, method and method parameters for determining if the text in the submitted email field is a valid email address.

Ok, one more. Let's look now at the how we can implement the password validation:

<field
property="password"
depends="required,minlength,maxlength">
<arg0 key="label.password"/>
<arg1
name="minlength"
key="${var:minlength}"
resource="false"/>
<var>
<var-name>minlength</var-name>
<var-value>8</var-value>
</var>
<arg2
name="maxlength"
key="${var:maxlength}"
resource="false"/>
<var>
<var-name>maxlength</var-name>
<var-value>36</var-value>
</var>
</field>

I did not mention the arg0 attribute in the last discussion of email, so lets talk about it now. The arg0 attribute replaces the {0} found in the ApplicationResources.properties file under the text values for errors.required, errors.minlength, and errors.maxlength with the text value under label.password. That sounds more complicated that it really is. Really we just want to find a way to be able to resource all our strings (including validation error messages) back to the ApplicationResources.properties file (or a region specific one). When you see an arg1, or arg2 attribute, then these would replace a {1} or {2} respectively, and so on.

Remember how we used the following in our register.jsp as a quick and dirty way to display validation errors?

<html:errors/>

Well, now we have a chance to improve on it. Replace the above with what is shown below:

<%-- Improved error display --%>
<logic:messagesPresent>
<ul class="error">
<html:messages id="error">
<li style="color: red;"><bean:write name="error"/></li>
</html:messages>
</ul>
</logic:messagesPresent>

You may be asking, why take something that was simple and do the same thing in a more complicated way? Well, the above will allow us more control over the display of the errors (e.g. carriage returns and color).

Now when you run this application, you should get errors displayed in red when you submit the form without having the fields correct filled out.

Now that validation has been introduced, this tutorial should provide enough information to get you started working with Struts, Spring and Hibernate. This concludes this tutorial.

Server Response from: ETNASC03