Wednesday, July 6, 2011

Struts 1.x - Notes

How a Request is Processed
ActionServlet is the only servlet in Struts framework, and is responsible for handling all of the requests. Whenever it receives a request, it first tries to find a sub-application for the current request. Once a sub-application is found, it creates a RequestProcessor object for that sub-application and calls its process() method by passing it HttpServletRequest and HttpServletResponse objects.
The RequestProcessor.process() is where most of the request processing takes place. The process() method is implemented using the Template Method design pattern, in which there is a separate method for performing each step of request processing, and all of those methods are called in sequence from the process() method. For example, there are separate methods for finding the ActionForm class associated with the current request, and checking if the current user has one of the required roles to execute action mapping. This gives us tremendous flexibility. The RequestProcessor class in the Struts distribution provides a default implementation for each of the request-processing steps. That means you can override only the methods that interest you, and use default implementations for rest of the methods. For example, by default Struts calls request.isUserInRole() to find out if the user has one of the roles required to execute the current ActionMapping, but if you want to query a database for this, then then all you have to do is override the processRoles() method and return true or false, based whether the user has the required role or not.
First we will see how the process() method is implemented by default, and then I will explain what each method in the default RequestProcessor class does, so that you can decide what parts of request processing you want to change.

public void process(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
// Wrap multipart requests with a special wrapper
request = processMultipart(request);
// Identify the path component we will
// use to select a mapping
String path = processPath(request, response);
if (path == null) {
return;
}
if (log.isDebugEnabled()) {
log.debug("Processing a '" + request.getMethod() +
"' for path '" + path + "'");
}
// Select a Locale for the current user if requested
processLocale(request, response);
// Set the content type and no-caching headers
// if requested
processContent(request, response);
processNoCache(request, response);
// General purpose preprocessing hook
if (!processPreprocess(request, response)) {
return;
}
// Identify the mapping for this request
ActionMapping mapping =
processMapping(request, response, path);
if (mapping == null) {
return;
}
// Check for any role required to perform this action
if (!processRoles(request, response, mapping)) {
return;
}
// Process any ActionForm bean related to this request
ActionForm form =
processActionForm(request, response, mapping);
processPopulate(request, response, form, mapping);
if (!processValidate(request, response, form, mapping)) {
return;
}
// Process a forward or include specified by this mapping
if (!processForward(request, response, mapping)) {
return;
}
if (!processInclude(request, response, mapping)) {
return;
}
// Create or acquire the Action instance to
// process this request
Action action =
processActionCreate(request, response, mapping);
if (action == null) {
return;
}
// Call the Action instance itself
ActionForward forward =
processActionPerform(request, response,
action, form, mapping);
// Process the returned ActionForward instance
processForwardConfig(request, response, forward);
}



processMultipart(): In this method, Struts will read the request to find out if its contentType is multipart/form-data. If so, it will parse it and wrap it in a wrapper implementing HttpServletRequest. When you are creating an HTML FORM for posting data, the contentType of the request is application/x-www-form-urlencoded by default. But if your form is using FILE-type input to allow the user to upload files, then you have to change the contentType of the form to multipart/form-data. But by doing that, you can no longer read form values submitted by user via the getParameter() method of HttpServletRequest; you have to read the request as an InputStream and parse it to get the values.

processPath(): In this method, Struts will read request URI to determine the path element that should be used for getting the ActionMapping element.
processLocale(): In this method, Struts will get the Locale for the current request and, if configured, it will save it in HttpSession as the value of the org.apache.struts.action.LOCALE attribute. HttpSession would be created as a side effect of this method. If you don't want that to happen, then you can set the locale property to false in ControllerConfig by adding these lines to your struts-config.xml file:


processContent(): Sets the contentType for the response by calling response.setContentType(). This method first tries to get the contentType as configured in struts-config.xml. It will use text/html by default. To override that, use the following:


processNoCache(): Struts will set the following three headers for every response, if configured for no-cache:
requested in struts config.xml
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 1);
If you want to set the no-cache header, add these lines to struts-config.xml:



processPreprocess(): This is a general purpose, pre-processing hook that can be overridden by subclasses. Its implementation in RequestProcessor does nothing and always returns true. Returning false from this method will abort request processing.

processMapping(): This will use path information to get an ActionMapping object. The ActionMapping object represents the element in your struts-config.xml file.
name="newContactForm" scope="request">

The ActionMapping element contains information like the name of the Action class and ActionForm used in processing this request. It also has information about ActionForwards configured for the current ActionMapping.
processRoles(): Struts web application security just provides an authorization scheme. What that means is once user is logged into the container, Struts' processRoles() method can check if he has one of the required roles for executing a given ActionMapping by calling request.isUserInRole().

Say you have AddUserAction and you want only the administrator to be able to add a new user. What you can do is to add a role attribute with the value administrator in your AddUserAction action element. So before executing AddUserAction, it will always make sure that the user has the administrator role.

processActionForm(): Every ActionMapping has a ActionForm class associated with it. When Struts is processing an ActionMapping, it will find the name of the associated ActionForm class from the value of the name attribute in the element. type="org.apache.struts.action.DynaActionForm">
type="java.lang.String"/>
type="java.lang.String"/>

In our example, it will first check to see if an object of the org.apache.struts.action.DynaActionForm class is present in request scope. If so, it will use it; otherwise, it will create a new object and set it in the request scope.

processPopulate(): In this method, Struts will populate the ActionForm class instance variables with values of matching request parameters.

processValidate(): Struts will call the validate() method of your ActionForm class. If you return ActionErrors from the validate() method, it will redirect the user to the page indicated by the input attribute of the element.

processForward() and processInclude(): In these functions, Struts will check the value of the forward or include attributes of the element and, if found, put the forward or include request in the configured page.


You can guess difference in these functions from their names. processForward() ends up calling RequestDispatcher.forward(), and processInclude() calls RequestDispatcher.include(). If you configure both forward and include attributes, it will always call forward, as it is processed first.

processActionCreate(): This function gets the name of the Action class from the type attribute of the element and create and return instances of it. In our case it will create an instance of the com.sample.NewContactAction class.

processActionPerform(): This function calls the execute() method of your Action class, which is where you should write your business logic.

processForwardConfig(): The execute()method of your Action class will return an object of type ActionForward, indicating which page should be displayed to the user. So Struts will create RequestDispatcher for that page and call the RequestDispatcher.forward() method.
The above list explains what the default implementation of RequestProcessor does at every stage of request processing and the sequence in which various steps are executed. As you can see, RequestProcessor is very flexible and it allows you to configure it by setting properties in the element. For example, if your application is going to generate XML content instead of HTML, then you can inform Struts about this by setting a property of the controller element.
Creating Your own RequestProcessor
Above, we saw how the default implementation of RequestProcessor works. Now we will present a example of how to customize it by creating our own custom RequestProcessor. To demonstrate creating a custom RequestProcessor, we will change our sample application to implement these two business requirements:
We want to create a ContactImageAction class that will generate images instead of a regular HTML page.
Before processing every request, we want to check that user is logged in by checking for userName attribute of the session. If that attribute is not found, we will redirect the user to the login page. We will change our sample application in two steps to implement these business requirements.
Create your own CustomRequestProcessor class, which will extend the RequestProcessor class, like this:

public class CustomRequestProcessor
extends RequestProcessor {
protected boolean processPreprocess (
HttpServletRequest request,
HttpServletResponse response) {
HttpSession session = request.getSession(false);
//If user is trying to access login page
// then don't check
if( request.getServletPath().equals("/loginInput.do")
request.getServletPath().equals("/login.do") )
return true;
//Check if userName attribute is there is session.
//If so, it means user has allready logged in
if( session != null &&
session.getAttribute("userName") != null)
return true;
else{
try{
//If no redirect user to login Page
request.getRequestDispatcher
("/Login.jsp").forward(request,response);
}catch(Exception ex){
}
}
return false;
}
protected void processContent(HttpServletRequest request,
HttpServletResponse response) {
//Check if user is requesting ContactImageAction
// if yes then set image/gif as content type
if( request.getServletPath().equals("/contactimage.do")){
response.setContentType("image/gif");
return;
}
super.processContent(request, response);
}
}
In the processPreprocess method of our CustomRequestProcessor class, we are checking for the userName attribute of the session and if it's not found, redirect the user to the login page.
For our requirement of generating images as output from the ContactImageAction class, we have to override the processContent method and first check if the request is for the /contactimage path. If so, we set the contentType to image/gif; otherwise, it's text/html.
Add these lines to your struts-config.xml file after the element to inform Struts that CustomRequestProcessor should be used as the RequestProcessor class:
;

Please note that overriding processContent() is OK if you have very few Action classes where you want to generate output whose contentType is something other than text/html. If that is not the case, you should create a Struts sub-application for handling requests for image-generating Actions and set image/gif as the contentType for it. The Tiles framework uses its own RequestProcessor for decorating output generated by Struts.



Spring and Hibernate, Simplify the DAO layer
Spring and Hibernate Simplify the DAO layer

When developing web applications, many people create their data access layer using two basic design patterns(design pattern meaning an informal description, not to be confused with the gang of four). The first is to put the responsibility on the DAO class of acquiring the data source. Approaches may vary from coding this in a super class to externalizing it in a specialized delegate class. The bottom line is that the DAO cannot provide value to your app without obtaining a data source directly(e.g. JNDI) or via a delegate class. One of the major limiting factors here is that your DAO classes are not easily testable outside of the container. An arguable better implementation would be to parameterize the data source. This allows the unit test to acquire the data source independent of the DAO class and pass it in upon execution. This has an upside for the DAO class since it can focus on it's primary responsibility of providing data. This also makes the DAO class testable outside the container since the unit test would pass it in. It's a win, win situation right? Well not exactly! The downside is that now client modules, unit tests in this case, need to know how to obtain a data source. This is a grave injustice placed on the client code developers. After all, part of the reason that we have the DAO layer is to simplify data access. Pick your poison: Testable and inflexible or flexible but difficult to test.

To make things worse, what if you want to swap out your JDBC DAO classes for ones that use a persistence framework like Hibernate. Hibernate catches my eye because I immensely dislike writing gobs of SQL. For a Java programmer, Hibernate offers a much more natural way of persisting objects. Instead of writing JDBC code, hibernate allows me to deal with POJO's and persist them for me. The bulk of my work lies in maintaing OR Mapping files. With hibernate, developers working outside the data access layer will not know which classes are persisted and which are not since the POJO's do not extend or implement anything. So lets use hibernate in our application them. Unfortunately for us, our make believe application has DataSource's floating around in method signatures. Since hibernate uses a SessionFactory object instead of a data source, mass code changes will need to be made to make the transition. Obviously Hibernate knows about data sources under the covers but client code is not aware of that. For those of you thinking of a "find and replace" engineering solution, I hope I never get the opportunity to work with you :) The find and replace approach is similar to loosening a rusty bolt with a pair of pliers. It might work once, maybe twice, but over time your bolt's integrity will be compromised and in need of a replacement. The same is true for your code. It will start to deteriorate over time. I've seen this over and over and over again! Heck I may have even participated in this kind of macro programming in years past. What are the options now?

So far I have preached that working with Hibernate is easier and more enjoyable than working with JDBC directly. This is a personal opinion but one that I know many developers share. An even better approach is to use the Spring Framework with Hibernate. Bringing Spring into the mix will allow you to reduce the amount of plumbing code required for hibernate by a factor of 50% or more. In Spring Live, Matt Raible claims that this code reduction is in the neighborhood of 75%. I tend to agree with Matt and I have created a sample application to make my case. My application contains two unit tests(one DAO using just hibernate and the other using spring and hibernate) that create a 3 field record in a MySQL database(loosely based on the myusers app from spring live).

First both DAO classes implement the MyRecordDAO interface.


package com.shoesobjects;

import java.io.Serializable;

public class MyRecord implements Serializable {
private Long id = null;
private String firstName = null;
private String lastName = null;

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}
}

Here is the data access object using just hibernate. Not to bad but lots of cookie cutter try/catch blocks and redundant opening/closing of sessions. Notice it is 114 lines of code and would be even larger if I handled exceptions properly like any good programmer would.


package com.shoesobjects.dao;

import com.shoesobjects.MyRecord;
import net.sf.hibernate.*;
import net.sf.hibernate.cfg.Configuration;

import java.util.List;

public class MyRecordDAOHibernate implements MyRecordDAO {
private static SessionFactory sessionFactory = null;

public MyRecordDAOHibernate() {
Configuration cfg = null;
try {
cfg = new Configuration().addClass(MyRecord.class);
} catch (MappingException e) {
e.printStackTrace();
}

try {
sessionFactory = cfg.buildSessionFactory();
} catch (HibernateException e) {
e.printStackTrace();
}
}

private Session getSession() {
Session session = null;
try {
session = sessionFactory.openSession();
} catch (HibernateException e) {
e.printStackTrace();
}
return session;
}

public MyRecord getRecord(Long id) {
Session session = this.getSession();
MyRecord record = null;
try {
record = (MyRecord) session.load(MyRecord.class, id);
} catch (HibernateException e) {
e.printStackTrace();
} finally {
if (session != null) {
try {
session.close();
} catch (HibernateException e) {
e.printStackTrace();
}
}
}

return record;
}

public List getRecords() {
Session session = this.getSession();
List list = null;
try {
Query query = session.createQuery("select myrecord from com.shoesobjects.MyRecord");
list = query.list();
} catch (HibernateException e) {
e.printStackTrace();
} finally {
if (session != null) {
try {
session.close();
} catch (HibernateException e) {
e.printStackTrace();
}
}
}

return list;
}

public void saveRecord(MyRecord record) {
Session session = this.getSession();
try {
session.saveOrUpdate(record);
session.flush();
} catch (HibernateException e) {
e.printStackTrace();
} finally {
if (session != null) {
try {
session.close();
} catch (HibernateException e) {
e.printStackTrace();
}
}
}
}

public void removeRecord(Long id) {
Session session = this.getSession();
try {
MyRecord record = (MyRecord) session.load(MyRecord.class, id);
session.delete(record);
session.flush();
} catch (HibernateException e) {
e.printStackTrace();
} finally {
if (session != null) {
try {
session.close();
} catch (HibernateException e) {
e.printStackTrace();
}
}
}
}
}


Hibernate uses a properties file or xml file for it's configuration information(datasource url, username, password, database dialect, etc). For simplicity, I'm listing only the necessary information.


#hibernate.properties
#only used for MyRecordDAOHibernateTest
hibernate.connection.url = jdbc:hsqldb:mem:test
hibernate.connection.username = sa
hibernate.connection.password =
hibernate.dialect = net.sf.hibernate.dialect.HSQLDialect
hibernate.connection.driver_class = org.hsqldb.jdbcDriver
hibernate.hbm2ddl.auto=create

The unit test to exercise this DAO class is listed below. It is a pretty straight forward JUnit test. The only thing that might look out of the ordinary is that we are instantiating the MyRecordDAOHibernate() class directly. In practice, it would be beneficial to hide this behind a Factory. Then you could more easily allow for alternate implementations.


package com.shoesobjects;

import com.shoesobjects.dao.MyRecordDAO;
import com.shoesobjects.dao.MyRecordDAOHibernate;
import junit.framework.Assert;
import junit.framework.TestCase;

public class MyRecordDAOHibernateTest extends TestCase {
private MyRecord record = null;
private MyRecordDAO dao = null;

protected void setUp() throws Exception {
super.setUp();
dao = new MyRecordDAOHibernate();
}

protected void tearDown() throws Exception {
super.tearDown();
dao = null;
}

public void testSaveRecord() throws Exception {
record = new MyRecord();
record.setFirstName("Gavin");
record.setLastName("King");
dao.saveRecord(record);
Assert.assertNotNull("primary key assigned", record.getId());
}
}


The version using Hibernate and Spring has a much cleaner implementation thanks to Springs. Notice this version doesn't have to deal with creating the SessionFactory or dealing with opening/closing sessions. The Spring Framework takes care of this under the covers for you. All you have to do is add the SessionFactory as a needed dependency in Spring's applicationContext.xml file. Notice this class is only 26 lines of code. Look at how clear and concise this version is.


package com.shoesobjects.dao;

import com.shoesobjects.MyRecord;
import org.springframework.orm.hibernate.support.HibernateDaoSupport;

import java.util.List;

public class MyRecordDAOHibernateWithSpring extends HibernateDaoSupport implements MyRecordDAO {

public MyRecord getRecord(Long id) {
return (MyRecord) getHibernateTemplate().get(MyRecord.class, id);
}

public List getRecords() {
return getHibernateTemplate().find("from MyRecord");
}

public void saveRecord(MyRecord record) {
getHibernateTemplate().saveOrUpdate(record);
}

public void removeRecord(Long id) {
Object record = getHibernateTemplate().load(MyRecord.class, id);
getHibernateTemplate().delete(record);
}
}


The applicationContext.xml file contains all the information for Spring's Bean Factory to instantiate the necessary class es and wire the dependencies. Notice the bean with an id of myRecordDAO, it has a reference to the bean sessionFactory which is also defined in this config file.



"http://www.springframework.org/dtd/spring-beans.dtd">



org.hsqldb.jdbcDriver
jdbc:hsqldb:mem:test
sa








MyRecord.hbm.xml




net.sf.hibernate.dialect.HSQLDialect
create














Here is the JUnit test for Hibernate+Spring implementation of the data access object. Notice it is just as straight forward as the last unit test but has one noticeable advantage. Look at line 22 where we instantiate the data access object. No concrete implementation is listed, only the interface. The concrete class is instantiated by Spring's BeanFactory behind the scenes allowing us to swap out the implementation by editing the applicationContext.xml descriptor. This flexibility allows our unit test to easily be modified to use a mock implementation without using command line parameters to the JVC or making code changes.


package com.shoesobjects;

import com.shoesobjects.dao.MyRecordDAO;
import junit.framework.Assert;
import junit.framework.TestCase;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyRecordDAOHibernateWithSpringTest extends TestCase {
private ApplicationContext ctx = null;
private MyRecord record = null;
private MyRecordDAO dao = null;

public MyRecordDAOHibernateWithSpringTest() {
// Should put in a parent class that extends TestCase
String[] paths = {"applicationContext.xml"};
ctx = new ClassPathXmlApplicationContext(paths);
}

protected void setUp() throws Exception {
super.setUp();
dao = (MyRecordDAO) ctx.getBean("myRecordDAO");
}

protected void tearDown() throws Exception {
super.tearDown();
dao = null;
}

public void testSaveRecord() throws Exception {
record = new MyRecord();
record.setFirstName("Rod");
record.setLastName("Johnson");
dao.saveRecord(record);
Assert.assertNotNull("primary key assigned", record.getId());
}

}


In order to run this example, the following steps are required.
1) Install MySQL, login as root
2) Create a database called myrecord
create database myrecord;
3) Create a database user called myrecord with a password of myrecord.
grant all privileges on myrecord.* to myrecord@localhost identified by 'myrecord' with grant option;
4) Run the unit test's. The database table will be created upon running the test due to the following line in applicationContext.xml
update
3) Run the unit tests

No comments:

Bird

Bug

Image and video hosting by TinyPic