Wednesday, July 24, 2013

Maven + Struts2 + Spring + Hibernate + Struts2-Convention-Plugin

After spending so many days and nights and days again I finally be able to achieve the basic Architecture. Well, the basic architecture was quite simple and you will be able to get a lot of tutorials online about it but, probably they have few things missing (i.e. Maven or sometimes Struts2-Convention-Plugin).
Reason behind choosing this structure is to make an easy expandable application structure with an isolation in between layers.

Reason Behind:
Maven is just used for dependency management of libraries and easy deployment.
Struts2 is used for UI.
Spring for service layer, as we all know we can easily change implementation which will be helpful for us to entertain the change request.
Hibernate for ORM that will play with the database.


Directory Structure:

Don't worry about such a big directory tree. One by one I will tell you each and everything about it. Before starting everything we will start telling you about the pom that will be required to setup the artifacts of your project.

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
       <modelVersion>4.0.0</modelVersion>
       <groupId>TestWar</groupId>
       <artifactId>TestWar</artifactId>
       <version>0.0.1-SNAPSHOT</version>
       <packaging>war</packaging>
       <name />
       <description />
       <dependencies>
              <!-- Struts 2 -->
              <dependency>
                     <groupId>org.apache.struts</groupId>
                     <artifactId>struts2-core</artifactId>
                     <version>2.1.8.1</version>
              </dependency>
              <!-- Struts 2 End-->
              <!-- Struts 2 + Spring plugins -->
              <dependency>
                     <groupId>org.apache.struts</groupId>
                     <artifactId>struts2-spring-plugin</artifactId>
                     <version>2.1.8.1</version>
              </dependency>

              <!-- MySQL database driver -->
              <dependency>
                     <groupId>mysql</groupId>
                     <artifactId>mysql-connector-java</artifactId>
                     <version>5.1.9</version>
              </dependency>
              <!-- Spring framework -->
              <dependency>
                     <groupId>org.springframework</groupId>
                     <artifactId>spring</artifactId>
                     <version>2.5.6</version>
              </dependency>
              <dependency>
                     <groupId>org.springframework</groupId>
                     <artifactId>spring-web</artifactId>
                     <version>2.5.6</version>
              </dependency>
              <!-- Hibernate core -->
              <dependency>
                     <groupId>asm</groupId>
                     <artifactId>asm-all</artifactId>
                     <version>3.3</version>
              </dependency>
              <dependency>
                     <groupId>asm</groupId>
                     <artifactId>asm</artifactId>
                     <version>3.3</version>
              </dependency>
              <dependency>
                     <groupId>org.hibernate</groupId>
                     <artifactId>hibernate-core</artifactId>
                     <version>3.6.7.Final</version>
              </dependency>

              <!-- Hibernate core library dependency start -->
              <dependency>
                     <groupId>org.apache.struts</groupId>
                     <artifactId>struts2-convention-plugin</artifactId>
                     <version>2.1.8.1</version>
              </dependency>
              <dependency>
                     <groupId>javax.servlet</groupId>
                     <artifactId>javax.servlet-api</artifactId>
                     <version>3.0.1</version>
                     <scope>provided</scope>
              </dependency>
              <dependency>
                     <groupId>log4j</groupId>
                     <artifactId>log4j</artifactId>
                     <version>1.2.15</version>
              </dependency>
      
       </dependencies>
       <build>
              <sourceDirectory>${basedir}/src</sourceDirectory>
              <outputDirectory>${basedir}/WebRoot/WEB-INF/classes</outputDirectory>
              <resources>
                     <resource>
                           <directory>${basedir}/src</directory>
                           <excludes>
                                  <exclude>**/*.java</exclude>
                           </excludes>
                     </resource>
              </resources>
              <plugins>
                     <plugin>
                           <artifactId>maven-war-plugin</artifactId>
                           <configuration>
                                  <webappDirectory>${basedir}/WebRoot</webappDirectory>
                                     <warSourceDirectory>${basedir}/WebRoot</warSourceDirectory>
                           </configuration>
                     </plugin>
                     <plugin>
                           <artifactId>maven-compiler-plugin</artifactId>
                           <configuration>
                                  <source>1.5</source>
                                  <target>1.5</target>
                           </configuration>
                     </plugin>
              </plugins>
       </build>
</project>


In above pom file we have basically two main tags that will be helpful for you one is plugins and second one is dependencies. Well dependencies deal with the libraries that are required for the project, and plugins help for compilation and war creation.

Note: One can easily change the artifact version of the library but make sure the other libraries that are interacting with that library must belong to the same artifacts.

Now, lets proceed towards the deployment descriptor.

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
      http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
      <welcome-file-list>
            <welcome-file>index.jsp</welcome-file>
      </welcome-file-list>
      <filter>
            <filter-name>struts2</filter-name>
            <filter-class>
       org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
            </filter-class>

            <init-param>
                  <param-name>config</param-name>
                  <param-value>
                      struts-default.xml,struts-plugin.xml, struts.xml 
                  </param-value>
            </init-param>
      </filter>
      <filter>
            <filter-name>AuthenticationFilter</filter-name>
            <filter-class>com.test.lab.filter.AuthenticationFilter</filter-class>
            <init-param>
                  <param-name>insecurePages</param-name>
                  <param-value> /login.action </param-value>
            </init-param>
      </filter>

      <filter-mapping>
            <filter-name>AuthenticationFilter</filter-name>
            <url-pattern>*.action</url-pattern>
      </filter-mapping>
      <filter-mapping>

            <filter-name>struts2</filter-name>
            <url-pattern>*.action</url-pattern>
      </filter-mapping>
      <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/classes/resources/config/SpringBeans.xml</param-value>
      </context-param>
     

      <listener>
            <listener-class>
              org.springframework.web.context.ContextLoaderListener
            </listener-class>
      </listener>
</web-app>





We have defined one custom filter that we will use to handle each and every request that will be coming to our application. Secondly other then the struts built-in filter also using a ContextLoaderListener that will load
all the beans of Spring at the time of initialization.

In StrutsPrepareAndExecuteFilter, we have mentioned a config param that will be showing that these files will only be loaded for struts configuration. Here people will get a bit confuse that why we are using configuration while Struts2 offer us a complete annotation based project setup (A.K.A Zero Configuration). Yes we are not going to use configuration for action classes but for interceptors we must need a configuration file that will initiate our interceptors at the time of context-loading.

This is our struts.xml which will be used to declare the interceptors that will be used in our actions.

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

<struts>
      <constant name="struts.devMode" value="false" />
      <constant name="struts.convention.package.locators.basePackage"
            value="com.test.lab" />
      <constant name="struts.convention.exclude.packages"
            value="org.apache.struts.*,org.apache.struts2.*,
org.springframework.web.struts.*,
org.springframework.web.struts2.*,
org.hibernate.*,WarFileName.*" />
      <constant name="struts.convention.action.checkImplementsAction"
            value="false" />

      <constant name="struts.convention.package.locators" value="action,actions,struts,struts2" />
      <package name="interceptions" extends="struts-default"
            namespace="/">
            <interceptors>
                  <interceptor name="mylogging"
                        class="com.test.lab.interceptor.LoggingInterceptor">
                  </interceptor>
                  <interceptor-stack name="loggingStack">
                        <interceptor-ref name="mylogging" />

                        <interceptor-ref name="defaultStack" />
                  </interceptor-stack>
            </interceptors>
      </package>
</struts>



In above xml we have mentioned few properties that will be used to bound our application to use struts2-convention-plugin and interceptors. We are just declaring our interceptors here that will be initialized at the time of application startup and will be executed whenever we will use it in our action class by using Annotations.

Now, let's move towards our first action class and how we are going to use annotation in that.

@ParentPackage("interceptions")
@InterceptorRefs({ 
    @InterceptorRef("mylogging"
})
public class LoginAction  implements ModelDriven{



Before getting into the depth of this class we must need to tell that what we need to put in @ParanetPackage() in @ParentPackage we need to use the same name that we have used in our struts.xml to represent the package in which our interceptor lies. Then finally use the name of interceptor in InterceptorRef.

We are not extending this class with ActionSupport becuase we have used annotations but implementing ModelDriven that will help use to get a bean entity to be used in our action class to delegate operations to Spring Layer.

UpUser user = new UpUser();
UserBo userBo;   
      public Object getModel(){
            return user;
      }
      public void setUserBo(UserBo userBo) {
            this.userBo= userBo;
      }


This is where we are getting the model by implementing the method of our interface ModelDriven.

Finally here our action comes which will be used for mapping of our action to JSPs

@Action(value="/login",results={@Result(name="success",location="/jsp/successPage.jsp"),
                  @Result(name="login",location="/jsp/userLogin.jsp")})
      public String execute() {
            if(user.getUserScreenName()==null)
                  return "login";
            System.out.println(userBo.verifyUser(user));
            return "success";
      }




Note: As we have used Struts2-Convention-Plugin so we must need to end our action class with  "Action" Because it will only compile all those classes which will have action and defined in action package.

From struts.xml:

<constant name="struts.convention.package.locators.basePackage"
            value="com.test.lab" />



Now, i'll not go into much detail of the classes because it's basic java which you must be aware of that how we can do transformation from one class to another and delegate a class to call the method.

UserBo which we have used in our Action is a BusinessObject which will act as a middle layer that will manage the transformation from Struts->Hibernate


package com.test.lab.bo;

import java.util.List;

import com.test.lab.model.UpUser;

public interface UserBo {
      Boolean verifyUser(UpUser user) ;
}




As UserBo is an interface so here it's an implementation for it where we actually will do Dependency Injection a feature offered by Spring. 


package com.test.lab.bo.impl;

import java.util.List;


import com.test.lab.bo.UserBo;
import com.test.lab.dao.UserDAO;
import com.test.lab.model.UpUser;

public class UserBoImpl implements UserBo{
      UserDAO userDAO;
      //DI
      public void setUserDAO(UserDAO userDAO){
            this.userDAO = userDAO;
      }
      public Boolean verifyUser(UpUser user) {
            // TODO Auto-generated method stub
            return userDAO.verifyUser(user);
           
      }

}



Now we are going to dig UserDAO what exactly it will do. As from the name it's quite clear that it will act as a data layer for us

package com.test.lab.dao;

import java.util.List;

import com.test.lab.model.UpUser;

public interface UserDAO {
     
       Boolean verifyUser(UpUser user);
}


Once again we have created an interface of UserDAO. So, we must need to provide an implementation:

package com.test.lab.dao.impl;

import java.util.List;

import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Restrictions;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import com.test.lab.dao.UserDAO;
import com.test.lab.model.UpUser;

public class UserDAOImpl extends HibernateDaoSupport
implements UserDAO{

     
      public Boolean verifyUser(UpUser user) {
            // TODO Auto-generated method stub
            Boolean result = false;
            DetachedCriteria criteria = DetachedCriteria.forClass(
                  UpUser.class);
          criteria.add(Restrictions.eq("userScreenName", user.getUserScreenName()));
          criteria.add(Restrictions.eq("userPassword", user.getUserPassword()));
         
             List<UpUser> list = getHibernateTemplate().findByCriteria(criteria);
             if(list.size()>0)
                   result = true;
             return result;
      }
}


Entity of UpUser:


package com.test.lab.model;

import java.util.Date;
import java.util.HashSet;
import java.util.Set;

/**
 * UpUser entity. @author MyEclipse Persistence Tools
 */

public class UpUser implements java.io.Serializable {

      // Fields

      private Integer userId;
     
      private String userScreenName;
      private String userPassword;
            // Constructors

      /** default constructor */
      public UpUser() {
      }

      /** minimal constructor */
      public UpUser(String userScreenName,String userPassword) {
            this.userScreenName = userScreenName;
            this.userPassword = userPassword;
            }

     
      // Property accessors

      public Integer getUserId() {
            return this.userId;
      }

      public void setUserId(Integer userId) {
            this.userId = userId;
      }
      public String getUserScreenName() {
            return this.userScreenName;
      }
      public void setUserScreenName(String userScreenName) {
            this.userScreenName = userScreenName;
      }
      public String getUserPassword() {
            return this.userPassword;
      }
      public void setUserPassword(String userPassword) {
            this.userPassword = userPassword;
      }
}


Hibernate mapping file that we need to keep in the same folder where our hibernate entity will be.

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!--
    Mapping file autogenerated by MyEclipse Persistence Tools
-->
<hibernate-mapping>
    <class name="com.test.lab.model.UpUser" table="up_user" catalog="test_db">
        <id name="userId" type="java.lang.Integer">
            <column name="user_id" />
            <generator class="identity" />
        </id>
    
        <property name="userScreenName" type="java.lang.String">
            <column name="user_screen_name" length="150" not-null="true" />
        </property>
       
        <property name="userPassword" type="java.lang.String">
            <column name="user_password" length="25" not-null="true" />
        </property>
            </class>
</hibernate-mapping>


Hibernate In the above file user_id, user_password and user_screen_name are the fields that we have in our database and now mapping here with the userId, userPassword and userScreenName in our UpUser.java file.

Here we are going to discuss main Spring file that will manage almost all the interaction between UI and Data layer.

This file need to be placed at: resources/com/test/lab/spring/UpUserBean.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<bean id="loginAction" class="com.test.lab.actions.LoginAction">
<property name="userBo" ref="userBo" />  
</bean>

<bean id="userBo" class="com.test.lab.bo.impl.UserBoImpl" >
<property name="userDAO" ref="userDAO" />
</bean>

<bean id="userDAO" class="com.test.lab.dao.impl.UserDAOImpl" >
<property name="sessionFactory" ref="sessionFactory" />
</bean>

</beans>


Few more configuration:

resources/config/database/properties/database.properties
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/legal_erp
jdbc.username=root
jdbc.password=root



resource/config/spring/DataSource.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

 <bean
   class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
   <property name="location">
     <value>WEB-INF/classes/resources/config/database/properties/database.properties</value>
   </property>
</bean>

  <bean id="dataSource"
         class="org.springframework.jdbc.datasource.DriverManagerDataSource">
      <property name="driverClassName" value="${jdbc.driverClassName}" />
      <property name="url" value="${jdbc.url}" />
      <property name="username" value="${jdbc.username}" />
      <property name="password" value="${jdbc.password}" />
  </bean>

</beans>


 resources/config/spring/HibernateSessionFactory.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<!-- Hibernate session factory -->
      <bean id="sessionFactory"
      class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
       
            <property name="dataSource">
                  <ref bean="dataSource"/>
            </property>
       
            <property name="hibernateProperties">
                  <props>
                        <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
                        <prop key="hibernate.show_sql">true</prop>
                        <prop key="hibernate.connection.zeroDateTimeBehavior">convertToNull
                        </prop>
                  </props>
            </property>
       
            <property name="mappingResources">
                  <list>
                 
<value>com/test/lab/model/UpUser.hbm.xml</value>
                  </list>
            </property>
     
      </bean>
</beans>

  resources/config/SpringBeans.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<!-- Database Configuration -->
<import resource="spring/DataSource.xml"/>
<import resource="spring/HibernateSessionFactory.xml"/>

<!-- Beans Declaration -->
<import resource="../com/test/lab/spring/UpUserBean.xml"/>

</beans>


 Now wrapping up everything with an AuthenticationFilter and how to write a custom filter that we have just mentioned in our web.xml

AuthenticationFilter.java

package com.test.lab.filter;

import java.io.IOException;
import java.util.HashSet;
import java.util.StringTokenizer;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;


public class AuthenticationFilter implements Filter
{
    private static Logger logger = Logger.getLogger( AuthenticationFilter.class );
    private HashSet<String> insecurePages = null;

    public void init( FilterConfig config ) throws ServletException
    {
      System.out.println( "PUBLIC Authentication Fileter Started." );
         logger.info( "PUBLIC Authentication Fileter Started." );
        String strInsecurePages = config.getInitParameter( "insecurePages" );
        StringTokenizer tokenizer = new StringTokenizer( strInsecurePages, ",", false );
        insecurePages = new HashSet<String>( tokenizer.countTokens() );

        while ( tokenizer.hasMoreTokens() )
        {
            insecurePages.add( tokenizer.nextToken().trim() );
        }
        logger.info( "PUBLIC Authentication Fileter Started." );
    }

    public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain )
            throws IOException, ServletException
    {
        if ( request instanceof HttpServletRequest )
        {
            System.out.println("Request Servlet Request");
            HttpServletRequest req = (HttpServletRequest) request;
            HttpServletResponse res = (HttpServletResponse) response;
           
            logger.info( "Request received: " + req.getRequestURI()
                         + " from user with session id: " + req.getRequestedSessionId() );
           
        }

        chain.doFilter( request, response );
    }

    public void destroy()
    {
        insecurePages = null;
    }

}

 And our basic Interceptor that we have just to calculate the time of our action request.
LoggingInterceptor.java

package com.test.lab.interceptor;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.Interceptor;

public class LoggingInterceptor implements Interceptor{

      private static final long serialVersionUID = 1L;

      public String intercept(ActionInvocation invocation) throws Exception {

            String className = invocation.getAction().getClass().getName();
            long startTime = System.currentTimeMillis();
            System.out.println("Before calling action: " + className);

            String result = invocation.invoke();

            long endTime = System.currentTimeMillis();
            System.out.println("After calling action: " + className
                        + " Time taken: " + (endTime - startTime) + " ms");

            return result;
      }

      public void destroy() {
            System.out.println("Destroying MyLoggingInterceptor...");
      }
      public void init() {
            System.out.println("Initializing MyLoggingInterceptor...");
      }
}


LoginAction.java

This is just a dumb action so will not perform a lot of things for you but, it will give you an idea of using it

package com.test.lab.actions;

import java.util.ArrayList;
import java.util.List;

import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.InterceptorRef;
import org.apache.struts2.convention.annotation.InterceptorRefs;
import org.apache.struts2.convention.annotation.ParentPackage;
import org.apache.struts2.convention.annotation.Result;

import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;
import com.test.lab.bo.UserBo;
import com.test.lab.model.UpUser;
@ParentPackage("interceptions")
@InterceptorRefs({ 
    @InterceptorRef("mylogging") 
})
public class LoginAction  implements ModelDriven{
      UpUser user = new UpUser();
      UserBo userBo;
      @Action(value="/login",results={@Result(name="success",location="/jsp/successPage.jsp"),
                  @Result(name="login",location="/jsp/userLogin.jsp")})
      public String execute() {
            if(user.getUserScreenName()==null)
                  return "login";
            System.out.println(userBo.verifyUser(user));
            return "success";
      }
     
      public Object getModel(){
            return user;
      }
      public void setUserBo(UserBo userBo) {
            this.userBo= userBo;
      }
     
      public UpUser getUser() {
            return user;
      }

      public void setUser(UpUser user) {
            this.user = user;
      }
     
}


resources/config/log4j.xml

 So, this is it related to the whole configuration and coding. 

I have intentionally didn't add any jsp file in this code because I think JSP can be simply added by anyone by looking into a lot of example available so don't want to waste my time into that but if, anyone have any issues  in implementing this then they may contact me.

2 comments: