Skip navigation

Monthly Archives: December 2007




Spring’s DataAccessException

Discussion


In Spring, all DB Access Exception is subclasses of org.springframework.dao.DataAccessException. So you just try-catch what you have interest in instead of all DataAccessExceptions (I mean the sub-class of DataAccessException).

[1] DataAccessException Table

The following table lists some of the subclasses of DataAccessException.

DataAccessException Is thrown when…
CleanupFailureDataAccessException An operation completes successfully, but an exception ocurs while cleaning up database resources. (ex. Closing a Connection)
DataAccessResourceFailureException A data access resource fails completely, such as not being able to connect to a database.
DataIntegrityViolationException An insert or update results in an integrity violation, such as a violation of a unique constraint.
DataRetrievalFailureException Certain data could not be retrieved, such as not finding a row by primary key.
DeadlockLoserDataAccessException The current process was a deadlock loser.
IncorrectUpdateSemanticsDataAccessException When something unintended happens on an update, such as updating more rows than expected. When this exception is thrown, the operation’s transaction has not been rolled back.
InvalidDataAccessApiUsageException A data access Java API is used incorrectly, such as failing to compile a query that must be compiled before execution.
InvalidDataAccessResourceUsageException A data access resource is used incorrectly, such as using bad SQL grammar to access a relational database.
OptimisticLockingFailureException There is an optimistic locking failure. This will be thrown by ORM tools or by custom DAO implementations.
TypeMismatchDataAccessException There is a mismatch between Java type and data type, such as trying to insert a String into a numeric database column.
UncategorizedDataAccessException Something goes wrong, but a more specific exception cannot be determined.


[2] Hierarchy For org.springframework.dao.DataAccessException


see springframewrok1.2.9 javadoc

Package :
(1) java.lang.
(2) org.springframework.core.
(3) org.springframework.dao.
Exception Class Hierarchy :
Object (1)
   Throwable
      Exception
          RuntimeException
            NestedRuntimeException (2)
               DataAccessException (3)
                  CleanupFailureDataAccessException
                  ConcurrencyFailureException
                     OptimisticLockingFailureException
                     PessimisticLockingFailureException
                        CannotAcquireLockException
                        CannotSerializeTransactionException
                        DeadlockLoserDataAccessException
                  DataAccessResourceFailureException
                  DataIntegrityViolationException
                  DataRetrievalFailureException
                     IncorrectResultSizeDataAccessException
                  InvalidDataAccessApiUsageException
                  InvalidDataAccessResourceUsageException
                     IncorrectUpdateSemanticsDataAccessException
                     TypeMismatchDataAccessException
                  UncategorizedDataAccessException

Spring’s Hibernate Exceptions class Hierarchy

      org.springframework.dao
      org.springframework.orm
      org.springframework.orm.hibernate

DataRetrievalFailureException
   ObjectRetrievalFailureException
      HibernateObjectRetrievalFailureException
ConcurrencyFailureException
   OptimisticLockingFailureException
      ObjectOptimisticLockingFailureException
         HibernateOptimisticLockingFailureException
InvalidDataAccessResourceUsageException
   HibernateQueryException
UncategorizedDataAccessException
   HibernateJdbcException
   HibernateSystemException

When using HibernateTemplate, its execute() method will be called everytime when you execute select, insert, update or delete operation.
HibernateException and SQLException will be catched within execute() method.
If HibernateException is catched, it will be converted to DataAccessException using convertHibernateAccessException() method in HibernateAccessor.
And SQLException will be converted to DataAccessException using convertJdbcAccessException() method in HibernateAccessor when catched.
Actually, when converting HibernateException, if HibernateException is instance of JDBCException, it will be treated as SQLException to convert (JDBCException.getSQLException is called as a method parameter).
When converting SQLException and JDBCException, SQLExceptionTranslator’s translate() method is called to translate SQLException to an appropriate DataAccessException.

more details to see HibernateTemplate, HibernateAccessor, SessionFactoryUtils, SQLExceptionTranslator, SQLErrorCodeSQLExceptionTranslator, SQLStateSQLExceptionTranslator source code

handle_hibernateexception.gif

Table(1) Convert HibernateException to DataAccessException

net.sf.hibernate org.springframework.orm.hibernate
HibernateException DataAccessException
UnresolvableObjectException HibernateObjectRetrievalFailureException
ObjectNotFoundException HibernateObjectRetrievalFailureException
ObjectDeletedException HibernateObjectRetrievalFailureException
WrongClassException HibernateObjectRetrievalFailureException
StaleObjectStateException HibernateOptimisticLockingFailureException
QueryException HibernateQueryException
PersistentObjectException InvalidDataAccessApiUsageException
TransientObjectException InvalidDataAccessApiUsageException
Others HibernateSystemException

Spring’s JDBC Exceptions class Hierarchy

      org.springframework.dao
      org.springframework.jdbc

InvalidDataAccessResourceUsageException
   BadSqlGrammarException
DataAccessResourceFailureException
   CannotGetJdbcConnectionException
InvalidDataAccessResourceUsageException
   InvalidResultSetAccessException
InvalidDataAccessResourceUsageException
   IncorrectUpdateSemanticsDataAccessException
      JdbcUpdateAffectedIncorrectNumberOfRowsException
DataRetrievalFailureException
   LobRetrievalFailureException
UncategorizedDataAccessException
   SQLWarningException
   UncategorizedSQLException

Before looking Table(2)

SQLExceptionTranslator is in interface, default SQLErrorCodeSQLExceptionTranslator and SQLStateSQLExceptionTranslator implement it.
Within SQLErrorCodeSQLExceptionTranslator’s translate() method, customer translator will be looked for first. That means customer translator will be userd first.
Next, the DataAccessionException listed in Table(2) will be thrown against the grouped error codes.
If no error codes are available, SQLStateSQLExceptionTranslator’s translate() method is called to handle this.

Table(2) Convert SQLException to DataAccessException

java.sql org.springframework.jdbc (1)
org.springframework.dao (2)
SQLException Spring’s JDBC Exceptions
DataAccessException
SQLException BadSqlGrammarException (1)
InvalidResultSetAccessException (1)
DataAccessResourceFailureException (2)
DataIntegrityViolationException (2)
CannotAcquireLockException (2)
DeadlockLoserDataAccessException (2)
CannotSerializeTransactionException (2)

According to SQLState, BadSqlGrammarException or DataIntegrityViolationException will be thrown in SQLStateSQLExceptionTranslator’s translate() method.
Otherwise, UncategorizedSQLException will be thrown. (Not able to diagnose all problems, , but is portable between databases anddoes need require special initialization (no database vendor detection etc). by Rod Johnson and Juergen Hoeller)




Schedule a cron job with Springframework


Discussion

Not everything that happens in an application is the result of a user action. Sometimes the software itself initiates an action. That is the schedualing job.
If you want to control over when the job is run more precise, Using a Cron Job is a good choice.

Solution

(1) Create a Job interface and the implement

// a Job interface
public interface SHJob {
  void excJob();
}

// a Job implement
public class SHJobImpl implements SHJob {
  private static Logger logger = 
    Logger.getLogger(SHJobImpl.class.getName());
  public void excJob() {
    Date startTime = 
      new Date(System.currentTimeMillis());
    String startTimeStr = 
      DateFormatUtils.format(startTime, 
        "yyyy/MM/dd HH:mm:ssS", Locale.JAPAN);
    logger.info("Start Time: " + startTimeStr);
    for(int i = 0; i < 5; i++) {
      logger.info("do Job: " + i);
    }
    Date endTime = new Date(System.currentTimeMillis());
    String endTimeStr = 
      DateFormatUtils.format(endTime, 
        "yyyy/MM/dd HH:mm:ssS", Locale.JAPAN);
    logger.info("Start Time: " + endTimeStr);
  }
}

(2) Configurate how to schedule the cron job in spring’s config file

<!-- a Job implementation -->
<bean id="shJob" 
  class="org.sh.common.job.impl.SHJobImpl"/>

<!-- a Job Detail Factory tells 
  which Job class and which method is 
    the real job implementation -->
<bean id="shJobDetail" 
  class="org.springframework.scheduling.quartz.
             MethodInvokingJobDetailFactoryBean">
    <property name="targetObject">
        <ref bean="shJob"/>
    </property>
    <property name="targetMethod">
        <value>excJob</value>
    </property>
</bean>

<!-- a Cron Trigger gives you more precise 
  control over when your job is run. -->
<bean id="cronTrigger" 
  class="org.springframework.scheduling.quartz.
             CronTriggerBean">
    <property name="jobDetail">
        <ref bean="shJobDetail"/>
    </property>
    <property name="cronExpression">
        <value>0 * * * * ?</value>
    </property>
</bean>
<!-- Scheduler Factory starts the job -->
<bean 
  class="org.springframework.scheduling.quartz.
              SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <ref bean="cronTrigger"/>
            <!-- Start other jobs here -->
            <!-- 
            <ref bean="cronTriggerOther"/>
            -->
        </list>
    </property>
</bean>

Cron Expression

A cron expression has at least 6 (and optionally 7) time elements, separated by spaces. In order from left to right, the elements are defined as follows:

    1) Seconds (0 – 59)
    2) Minutes (0 – 59)
    3) Hours (0 – 23)
    4) Day of Month (1 – 31)
    5) Month (1 – 12 or JAN – DEC)
    6) Day of Week (1 – 7 or SUN – SAT)
    7) Year (1970 – 2099)

Each of these elements can be specified with an explicit value (eg. 6), a range value (eg. 9-12), a list (eg. 9, 11, 13), or a wildcast (eg. *). The day of month and day of week elements are mutually exclusive, so you should also indicate which one of these fields you don’t want to set by specifying it with question mask (?)

The following Table is some examples:

Expression What it means
0 0 10,14,16 * * ? Everyday at 10 am, 2 pm, and 4 pm
0 0,15,30,45 * 1-10 * ? Every 15 minutes on the first 10 days of every month
30 0 0 1 1 ? 2012 30 seconds after midnight on January 1st, 2012
0 0 8-5 ? * MON-FRI Every working hour of every business day




Integrating Struts with Spring


Discussion

When your project work using Struts as Web framework, and Spring as Business Logic framework, integrating them is a good choice, and let Springframework manage Struts’ action.

Solution

     (1) Using Spring’s ActionSupport to integrate Struts with Spring
     (2) Override Struts’s RequestProcessor with Spring’s DelegatingRequestProcessor
     (3) Delegate Struts Action management to the Spring framework

Common Issue

Register Struts plugin with Spring’s ContextLoaderPlugin

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.3//EN"
  "http://struts.apache.org/dtds/struts-config_1_3.dtd" >
<struts-config>
  <form-beans>
    <form-bean name="courseSearchForm" 
             type="jp.co.gky.form.CourseSearchForm"/>
  </form-beans>
  <action-mappings>
    <action
        path="/listCourse"
        type="jp.co.gky.action.ListCourseAction"
        name="courseSearchForm"
        scope="request">
      <forward name="listCourseOk" path="/showCourses.jsp"/>
    </action>
  </action-mappings>
  <message-resources parameter="MessageResource"/>
  <plug-in className="
    org.springframework.web.struts.ContextLoaderPlugIn">
    <set-property property="contextConfigLocation"
          value="/WEB-INF/applicationContext.xml"/>
  </plug-in>
</struts-config>

(1) Using Spring’s ActionSupport to integrate Struts with Spring

org.springframework.web.struts.ActionSupport provides a getWebApplicationContext() method to easily obtain a Spring context.

public class ListCourseAction 
                        extends ActionSupport {
  public ActionForward execute(
      ActionMapping mapping, ActionForm form,
      HttpServletRequest request, HttpServletResponse response) 
      throws Exception {
    ApplicationContext context = getWebApplicationContext();
    CourseService service = context.getBean("courseService");
    return mapping.findForward("doAddOk");
  }
}

Something good:
     Getting ApplicationContext is easy.
 
Something bad:
     It couples the Struts Action to the Spring framework. If you ever decide to replace Spring framework, you have to rewrite the code. So it’s not a desirable solution against the solution (2) and solution (3).

(2) Override Struts’s RequestProcessor with Spring’s DelegatingRequestProcessor

Decoupling Spring framework from Struts Action is a much smarter design choice. One way to do this is to override the Struts’ RequestProcessor with Spring’s DelegatingRequestProcessor.

    i) Override Struts’ RequestorProcessor with Spring’s DelegatingRequestProcessor in struts-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.3//EN"
  "http://struts.apache.org/dtds/struts-config_1_3.dtd" >
<struts-config>
  <form-beans>
    <form-bean name="courseSearchForm" 
             type="jp.co.gky.form.CourseSearchForm"/>
  </form-beans>
  <action-mappings>
    <action
        path="/listCourse"
        type="jp.co.gky.action.ListCourseAction"
        name="courseSearchForm"
        scope="request">
      <forward name="listCourseOk" path="/showCourses.jsp"/>
    </action>
  </action-mappings>
  <controller processorClass="
    org.springframework.web.struts.DelegatingRequestProcessor"/>
  <message-resources parameter="MessageResource"/>
  <plug-in className="
    org.springframework.web.struts.ContextLoaderPlugIn">
    <set-property property="contextConfigLocation"
          value="/WEB-INF/applicationContext.xml"/>
  </plug-in>
</struts-config>

    ii) Register Struts’ Action in spring’s config file

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans -//SPRING//DTD BEAN//EN
  "http://www.springframework.org/dtd/spring-beans.dtd" >
<beans>
  <bean name="/listCourse" 
          class="org.learning.action.ListCourseAction">
    <property name="courseService">
      <ref name="courseService"/>
    </property>
  </bean>
  <bean id="courseService" 
          class="org.learning.business.CourseServiceImpl"/>
</beans>

The following is ListCourseAction source code.

public class ListCourseAction extends Action {
  private CourseService courseService;
  public ActionForward execute(
      ActionMapping mapping, ActionForm form, 
      HttpServletRequest request, HttpServletResponse response) 
      throw Exception {
    CourseSearchForm courseSearchForm = 
        (CourseSearchForm) form;
    CourseService service = getCourseService();
    List courseList = 
        service.listCourse(courseSearchForm.getKeyword());
    request.setAttribute("courseList", courseList);
    return mapping.findForward("success");
  }
  // Inject the service here
  public CourseService getCourseService() {
    return this.courseService;
  }
  public void setCourseService(CourseService courseService) {
    this.courseService = courseService
  }
}

Something good:
     This design keep Struts from knowing it’s being managed by Spring framework while giving you all benefits of Spring’ action management framework.
 
Something bad:
     If you’re using a different RequestProcessor, then you would need to integrate the Spring’s DelegatingRequestProcessor manually. The added code would become a maintenance hassle and would also reduce your application flexibiliy going forward. Moreover, there has some talk of replacing the Struts RequestProcessor with a chain of command. Such a change would negatively impact the longevity of this solution.

(3) Delegate Struts Action management to the Spring framework

By registering a proxy in struts-config.xml action mapping to do this. The proxy is responsible for looking up the Struts Action in the Spring context. Because the Action is under Spring’s control, it populates the action’s JavaBean properties and leaves the door open to applying features such as Spring’s AOP interceptors.

    i) Register Spring’s DelegatingActionProxy into Struts’ action configuration in struts-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.3//EN"
  "http://struts.apache.org/dtds/struts-config_1_3.dtd" >
<struts-config>
  <form-beans>
    <form-bean name="courseSearchForm" 
             type="jp.co.gky.form.CourseSearchForm"/>
  </form-beans>
  <action-mappings>
    <action
      path="/listCourse"
      type="
        org.springframework.web.struts.DelegatingActionProxy"
      name="courseSearchForm"
      scope="request">
      <forward name="listCourseOk" path="/showCourses.jsp"/>
    </action>
  </action-mappings>
  <message-resources parameter="MessageResource"/>
  <plug-in className="
    org.springframework.web.struts.ContextLoaderPlugIn">
    <set-property property="contextConfigLocation"
          value="/WEB-INF/applicationContext.xml"/>
  </plug-in>
</struts-config>

    ii) Register Struts’ Action in spring’s config file

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans -//SPRING//DTD BEAN//EN
  "http://www.springframework.org/dtd/spring-beans.dtd" >
<beans>
  <bean name="/listCourse" 
          class="org.learning.action.ListCourseAction">
    <property name="courseService">
      <ref name="courseService"/>
    </property>
  </bean>
  <bean id="courseService" 
          class="org.learning.business.CourseServiceImpl"/>
</beans>

The following is ListCourseAction source code.

public class ListCourseAction extends Action {
  private CourseService courseService;
  public ActionForward execute(
      ActionMapping mapping, ActionForm form, 
      HttpServletRequest request, HttpServletResponse response) 
      throw Exception {
    CourseSearchForm courseSearchForm = 
        (CourseSearchForm) form;
    CourseService service = getCourseService();
    List courseList = 
        service.listCourse(courseSearchForm.getKeyword());
    request.setAttribute("courseList", courseList);
    return mapping.findForward("success");
  }
  // Inject the service here
  public CourseService getCourseService() {
    return this.courseService;
  }
  public void setCourseService(CourseService courseService) {
    this.courseService = courseService
  }
}

Here is the benefits:
     The action-delegating solution is the best of the three. The Struts Action has no knowledge of Spring and could be used in non-Spring applications without changing a single line of codes. It’s not at the mercy of a change to the RequestProcessor, and it can take advantage of Spring’s AOP features. Once you have Struts’ Action under Spring’s control, you can leverage Spring to give them more pizzazz. For example, without Spring, all Actions must be thread-safe. If you set tag’s singleton attribute to “false”, however your application will have a newly minted action object on every request. You might not need this feature, but it’s nice to know you have it in your, back pocket. You can also take advantage of Spring’s lifecycle method. For example, the tag’s init-method attribute is used to run a method when Struts Action is instantiated. Similarly, the destory-method attribute executes a method just before the bean is removed from the container. These methods are a great way to manage expensive objects in much the same way as the Servlet lifecycle does.
 
 
     Spring framework recognizes <bean> tag’s name attribute to delegate Struts Action. It’s the same value with path attribute of <action> tag in struts-config.xml




Get Real Path from ServletContext


Discussion
Sometimes your web application will generate some file into your physisical disk, such as *.pdf, *.xls, *.txt, …
according to your business requirement. So your application has to know the real path to generate the requested
files. If you hard code the real path in some configuration file to do this, OK, no problem. But sometimes maybe
you have no right to know the real path where your web application is residing. Such as if you rent a sharing host
of another host renting company. And the administrator won’t tell you the real path information.
On another hand, if you hard code the real path, it will not be a good choice when imigrating your web application
I think. You have to change your configuration file each time when you change a running server.
So, here is the problem, how to do?
Thank God, the J2EE support you ServletContext, and we can fix this up.
ServletContext supports us a method called getRealPath(String path). If a virtual path as the path parameter, the return
value is the real path for the given virtual path.

Here is the example:

The web application residing real path: C:\jakarta-tomcat-5.0.28\webapps\dispatchEx\
The dispatchEx is the web application. The application will generate a pdf file everytime when the user clicks
the link in the web page. And the file will be generate under the real path of this web application.

Solution

public class GeneratePdfAction extends Action {
  public ActionForward execute(ApplicationMapping mapping, ActionForm form,
          HttpServletRequest request, HttpServletResponse response) throws Exception {
    String dir = 
        getServlet().getServletContext().getRealPath("/");
    //dir=C:\jakarta-tomcat-5.0.28\
            webapps\dispatchEx\ while debuging
    generatePdf(dir);
    return mapping.findforward("success");

  }
  private void generatePdf(String dir) {
    String filename = dir + 
      DateFormatUtils.format(System.currentTimeMillis(), 
                                           "yyyyMMddHHmm") +
          RandomStringUtils.random(4, true, false) + ".pdf";
    this.pdfGenerator.generatePdf(filename);
  }
  public PdfGenerator getPdfGenerator() {
    return this.pdfGenerator
  }
  public void setPdfGenerator(PdfGenerator pdfGenerator) {
    this.pdfGenerator = pdfGenerator;
  }
}




Using Struts EventDispatchAction


Discussion
When using more than two

<input type="image" src="image/submit_btn.gif" border="0" alt="Submit"/>


tags as submit button to submit form data to request, Struts 1.1’s LookupDispatchAction doesn’t work.

for example:

<!DOCTYPE HTML 
  PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<%@ page 
  contentType="text/html;charset=UTF-8"
    language="java" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
<html>
<head>
 <title>EventDispatchAction Example</title>
  <META http-equiv="Content-Type" content="text/html; 
  charset=UTF-8"/>
  <link href="css/style.css" 
  rel="stylesheet" type="text/css" >
</head>
<body>
<html:form action="/gkyEventDispatch">
<TABLE border="0" cellspacing="0" cellpadding="0" width="700">
  <TR>
    <TD>Name</TD>
    <TD>
      <html:text property="name" styleClass="textInput"/>
    </TD>
  </TR>
  <TR>
    <TD>
      <html:image property="search" 
        src="image/submit_search_btn.gif" 
        border="0"/>
    </TD>
    <TD>
      <html:image property="add" 
        src="image/submit_add_btn.gif" 
        border="0"/>
      </TD>
  </TR>
</TABLE>
</html:form>
</body>
</html>

Solution

Using EventDispatchAction instead of LookupDispatchAction to implement one form multi-image-submit button.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.3//EN"
  "http://struts.apache.org/dtds/struts-config_1_3.dtd" >
<struts-config>
  <form-beans>
    <form-bean name="gkyForm" type="jp.co.gky.form.GkyForm"/>
  </form-beans>
  <action-mappings>
    <action
        path="/gkyInit"
        type="jp.co.gky.action.GkyInitAction"
        name="gkyForm"
        scope="request">
      <forward name="gkyInitOk" path="/gkyInput.jsp"/>
    </action>
    <action
        path="/gkyEventDispatch"
        type="jp.co.gky.action.GkyEventDispatchAction"
        name="gkyForm"
        input="/gkyInput.jsp"
        validate="false"
        parameter="add=doAdd,search=doSearch"
        scope="request">
      <forward redirect="false" 
          name="doAddOk" path="/gkyInit.do"/>
      <forward redirect="false" 
          name="doSearchOk" path="/gkyInit.do"/>
    </action>
  </action-mappings>
  <message-resources parameter="MessageResource"/>
 
</struts-config>

public class GkyEventDispatchAction 
                        extends EventDispatchAction {
  public ActionForward doAdd(
      ActionMapping mapping, ActionForm form,
      HttpServletRequest request, HttpServletResponse response) 
      throws Exception {
    ActionErrors errors = new ActionErrors();
    if(!isTokenValid(request)) {
      errors.add(ActionMessages.GLOBAL_MESSAGE, 
          new ActionMessage("errors.token"));
    }
    resetToken(request);
    if(!errors.isEmpty()) {
      saveErrors(request, errors);
      saveToken(request);
      return mapping.getInputForward();
    }
    // .....
    request.setAttribute("newKeyValue", reqMsg);
    return mapping.findForward("doAddOk");
  }

  public ActionForward doSearch(
      ActionMapping mapping, ActionForm form,
      HttpServletRequest request, HttpServletResponse response)
      throws Exception {
    ActionErrors errors = new ActionErrors();
    if(!isTokenValid(request)) {
      errors.add(ActionMessages.GLOBAL_MESSAGE, 
          new ActionMessage("errors.token"));
    }
    resetToken(request);
    if(!errors.isEmpty()) {
      saveErrors(request, errors);
      saveToken(request);
      return mapping.getInputForward();
    }
    //....
    request.setAttribute("searchKeyValue", reqMsg);
    return mapping.findForward("doSearchOk");
  }
}




Avoid Form Data Duplicate submitting


Discussion
Form data duplicate submitting can caused by either:
1.Using the browser BACK button to return to the previous page and resubmit the form.2.Refreshing this page and selecting OK when the broswer ask whether you want to resubmit the form data.

Solution

Using Token.

The following is the sample code:

public class InitAction extends Action { 
  public ActionForward execute( 
    ActionMapping mapping, ActionForm form, 
      HttpServletRequest request, HttpServletResponse response) 
      throws Exception { 
    GkyForm gkyForm = (GkyForm) form; 
    gkyForm.setKey(""); 
    gkyForm.setValue(""); 
    // avoid duplicated submitting to set Token here. 
    saveToken(request); 
    return mapping.findForward("gkyInitOk"); 
  } 
}  

public class SubmitAction extends Action { 
  public ActionForward execute( 
    ActionMapping mapping, ActionForm form, 
      HttpServletRequest request, HttpServletResponse response) 
      throws Exception { 
    ActionErrors errors = new ActionErrors(); 
    if(!isTokenValid(request)) { 
      errors.add(ActionMessages.GLOBAL_MESSAGE, 
        new ActionMessage("errors.token")); 
    } 
    resetToken(request); 
    if(!errors.isEmpty()) { 
      saveErrors(request, errors); 
      saveToken(request); 
      return mapping.getInputForward();  
    } 
    GkyForm gkyForm = (GkyForm) form; 
    String key = gkyForm.getKey(); 
    String value = gkyForm.getValue(); 
    // ..... 
    return mapping.findForward("doAddOk"); 
  } 
}

The following is Processing Flow

processing-flow.gif



Defeating Browser Caching

Discussion
To speed processing, browsers frequently keep a copy of a visited page on the client’s local system. If an identical URL for the original page is requested and that page hasn’t expired, the browser may display the page from the local cache instead of issuing a new request. This caching reduces network traffic and improves the user experience significantly. However, this can cause problems for dynamically generated pages. Consider a JSP page that renders data retrieved from the HTTP session.If data stored in the session changes, the browser won’t be aware of the change.When the browser receives a new request for the page, it serves up the old page instead.
Solution

[1] general solution:in struts-config.xml:

<controller nocache="true"/>

[2] per-action basis solution:

Customize action-mapping for avoiding browser caching

public class NocacheActionMapping extends ActionMapping { 
  private String nocache; 
  private boolean nocacheEnabled = false;   

  public String getNocache() { 
    return this.nocache; 
  } 
  public void setNocache(String nocache) { 
    this.nocache = nocache; 
    this.nocacheEnabled = new Boolean(nocache).booleanValue(); 
  } 
  public boolean isNocacheEnabled() { 
    return this.nocacheEnabled; 
  } 
}

Customize RequestProcessor

public class NocacheRequestProcessor extends RequestProcessor { 
  protected ActionForward processActionPerform( 
                    HttpServletRequest request, 
                    HttpServletResponse response, 
                    Action action, 
                    ActionForm form, 
                    ActionMapping mapping) 
                    throw IOException, ServletException { 
    ActionForward forward = null; 
    if(mapping instanceof NocacheActionMapping) { 
      NocacheActionMapping nocacheMapping = 
                (NocacheActionMapping) mapping; 
      if(nocacheMapping.isNocacheEnabled()) { 
        response.setHeader("Pragma", "No-cache"); 
        response.setHeader("Cache-Control", "no-cache"); 
        response.setDateHeader("Expires", 0); 
      } 
    } 
    forward = 
        super.processActionPerform( 
                  request, response, action, form, mapping); 
    return forward; 
  } 
}

Struts-config for action-based response caching


<!DOCTYPE struts-config PUBLIC 
"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN" 
"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd"> 
<struts-config> 
  <action-mappings 
      type="com.xxx.common.actionMapping.NocacheActionMapping"> 
    <action path="/xCustomized" 
        type="com.xxx.action.CustomizedAction" 
        name="customizedForm" 
        scope="request"> 
      <set-property property="nocache" value="true"/> 
      <forward name="success" path="/customizedPage.jsp"/> 
    </action> 
  </action-mappings> 
  <controller 
    processorClass="com.xxx.ctrl.NocacheRequestProcessor"/> 
</struts-config>