[All]
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:
- Create a new project in JBuilder with a unique name. (e.g. StrtusTutorial.jpx).
- Set the server for the project in the 'Project Properties' to
Tomcat 5.5.
- 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:
- Open a console and navigate to .../MySQLServer/bin
- Run mysql.exe
- 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:
- Enterprise | Enterprise Setup | Database Drivers | Add...
- New... | Type 'MySQL' for the name, set the location to be
'User Home' | Add...
- Navigate to the location of the MySQL JDBC driver and select
it (e.g. .../mysql-connector-java-3.1.13-bin.jar).
- OK | OK | OK
- Choose File | Save All (just in case you have not already)
- Exit and restart JBuilder
Now, take a moment to configure the project for MySQL:
- Project | Project Properties | Paths | Required Libraries
- Add... | select the 'MySQL' from the 'User Home' section
- 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.:
- Tools | Database Pilot
- 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
- File | New... | select 'com.mysql.jdbc.Driver' from the Driver
combobox | and paste in the following for the URL: jdbc:mysql://localhost/tutorial
| OK
- File | Apply (if available)
Finally, we open the connection we just created to create our 'users'
table.
- Double-click the 'mysql://localhost/tutorial' in the treeview
under 'Database URLs' in the Database Pilot
- Enter the database username and password (e.g. root/<no password>)
| OK
- 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)
- File | Apply (if available)
- 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.
|
|
Connect with Us