Spring 2.5+JPA 2.0+Strut 1.3 Web application development
There are lots of web application frameworks in the Java world. Still Struts is the de facto although Struts is the oldest. Struts only for the presentation. As usual, if there is Spring involvement, there are lots of benefits, but configuration is very difficult. In this blog introduce the way the following technologies are configured to develop web applications.
- Struts 1.3.2
- JPA 2.0 (EclipseLink 2.0)
- Spring 2.5
- Tiles 1.3
These technologies for different purposes and avoided the overlaps. For example, Struts for presentation layer, JPA as a model of presentation and Plain Old Java Objects (POJOs) will be as business objects.
Productivity is another concern. In this blog, I will show you how to create web application using above technologies and develop with Eclipse Galileo.
Let’s consider the typical web application architecture. As shown in the following picture, Struts plays the MVC framework role, in the middle Business services and last, the Data Access Objects(DAOs). However, to avoid the problem of mismatch of two paradigms object oriented and relational, used JPA as a solution.
Fig 1: Application Architecture based on Spring
In this blog, Eclipselink 2.0 has been used as a JPA 2.0 implementation. But there are number of other object relational mapping (ORM) solutions such as Hibernate. JPA needs database to persist objects as mapped tables, therefore Apache Derby has been used in this blog. But if you are willing to use another database, no restrictions.
Why Spring ?
Why use the Spring framework is the only remaining question. There are number of advantages of using Spring framework(see [1] in the reference). Following are the factors, drove to use the Spring Framework,
- Spring take care of concurrency handling declaratively. Therefore, only up to the Action class concurrency need to be programmed. For Action classes, always keep all the variables as local variables without any instance variables.
- Spring provide declarative transactions. However, in this blog consider only the Local transactions but Spring is capable of handling JTA transactions also.
- Use of non-invasive POJOs for Business Services.
However, instead of Spring Framework, someone can suggests a Java EE application Server also. To keep simplicity, Tomcat 6.0 has been used as a Java EE container which don’t have EJB container.
ScreenCast 1: Create Struts based Application
Let’s see how to configure Tomcat for Spring use. Although it is simple, in this project, Aspect Oriented Programming (AOP) features will be used. According to the Spring official documentation[2 & 3], the LoadtimeWeaver interface is a Spring class that allows JPA ClassTransformer instances to be plugged in a specific manner depending on the environment. Follow the following steps
- copy the spring-tomcat-weaver.jar to $CATALINA_HOME$/lib.
- Instruct Tomcat to use the customer ClassLoader in the context.xml which should be created in /WebContent/META-INF folder.
In the following code, which has JDBC connection in addition.
<?xml version="1.0" encoding="UTF-8"?> <Context> <Resource name="jdbc/catalogueDB" auth="Container" type="javax.sql.DataSource" username="app" password="app" driverClassName="org.apache.derby.jdbc.ClientDriver" url="jdbc:derby://localhost:1527/catalogueDB;create=true" maxActive="8" maxIdle="4"/> <Loader loaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader"/> </Context>
Source 1: Source of the context.xml for Tomcat 6.0
JDBC configuration is not a big deal because it is basics about Tomcat. As you know, this connection should be added to the JNDI tree via web.xml file as shown in the following code.
.... jdbc/catalogueDB javax.sql.DataSource Container
Source 2: JNDI configuration for datasoruce
The JNDI jdbc/catalogueDB should be added to the Spring web configuration file that is applicationContext.xml file in this project. Later you will see how to configure this file to be Spring configuration file.
To add the above JNDI name, Spring provides the jee:jndi-lookup tag at line 37 as shown in the following source of applicationContext.xml file.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"> <tx:annotation-driven/> <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/> <context:annotation-config/> <bean id="jpaAdapter" class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter"> <property name="databasePlatform" value="org.eclipse.persistence.platform.database.DerbyPlatform" /> <property name="showSql" value="true" /> <property name="generateDdl" value="true"/> </bean> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory" /> <property name="dataSource" ref="dataSource"/> </bean> <context:load-time-weaver/> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="jpaVendorAdapter" ref="jpaAdapter"/> <property name="persistenceUnitName" value="CatalogueSystem"/> <property name="persistenceUnitManager" ref="PUnitMgr"/> </bean> <jee:jndi-lookup id="dataSource" jndi-name="jdbc/catalogueDB"/> <bean id="PUnitMgr" class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager"> <property name="persistenceXmlLocations"> <list> <value>/WEB-INF/classes/META-INF/persistence.xml</value> </list> </property> <property name="defaultDataSource" ref="dataSource"/> </bean> <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/> <bean id="jpaUserDao" class="com.abccompany.dao.JpaUserDao"/> <bean name="/userManagement" class="com.abccompany.action.UserAddAction"/> <bean id="userService" class="com.abccompany.service.UserService"/> </beans>
Soruce 3: Source of the applicationContext.xml
In addition to that, to use Spring annotation driven transaction, the <tx:annotation-driven/>(line 13) has been added. Later you will find the @Transactional (see [4]) annotation in the DAO source code. For that annotation driven configuration is allowed at line 15. Back to the main discussion, the <
context:load-time-weaver
/>
(line 28) has been configured in conjunction with the TomcatInstrumentableClassLoader in the context.xml file.
The persistence.xml file has been configured at line 42 because in this application JPA is the chosen persistence mechanism.
Spring & JPA configuration
As mentioned, EclipseLink is the JPA implementation to be used in this application. Standard Spring configuration for JPA has been followed to configure EclipseLink with Spring. The EclipseLink JPA adapter has been configured at line 17 in the above applicationContext.xml file. Under the persistence unit manager, the location of the persistence.xml has been provided at line 42. The most important Entity Manager has been configured at line 29, where data source, persistence unit manager and the persistence unit has been injected. This configuration allowed to use @PersistenceContext in the DAO to reference entity manager.
Getting started with application
Application is simplified to make it understandable. As first step, the common requirement of “register the users” functionality has been considered.
Fig 2: User Management navigation panel
When you click on “User” link in left navigation panel, the above “User Management” screen will be available. To add the new user click “Add”, then you will end up with the following page.
Fig 3: User Registration form
As shown in the above screen shot, User Name is required. Password should be conform to ^[a-zA-Z0-9_[\S]+]*$ regular expression (see [6]) which is any character without whitespace characters, but include underscore. This validation has been given in the validation.xml file in the struts configuration.
<formset> <form name="userAddForm"> <field property="user.userName" depends="required"> <arg key="user.add.userName" /> </field> <field property="user.password" depends="mask"> <arg key="user.add.password" /> <var> <var-name>mask</var-name> <var-value>^[_0-9a-zA-Z[\S]+]*$</var-value> </var> </field> </form> </formset>
Source 4: Validation for userAddForm in the validation.xml file
As shown in the line 2, the form name “userAddForm” has been configured in the struts-config.xml file as shown in the following code segment. It is necessary to emphasis that the form-bean is type of DynaValidatorForm instead of DynaActionForm [5].
<form-bean name="userAddForm" type="org.apache.struts.validator.DynaValidatorForm"> <form-property name="user" type="com.abccompany.domain.SystemUser"/> <form-property name="password" type="java.lang.String"/> </form-bean> </form-beans>
Source 5: Form-bean configuration of userAddForm in struts-config.xml
As shown in the above code segment, the form is associated form object “SystemUser” which is also used as a domain object too. That’s mean, form bean can be persist because it also play the role of domain object as shown in the following code.
package com.abccompany.domain; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.lang.String; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import javax.persistence.*; import sun.misc.BASE64Encoder; import com.sun.org.apache.xml.internal.security.algorithms.MessageDigestAlgorithm; /** * Entity implementation class for Entity: SystemUser * */ @Entity public class SystemUser implements Serializable { @Id private String userName; private String name; @Transient private String password; private String email; private String phno; private String encryptedPassword; public String getEncryptedPassword() { return encryptedPassword; } public void setEncryptedPassword(String encryptedPassword) { try { this.encryptedPassword = encrypt(encryptedPassword); } catch (NoSuchAlgorithmException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public String getPhno() { return phno; } public void setPhno(String phno) { this.phno = phno; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } private static final long serialVersionUID = 1L; public SystemUser() { super(); } public String getUserName() { return this.userName; } public void setUserName(String userName) { this.userName = userName; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } public String getPassword() { return this.password; } public void setPassword(String password) { this.password = password; setEncryptedPassword(password); } private String encrypt(String password) throws NoSuchAlgorithmException, UnsupportedEncodingException { MessageDigest md; // use SHA-1 algorithm md = MessageDigest.getInstance("SHA"); // convert palin text to byte array using UTF-8 encoding md.update(password.getBytes("UTF-8")); // create string representation to store in database byte[] raw = md.digest(); // and return return (new BASE64Encoder()).encode(raw); } public boolean isCompatible(String password) { return password.equals(this.password); } }
Source 6: Source of the SystemUser.java
As shown in the above code, password is transient means not suppose to persist. Instead, there is a encryptedPassword which is supposed to save because password is encrypted using one-way encryption algorithm which is implemented in the encrypt() method in the above java code. For the secure communication between client browser and server, JSP form submit method should be a type of “POST” request as shown in the following code.
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %> <%@ taglib uri="http://struts.apache.org/tags-html" prefix="html"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html:html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Insert title here</title> <html:base/> </head> <body> <html:form action="userManagement" method="post"> <html:messages id="messages" message="true"> <center><bean:write name="messages"/></center> </html:messages> <table width="50%" border="1" class="signup" align="center"> <tr> <td colspan="2" align="center"> <font size="4" color="#660099">Please Enter the Following Details</font><br> </td> </tr> <tr><td colspan="2" align="center"><font color="red"><html:errors/></font> </td></tr> <tr> <td align="right" width="50%"><b>User Name<font color="#FF0000">*</font></b></td> <td align="left" width="50%"> <html:text property="user.userName" size="30" maxlength="120"/> </td> </tr> <tr> <td align="right"><b>Password</b></td> <td align="left"> <html:password property="user.password" size="30" maxlength="120"/> </td> </tr> <tr> <td align="right"><b>Con. Password</b></td> <td align="left"> <html:password property="password" size="30" maxlength="120"/> </td> </tr> <tr> <td align="right"><b>Name</b></td> <td align="left"> <html:text property="user.name" size="30" maxlength="120"/> </td> </tr> <tr> <td align="right"><b>Email</b></td> <td align="left"> <html:text property="user.email" size="30" maxlength="120"/> </td> </tr> <tr> <td align="right"><b>Phone No.</b></td> <td align="left"> <html:text property="user.phno" size="30" maxlength="120"/> </td> </tr> <tr><td colspan="2"> </td></tr> <tr> <td align="center" colspan="2"> <html:submit property="method">add</html:submit> </td> </tr> </table> </html:form> </body> </html:html>
Source 7: Source of the userRegistration.jsp
In the above userRegistation.jps, the <html:submit> at line 71 is the one of the important thing to highlight because the DispatchAction has been used instead of ActionForm( see [5]). Therefore, in the action class, that is UserAddAction.java should have a method to execute in the name of “add” as shown in the following code;
package com.abccompany.action; import java.sql.SQLIntegrityConstraintViolationException; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import org.apache.struts.action.ActionMessage; import org.apache.struts.action.ActionMessages; import org.apache.struts.action.DynaActionForm; import org.apache.struts.actions.DispatchAction; import org.apache.struts.util.MessageResources; import org.springframework.transaction.TransactionSystemException; import com.abccompany.domain.SystemUser; import com.abccompany.service.UserService; public class UserAddAction extends DispatchAction { @Resource(name="userService") UserService userService; MessageResources mr; public ActionForward add(ActionMapping mappings, ActionForm form, HttpServletRequest request, HttpServletResponse response){ ActionMessages messages = new ActionMessages(); try{ DynaActionForm dyna = (DynaActionForm)form; SystemUser user = (SystemUser)dyna.get("user"); String pass = (String)dyna.get("password"); if (!user.isCompatible(pass)){ throw new Exception("user.add.passwordIncompatible"); } System.out.println("password: " + user.getPassword()); userService.add(user); messages.add("message", new ActionMessage("user.add.success")); }catch(TransactionSystemException te){ if (te.contains(SQLIntegrityConstraintViolationException.class)){ messages.add("message", new ActionMessage("exception.userAlreadyExist")); } }catch(Exception e){ e.printStackTrace(); messages.add("error", new ActionMessage(e.getMessage())); } saveMessages(request, messages); return mappings.findForward("success"); } }
Source 8: Source of the UserAddAction.java
There are few thing to note here. First, DynaActionForm has been used instead of DynaValidatorForm, because DynaValicatorForm is a subclass of the DynaActionForm. The UserService is the business service object to manipulate users. UserService, which is shared by the almost all the services, has been injected via Spring. The main advantage of use of Spring is that, Spring is capable of handling concurrency issues for injected service as shown in line 24. Due to the concurrency issues, everything else except Spring injected service, be kept as local variable in the method itself. The name of the service represent the bean id of the configured service in the applicationContext.xml file.
Now we are ready to configure the struts action in the struts-config.xml file as shown in the following code segment.
<!-- System User --> <action path="/systemUser" forward="user.management"/> <action path="/userAddView" forward="user.add.view"/> <action path="/userManagement" parameter="method" type="com.abccompany.action.UserAddAction" name="userAddForm" scope="request" validate="true" input="user.add.view"> <forward name="success" path="user.add.view"/> </action> </action-mappings>
Source 9: Source of the action configuration in the struts-config.xml
Before discuss the business service, I suppose to explain Tiles configuration if you are not familiar. The Struts Tiles implementation available in the Strtus-Tiles.jar file. View definitions are going in the tiles-defs.xml file as shown in the following code. For example, in the above action configuration, “user.add.view” is a tiles definition.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 1.3//EN" "http://jakarta.apache.org/struts/dtds/tiles-config_1_3.dtd"> <tiles-definitions> <definition name="site.baseLayout" template="/pages/layouts/classicLayout.jsp"> <put name="title" value="Page Title"/> <put name="header" value="/pages/common/header.jsp" /> <put name="footer" value="/pages/common/footer.jsp" /> <put name="menu" value="/pages/common/menu.jsp" /> <put name="body" value="/pages/common/welcome.jsp" /> </definition> <definition name="vedio.AddSuccess" extends="site.baseLayout"> <put name="title" value="Add Video"/> <put name="body" value="/pages/video/addVideo.jsp"/> </definition> <definition name="video.main" extends="site.baseLayout"> <put name="title" value="Video Menu"/> <put name="body" value="/pages/video/addVideo.jsp"/> </definition> <definition name="user.management" extends="site.baseLayout"> <put name="title" value="Category Management"/> <put name="body" value="/pages/user/userManagement.jsp"/> </definition> <definition name="user.add.view" extends="site.baseLayout"> <put name="title" value="Add/Edit User"/> <put name="body" value="/pages/user/userRegistration.jsp"/> </definition> </tiles-definitions>
Source 10: Source of the tiles-defs.xml file.
As shown in the above code, “site.baseLayout” define the base layout. As shown in the above Fig. 2 and 3, each page had header, footer, menu and the body which is define in line 07 to 13 in the Source 10. Back to our example, “user.add.view” is based on the “site.baseLayout” but the body has been replaced by the userRegistration.jsp (see Source 7).
<plug-in className="org.apache.struts.tiles.TilesPlugin" > <set-property property="definitions-config" value="/WEB-INF/tiles-defs.xml" /> <set-property property="moduleAware" value="true" /> <set-property property="definitions-parser-validate" value="true"/> </plug-in>
Source 11: Source of the Tiles configuration in struts-config.xml
It is necessary to add the struts plugin to sturts-config.xml. The location of the tiles-defs.xml has to be given.
Let’s back to the business service, which is suppose to be model of the MVC pattern. Due to the Spring injection, no need of business delegate. Business Service access the DAO for basic persistence operations as shown in the following code.
package com.abccompany.service; import javax.annotation.Resource; import com.abccompany.dao.UserDao; import com.abccompany.domain.SystemUser; /** * User service to manipulate the user * @author Ojitha * */ public class UserService { @Resource(name = "jpaUserDao") UserDao userDao; public void add(SystemUser user) { userDao.save(user); } }
Source 12: UserService.java is a business Service
As shown in the Source 12, the user “add” service is the only service available, but usually one more should be. However, UserDao is the interface, the implementation is inject via Spring. The injected implementation is referred to as “jpaUserDao” (line 14) which is the bean id of the real implementation configuration in the applicationContext.xml file (see ling 52 in the Source 3 above).
Let’s consider the Dao which is in the JPA implementation. The class JpaUserDao is the JPA implementation. Anyway, if you are suppose to change the implementation to other such as JDBC or JDO, then you just need to change only the applicationContext.xml file without a need of recompilation.
package com.abccompany.dao; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import com.abccompany.domain.SystemUser; @Repository public class JpaUserDao implements UserDao { // persistence context injected by the Spring @PersistenceContext EntityManager em; @Transactional @Override public void save(SystemUser user) { em.persist(user); } }
Source 13: Source of the JpaUserDao.java Dao
As we mentioned early, EntityManager is injected and transactions are delegated to handled by the Spring framework. In addition to that the annotation @Repository has been used to get the generalized persistency exceptions. It is necessary to configure the PersistenceExceptionTranslationPostProcessor
in the applicationContext.xml (line 48 in the Source 3) file. For example, database integrity violation such as duplicate key raised the implementation dependent exception. Spring will map this proprietary exception to the spring generic exception as shown in the lines 46-50 in the Source 8. The SystemUser is the domain object (see Source 6), which is to be persisted by the EntityManager. Therefore, SystemUser should be a Entity defined in the persistence.xml file.
<?xml version="1.0" encoding="UTF-8"?> <persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"> <persistence-unit name="CatalogueSystem" transaction-type="RESOURCE_LOCAL"> <class>com.abccompany.domain.SystemUser</class> </persistence-unit> </persistence>
This persistence supported by the Spring is the level of local transaction, but there is possibility to configure for JTA transaction also. Another important thing is the persistence unit which should be the same persistence unit given in the EntityManager in the applciationContext.xml (see line 33 in the Source 3). Now you are ready to run the application.
Spring & Struts configuration
Before run the application you have to configure Strut with Spring. It is simple as in the following code segment of the struts-config.xml;
.............. </action-mappings> <!-- configure spring --> <controller processorClass="org.springframework.web.struts.DelegatingTilesRequestProcessor"/> ........................... ............................ <!-- configure spring --> <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn"> <set-property property="contextConfigLocation" value="/WEB-INF/applicationContext.xml" /> </plug-in> </struts-config>
As shown in the above code segment DelegatingTilesRequestProcessor is the necessary processorClass for Tiles configuration (see [7]) right after the </action-mappings>. In addition to that, configure the Struts Spring plugin which is standard.
In addition to that the necessary libraries are shown here.
Fig 4: Necessary Jars and configuration files
Above Fig 4 shows where your configuration files and what are the jar files necessary. However, persistence.xml is available under src/META-INF folder. Whatever available in this folder will be transferred to the WEB-INF/classess folder.
Run the application
you can run the application as shown in the following figure.
You can DOWNLOAD the source.
References
[1] | Introduction Spring Framework, Rod Jonson |
[2] | Aspect Oriented Programming with Spring, SpringSource |
[3] | Object Relational Mapping (ORM) data access, SpringSource |
[4] | Spring Transaction management, SpringSource |
[5] | Programming Jakarta Struts, Cbuck Cavaness. |
[6] | Regular Expressions Cookbook, Jan Goyvaerts & Steven levithan |
[7] | Spring in Action, 2nd Edition, Craig Walls. |
Comments
Post a Comment
commented your blog