Skip navigation



Using middlegen to generate hbm and java

Discussion

This article is about how to use middlegen to generate hibernate mapping xml file and POJO.

ant build.xml


<?xml version="1.0"?>
<!DOCTYPE project[
  <!ENTITY database SYSTEM "file:./db/mysql.xml">
]>
<project name="bug_management" basedir="." default="all">
  <property name="name" value="genkiya" />
  <property name="package" value="jp.co.bug_management.common.entity" />
  <property name="gui" value="true" />
  <property name="lib.dir" value="${basedir}/lib" />
  <property name="src.dir" value="${basedir}/src" />
  <property name="db.dir" value="${basedir}/db" />

  &database;
  <path id="lib.class.path">
    <pathelement path="${database.driver.classpath}"/>
    <fileset dir="${lib.dir}">
      <include name="*.jar"/>
    </fileset>
  </path>
  <!-- ====================== -->
  <!-- Task Initilization                        -->
  <!-- ======================-->
  <target name="init">
    <available 
      property="xdoclet1.2+" 
      classname="xdoclet.modules.hibernate.HibernateDocletTask" 
      classpathref="lib.class.path"/>
  </target>
  
  <!-- ====================== -->
  <!-- Fails if XDoclet 1.2.x is not on classpath -->
  <!-- ====================== -->
  
  <!--target name="fail-if-no-xdoclet-1.2" unless="xdoclet1.2+">
    <fail>
    You must download several jar files before you can build Middlegen.
    Execute the "download-deps" target. Then try to build again.
    
    If you are behind a proxy, you should define the properties
    http.proxyHost and http.proxyPort. Example:
    
    ant -Dhttp.proxyHost=foo.com -Dhttp.proxyPort=8080
    
    It's also possible to download the jars manually.
    </fail>
  </target-->
  
  <!-- ====================== -->
  <!-- Create tables                                -->
  <!-- ====================== -->
  
  <!--target name="create-tables" 
    depends="init,fail-if-no-xdoclet-1.2,
      check-driver-present,panic-if-driver-not-present"
    description="Create tables">
    <echo>Create tables using URL ${database.url}</echo>
    <sql 
      classpath="${database.driver.classpath}"
      driver="${database.driver}"
      url="${database.url}"
      userid="${database.userid}"
      password="${database.password}"
      src="${database.script.file}"
      print="true"
      output="result.txt"/>
  </target-->
  
  <target name="check-driver-present">
    <available file="${database.driver.file}" 
      type="file" property="driver.present"/>
  </target>
  
  <target name="panic-if-driver-not-present" 
    unless="driver.present">
    <fail>
    The JDBC driver you have specified by 
    including one of the files in ${basedir}/lib
    doesn't exist. You have to download this 
    driver separately and put it in ${database.driver.file}
    Please make sure you're using a version 
    that is equal or superior to the one we looked for.
    If you name the driver jar file differently, 
    please update the database.driver.file property
    in the ${basedir}/db/xxx.xml file accordingly.
    </fail>
  </target>
  
  <!-- =================== -->
  <!-- Run Middlegen                   -->
  <!-- =================== -->
  
  <target 
    name="middlegen"
    description="Run Middlegen"
    unless="middlegen.skip"
    depends="init,check-driver-present,
        panic-if-driver-not-present">
    
    
    <taskdef 
      name="middlegen"
      classname="middlegen.MiddlegenTask"
      classpathref="lib.class.path"/>
      
    <middlegen 
      appname="${name}"
      prefsdir="${src.dir}"
      gui="${gui}"
      databaseurl="${database.url}"
      initialContextFactory="${java.naming.factory.initial}"
      providerURL="${java.naming.provider.url}"
      datasourceJNDIName="${datasource.jndi.name}"
      driver="${database.driver}"
      username="${database.userid}"
      password="${database.password}"
      schema="${database.schema}"
      catalog="${database.catalog}"
      includeViews="false">
      
      <hibernate
        destination="${src.dir}/jp/co/bug_management/common/hbm"
        package="${package}"
        genXDocletTags="true"
        javaTypeMapper=
            "middlegen.plugins.hibernate.HibernateJavaTypeMapper">
      </hibernate>
    </middlegen>
  </target>
  
  <!-- =================== -->
  <!-- Build Everything                 -->
  <!-- =================== -->
  
  <target name="all" depends="middlegen" description="Build Everything"/>
  
  <!-- =================== -->
  <!-- Genernate ValueObject From Mapping Files -->
  <!-- =================== -->
  
  <target 
    name="hbm2java" 
    depends="middlegen" 
    description="Generate .java from .hbm files.">
    
    <taskdef 
      name="hbm2java"
      classname=
        "net.sf.hibernate.tool.hbm2java.Hbm2JavaTask"
      classpathref="lib.class.path"/>
      
    <hbm2java 
      output="${src.dir}"
      classpathref="lib.class.path">
      <fileset dir="${src.dir}">
        <include name="**/*.hbm.xml"/>
      </fileset>
    </hbm2java>
  </target>
</project>


file:./db/mysql.xml :
   <!-- ============================================ -->
   <!-- ant properties/targets for mysql                                                      -->
   <!-- note: this is not a proper xml file  (there is no root element)     -->
   <!--       it is intended to be imported from a *real* xml file              -->
   <!-- ============================================ -->

  <property name="database.script.file" 
      value="${db.dir}/create_dlfl.sql"/>
  <property name="database.driver.file"
      value="${lib.dir}/mysql-connector-java-5.0.7-bin.jar"/>
  <property name="database.driver.classpath"
      value="${database.driver.file}"/>
  <property name="database.driver"
      value="com.mysql.jdbc.Driver"/>
  <property name="database.url"
      value="jdbc:mysql://127.0.0.1:3306/bug_management"/>
  <property name="database.userid"
      value="root"/>
  <property name="database.password"
      value="mysql"/>
  <property name="database.schema"
      value="bug_management"/>
  <property name="database.catalog"
      value=""/>


Required jar files:

commons-collections-3.2.jar
commons-lang-2.3.jar
commons-logging-1.1.jar
commons-pool-1.3.jar
commons-validator-1.3.1.jar
hibernate2.jar
hibernate-tools.jar
jdom.jar
log4j-1.2.8.jar
middlegen-2.1.jar
middlegen-hibernate-plugin-2.1.jar
mysql-connector-java-5.0.7-bin.jar
velocity-1.4-dev.jar
xdoclet-hibernate-module-1.2.2-RC1.jar

put all these jar files into /lib directory.
Run ant task [all] or [hbm2java].



Handle Exception two ways in Web app

Discussion
In J2ee web application, there are two types of errors in the Web Tier:


  • HTTP Errors (400 – 500 series response)
    Communicate problems to Web clients.
  • Java Exceptions
    Communicate problems to Web container.



Exceptions can be generated within the tier or passed back from other tiers.
If you want exceptions to be passed on to a client, you must convert them to HTTP errors.



Exceptions thrown to the client

  • javax.servlet.ServletException
  • javax.io.IOException


In J2ee Web application, javax.servlet.ServletException and java.io.IOException will be thrown to the client.
All the exceptions will be wrapped into ServletException and thrown to the client, if you do not handle them by yourself.
So, we decide handling exceptions two ways:

  1. Handle Exceptions which are thrown under Action tier using ExceptionHandler in Struts (see post: Exception Handling in Struts)
  2. Handle Exceptions which are thrown over Action tier by configurating web.xml using tag <error-page>


approach 2:

In web.xml


<error-page>
    <exception-type>javax.servlet.ServletException</exception-type>
    <location>/error.do</location>
</error-page>
<error-page>
    <exception-type>javax.io.IOException</exception-type>
    <location>/error.do</location>
</error-page>


Define ErrorAction to handle exceptions


public class ErrorAction extends Action {
     /**
     * The request attribute under which we 
     * forward an HTTP status message
     * (as an object of type STring) to an error page.
     */
    public static final String ERROR_MESSAGE_ATTR =
        "javax.servlet.error.message";
    /**
     * The request attribute under which we 
     * forward a Java exception type
     * (as an object of type Class) to an error page.
     */
    public static final String EXCEPTION_TYPE_ATTR =
        "javax.servlet.error.exception_type";
    /**
     * The request attribute under which we 
     * forward the request URI
     * (as an object of type String) of the page on which 
     * an error occurred.
     */
    public static final String EXCEPTION_PAGE_ATTR =
        "javax.servlet.error.request_uri";
    /**
     * The request attribute under which we 
     * forward a Java exception
     * (as an object of type Throwable) to an error page.
     */
    public static final String EXCEPTION_ATTR =
        "javax.servlet.error.exception";
    /**
     * The request attribute under which we 
     * forward an HTTP status code
     * (as an object of type Integer) to an error page.
     */
    public static final String STATUS_CODE_ATTR =
        "javax.servlet.error.status_code";

    public ActionForward execute(
            ActionMapping mapping, 
            ActionForm form,
            HttpServletRequest request, 
            HttpServletResponse response)
            throws Exception {
        // ErrorMessage (java.lang.String)
        Object errorMsg = 
            request.getAttribute(ERROR_MESSAGE_ATTR);
        // Exception (java.lang.Exception)
        Object exception = 
            request.getAttribute(EXCEPTION_ATTR);
        // Exception Class (java.lang.Class)
        Object exceptionType = 
            request.getAttribute(EXCEPTION_TYPE_ATTR);
        // uri (java.lang.String)
        Object page = 
            request.getAttribute(EXCEPTION_PAGE_ATTR);
        // Error Code (java.lang.Integer)
        Object errorCode = 
            request.getAttribute(STATUS_CODE_ATTR);
        
        ActionErrors errors = 
            (ActionErrors) request.getAttribute(
                Globals.ERROR_KEY);
        
        if(errors == null) {
            errors = new ActionErrors();
        }
        errors.add("innerError", 
            new ActionMessage("errors.inner.error", 
                errorMsg, exception, errorCode, page));
        saveErrors(request, errors);
        return mapping.findForward("toErrorPage");
    }
}


In struts-config.xml, define ErrorAction


<action
    path="/error"
    type="org.springframework.web.struts.DelegatingActionProxy"
    scope="request">
        <forward name="toErrorPage" path="/error_page.jsp"/>
</action>


MessageResources.properties:


errors.inner.error=Error Message: {0} 
<br />Error Type : {1} <br />Error Code : {2} 
<br />Request URI : {3}


Jsp File


<span class=”errorMsg”><html:errors /></span>


Doing so, and handling exception in struts using ExceptionHandler,
you can handle the exceptions thrown not only under Action tier, but also over the Action tier.



Exception Handling in Struts

Discussion
Exception is hard to aviod in our application. So we just catch it, log it, show error messages in Error Page.
There are lots of appoach to do this. Now I just introduce one of them.
Extending Struts’ ExceptionHandler and using <global-exceptions> tag in struts-config.xml

The following is the sample code:


public class MyExceptionHandler extends ExceptionHandler {
    public ActionForward execute(
            Exception ex, 
            ExceptionConfig ae,
            ActionMapping mapping, 
            ActionForm formInstance,
            HttpServletRequest request, 
            HttpServletResponse response)
            throws ServletException {
        ActionForward forward = null;
        ActionErrors errors = 
            (ActionErrors) request.getAttribute(
                Globals.ERROR_KEY);
        if(ex instanceof PdfGenerateException) {
            if(errors == null) {
                errors = new ActionErrors();
            }
            errors.add(new ActionMessage(ae.getKey()));
        }
        if(ex instanceof WrongQueryStringException) {
            if(errors == null) {
                errors = new ActionErrors();
            }
            errors.add(new ActionMessage(ae.getKey()));
        }
        if(errors != null && !errors.isEmpty()) {
            request.setAttribute(Globals.ERROR_KEY, errors);
        }
        if (ae.getPath() != null) {
            forward = new ActionForward(ae.getPath());
        } else {
            forward = 
                super.execute(
                    ex, ae, mapping, formInstance, request, response);
        }
        return forward;
    }
}

In struts-config.xml, define <global-exceptions>


<global-exceptions>
    <exception
        key="errors.generate.pdf" 
        handler="jp.co.gky.handler.MyExceptionHandler"
        path="/error.do"
        type="jp.co.gky.exception.PdfGenerateException" />
    <exception
        key="errors.request.error" 
        handler="jp.co.gky.handler.MyExceptionHandler"
        path="/error.do"
        type="jp.co.gky.exception.WrongQueryStringException" />
</global-exceptions>

MessageResources.properties:


errors.request.error=QueryString "pageNo" is not a integer.
errors.generate.pdf=Exception occured when generating pdf file.
errors.illegal.argument=Illegal Argument {0}

Jsp File

<span class=”errorMsg”><html:errors /></span>



Implement download file

Discussion
Sometimes maybe the business logic needs to generate some *.csv, *.pdf, *.txt, etc to be available for user downloading.
Struts 1.2x, supports DownloadAction to implement downloading file.
struts-extras-1.2.x.jar must be added in classpath.
DownloadAction is an abstract class, and you should override


protected StreamInfo getStreamInfo( 
    ActionMapping mapping, 
    ActionForm form, 
    HttpServletRequest request, 
    HttpServletResponse response) 
    throws Exception

method.

In DownloadAction, there is an inner interface StreamInfo and two implement inner class FileStreamInfo and ResourceStreamInfo.
The FileStreamInfo simplifies the downloading of a file from the disk. and ResourceStreamInfo simplifies the downloading of a web application resource.

When overriding getStreamInfo() method, just set ContentType, ServletContext, web application resource’s path and response header if using ResourceStreamInfo as
StreamInfo’s implement class.
If using FileStreamInfo, just set ContentType, java.io.File and response’s header, when overriding getStreamInfo() method.

The following is the sample code:

CsvDownloadAction (using ResourceStreamInfo)


public class CsvDownloadAction 
  extends DownloadAction { 
  protected StreamInfo getStreamInfo( 
      ActionMapping mapping, 
      ActionForm form, 
      HttpServletRequest request, 
      HttpServletResponse response) 
      throws Exception { 
      ServletContext servletCtx = 
        request.getSession(false).getServletContext(); 
      String contentType = "application/octet-stream"; 
      String fileName = "abc.csv"; 
      String path="WEB-INF/csv/" + fileName; 
      response.setHeader( 
        "Content-Disposition", 
        "attachment;filename=" + fileName); 
      return 
        new ResourceStreamInfo( 
          contentType, servletCtx, path); 
  } 
}

CsvDownloadAction (using FileStreamInfo)


public class CsvDownloadAction 
  extends DownloadAction { 
  protected StreamInfo getStreamInfo( 
      ActionMapping mapping, 
      ActionForm form, 
      HttpServletRequest request, 
      HttpServletResponse response) 
      throws Exception { 
      ServletContext servletCtx = 
        request.getSession(false).getServletContext(); 
      String contentType = "application/octet-stream"; 
      String fileName = "abc.csv"; 
      String path = servletCtx.getRealPath("/") +
         "WEB-INF/csv/" + fileName;
      response.setHeader( 
        "Content-Disposition", 
        "attachment;filename=" + fileName); 
      return 
        new FileStreamInfo(contentType, new File(path));
  } 
}

Jsp File

<html:link: path=”/csvDownload.do”>abc.csv</html:link>




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