Monday, March 1, 2010

EYE on JAVA EE: Spring’s Practical Example for Enterprise Application

Clean Version

EYE on JAVA EE: Spring’s Practical Example for Enterprise Application


Spring, as a lightweight Java EE framework, offers a number of unique advantages, provides a one-stop-shop solution for all your programming needs from prototyping to full-scale production. The DI (Dependency Injection) feature significantly reduces the coupling among programming components, and promotes coding to interface concept in practice. POJO avoid all cumbersome platform dependent imports, greatly improves the testability. MVC offers clean separation among different roles, provide customizable binding and validation, while maintain the pluggability of other platform (like Struts). AOP makes declarative transaction management possible. Spring support of RMI, and JMS etc. makes distributed computing straightforward. All in all, Spring offers a complete support for most enterprise application development.


In this article, I will focus on a handful of most commonly used Spring features by developing a small electronic store application. It shows how different Spring components work and how easy to put them all work together.


First let’s go though the store specification. Since it is for demonstration purpose, the specification has been stripped down to the bare minimum.


The store sells exactly nine computer parts include computer, monitor, and SD cards. For each part, there is a threshold number, indicates the price adjustment if the order quantity is above this number. For example, for a computer – desktop PC, the regular price is $349. But when ordering more than five, the sixth one will be priced as $299. Multiple computer parts can be combined into one order.


In addition, the store offers two reports: one is how much total is sold within a certain period of time. The second one is how much total is sold for one particular computer part within a certain period of time.


The class diagram for the Order, OrderItem, Product, Report and Report Instance:



The UI for Add Product in Order Cart:



Make Order:



Request Report:



Browse Completed Reports:



Simple and Straightforward? Let’s start the fun of real coding.


Before we put in the first line, we need to mention a little about the environment. I set the project structure as following. Most are self-explaining while the web part followings Java EE standard. One thing need to be pointed out is that most of the configuration files are saved under config folder while the folder itself is added in the classpath.



Currently all Java EE platform claims to be full Object Oriented but when put in practice there is always debate of the pure business model vs. platform specific implementation. And comprise has to be made in between. Spring’s approach of POJO returns back to basic, makes it possible to model business in a complete Object Oriented way without concerns of platform limitation.


Five entity class created, exactly follow the class diagram.


Order.java Ignore the annotation for now.



package store.entity;

import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import store.web.Cart;

@Entity
@Table(name="ent_order")
public class Order implements Serializable {
@Id
@Column(name="id")
@GeneratedValue(strategy=GenerationType.AUTO)
private int id;
@Column(name="order_num", length=20)
private String orderNum;
@Column(name="purchase_date")
private Calendar purchaseDate;
@Column(name="total_price")
private double totalPrice;
@OneToMany(targetEntity=OrderItem.class, cascade={CascadeType.ALL}, mappedBy="order")
private List items = new ArrayList();

public Order() {}

static int counter=0;

public static String getUniqueOrderNum(Calendar c) {
counter++;
return "CMPT-"+(new SimpleDateFormat("yyyyMMdd")).format( c.getTime() )+"-"+counter;
}
public void addOrderItem(OrderItem item) {
items.add( item );
}
public int getId() {
return id;
}
public void setId( int id ) {
this.id = id;
}
public String getOrderNum() {
return orderNum;
}
public void setOrderNum( String orderNum ) {
this.orderNum = orderNum;
}
public Calendar getPurchaseDate() {
return purchaseDate;
}
public void setPurchaseDate( Calendar purchaseDate ) {
this.purchaseDate = purchaseDate;
}
public double getTotalPrice() {
return totalPrice;
}
public void setTotalPrice( double totalPrice ) {
for (OrderItem item : items) {
this.totalPrice += item.getItemPrice();
}
}
public List getItems() {
return items;
}
public void setItems( List items ) {
this.items = items;
}
}


Now it is time for testing. I am a strong believer of unit test, though have some doubt against TDD, for TDD in practice some times be put ahead of modeling. Anyway, by taking advantage of Annotation driven JUnit test suit, junit-4.4, testing is straightforward. OrderTest.java indicates a typical unit test class.


OrderTest.java


package store.entity;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

import java.util.Calendar;

import org.junit.Test;

import store.entity.Order;


public class OrderTest {
static final String valueStr = "This is A String";
static final int valueInt = 111;
static final double valueDouble = 111.111;
static final Calendar valueCalendar = Calendar.getInstance();

Order order = new Order();

@Test
public void testIdGetterSetter() {
assertEquals(0, order.getId());
order.setId( valueInt );
assertEquals(valueInt, order.getId());
}

@Test
public void testOrderNumGetterSetter() {
assertNull(order.getOrderNum());
order.setOrderNum( valueStr );
assertEquals(valueStr, order.getOrderNum());
}

@Test
public void testPurchaseDateGetterSetter() {
assertNull(order.getPurchaseDate());
order.setPurchaseDate( valueCalendar );
assertEquals(valueCalendar, order.getPurchaseDate());
}

@Test
public void testTotalPriceGetterSetter() {
assertEquals(0.0, order.getTotalPrice(), 0.000001);
order.setTotalPrice( valueDouble );
assertEquals(valueDouble, order.getTotalPrice(), 0.000001);
}
}


Notice the similarity of unit test class among most entity classes, it is a good time to refactor this similarity into a more robust test class. EntityTest.java is the result. Retest all unit test.


EntityTest.java


package store.entity;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.regex.Pattern;

import org.junit.Test;

public abstract class EntityTest {
static final String GETTER = "get[A-Z][a-zA-Z_0-9]*";
static final String SETTER = "set[A-Z][a-zA-Z_0-9]*";
static final String valueStr = "This is A String";
static final int valueInt = 111;
static final long valueLong = Integer.MAX_VALUE + 111;
static final double valueDouble = 111.111;
static final Calendar valueCalendar = Calendar.getInstance();
static final Object valueObject = new Object();

Object object;

@Test
public void getterSetter() {
//find all getter/setter pair
Class orderClass = object.getClass();
Method[] methods = orderClass.getMethods();
List<Method> getters = new ArrayList();
List<Method> setters = new ArrayList();
for (int i=0; i<methods.length; i++) {
String getterSetter = methods[i].getName();
if (Pattern.matches(GETTER, getterSetter)) {
String setter = "set" + getterSetter.substring( 3 );
for (int j=i; j<methods.length; j++) {
if (setter.equals( methods[j].getName() )) {
getters.add( methods[i] );
setters.add( methods[j] );
}
}
}
else if (Pattern.matches(SETTER, getterSetter)) {
String getter = "get" + getterSetter.substring( 3 );
for (int j=i; j<methods.length; j++) {
if (getter.equals( methods[j].getName() )) {
getters.add( methods[j] );
setters.add( methods[i] );
}
}
}
}

for (int i=0; i<getters.size(); i++) {
String returnTypeName = getters.get( i ).getReturnType().getSimpleName();
testGetterSetter(returnTypeName, getters.get( i ), setters.get( i ));
}
}

public void testGetterSetter(String returnTypeName, Method getter, Method setter) {
String testMethod = "test" + returnTypeName + "GetterSetter";
try {
Method method = this.getClass().getMethod( testMethod, new Class[] {Method.class, Method.class} );
method.invoke( this, (Object [])(new Method[] {getter, setter}) );
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public void testStringGetterSetter(Method getter, Method setter ) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
assertNull(getter.invoke( object, (Object [])null ));
setter.invoke( object, new String[] {valueStr} );
assertEquals(valueStr, getter.invoke( object, (Object [])null ));
}

public void testintGetterSetter(Method getter, Method setter ) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
testIntegerGetterSetter(getter, setter);
}

public void testIntegerGetterSetter(Method getter, Method setter ) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
assertEquals(0, getter.invoke( object, (Object [])null ));
setter.invoke( object, new Integer[] {new Integer(valueInt)} );
assertEquals(valueInt, getter.invoke( object, (Object [])null ));
}

public void testlongGetterSetter(Method getter, Method setter ) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
testLongGetterSetter(getter, setter);
}

public void testLongGetterSetter(Method getter, Method setter ) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
assertEquals(0, getter.invoke( object, (Object [])null ));
setter.invoke( object, new Long[] {new Long(valueLong)} );
assertEquals(valueLong, getter.invoke( object, (Object [])null ));
}

public void testdoubleGetterSetter(Method getter, Method setter ) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
testDoubleGetterSetter(getter, setter);
}

public void testDoubleGetterSetter(Method getter, Method setter ) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
assertEquals(0.0, getter.invoke( object, (Object [])null ));
setter.invoke( object, new Double[] {new Double(valueDouble)} );
assertEquals(valueDouble, getter.invoke( object, (Object [])null ));
}

public void testCalendarGetterSetter(Method getter, Method setter ) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
assertNull(getter.invoke( object, (Object [])null ));
setter.invoke( object, new Calendar[] {valueCalendar} );
assertEquals(valueCalendar, getter.invoke( object, (Object [])null ));
}

public void testObjectGetterSetter(Method getter, Method setter ) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
assertNull(getter.invoke( object, (Object [])null ));
setter.invoke( object, new Object[] {valueObject} );
assertEquals(valueObject, getter.invoke( object, (Object [])null ));
}
}


The above process represents a mini-serious of develop-test-refactor cycle, and it will be followed in the subsequent development.


Once we have the basic entities ready, it is time to think about persist them into database. Among all five entity classes, Report.java is different cause it is defined all two Report in static instances. The rest four will need to map to table. In this demonstration, I am going to apply Hibernate as the ORM tool for its popularity and reliability. HSQLDB is used as an in-memory database support. Start HSQLDB in another window/cmd prompt, create necessary table. Create hibernate configuration file to properly match the data source while takes advantage of the latest hibernate annotation.


hibernate.cfg.xml Now all the annotation tags in Order.java start to make sense.


<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<<hibernate-configuration> <session-factory> <property name="connection.url">jdbc:hsqldb:hsql://localhost:9001</property> <property name="connection.username">sa</property> <property name="connection.driver_class">org.hsqldb.jdbcDriver</property> <property name="dialect">org.hibernate.dialect.HSQLDialect</property> <property name="connection.password"></property> <property name="transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory</property> <!-- thread is the short name for org.hibernate.context.ThreadLocalSessionContext and let Hibernate bind the session automatically to the thread -->
<property name="current_session_context_class">thread</property>
<!-- this will show us all sql statements -->
<property name="hibernate.show_sql">true</property>
<!-- mapping files -->
<mapping class="store.entity.Product" />
<mapping class="store.entity.Order" />
<mapping class="store.entity.OrderItem" />
<mapping class="store.report.ReportInstance" />
</session-factory>
</hibernate-configuration>


As a common Java EE approach, Data Access Object (DAO) is created to bridge the calls. Notice all DAO calls do not explicitly handle transaction. It is expected that the caller will handle it.


HibernateOrderDAO.java


package store.manager;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;

import store.entity.Order;

//DAO leave transaction to the caller.
public class HibernateOrderDAO {
@Autowired
private SessionFactory sessionFactory;

public Order getOrder( int orderId ) {
Session session = sessionFactory.getCurrentSession();
return (Order)session.get( Order.class, orderId );
}

public Order saveOrder( Order order ) {
Session session = sessionFactory.getCurrentSession();
session.saveOrUpdate( order );
return order;
}

public void deleteOrder(Order order) {
Session session = sessionFactory.getCurrentSession();
//session.merge( order );
session.delete( order );
}

public void setSessionFactory( SessionFactory sessionFactory ) {
this.sessionFactory = sessionFactory;
}
}


Now it is testing time again. Testing hibernate DAO will be slightly tricky mainly for test class has to instantiate Hibernate SessionFactory programmatically.


HibernateOrderDAOTest.java


package store.manager;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import org.hibernate.FlushMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import store.entity.Order;
import store.entity.OrderItem;
import store.entity.Product;


public class HibernateOrderDAOTest {

private SessionFactory sessionFactory;
private HibernateOrderDAO orderDAO = new HibernateOrderDAO();

@Before
public void initialze() {
try {
sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
orderDAO.setSessionFactory( sessionFactory );
} catch ( Throwable ex ) {
ex.printStackTrace();
throw new ExceptionInInitializerError( ex );
}
}

@After
public void finalize() {
Session session = sessionFactory.getCurrentSession();
session.getTransaction().commit();
}

@Test
public void orderCRUD() {
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
session.setFlushMode( FlushMode.ALWAYS );
//get Order
Order o = orderDAO.getOrder( 1 );
assertNotNull(o);
assertEquals("CMPT-20100222-001", o.getOrderNum());
OrderItem item = o.getItems().get( 0 );
assertEquals(item.getItemPrice(), 349, 0.00001);
Product p = item.getProduct();
assertEquals(p.getName(), "computer");

}
}


Up to now, I have not touched Spring framework. It is time to first experience the power of IoC or DI. In the next section I will focus on the product order functionalities while leave the report functionalities till later purposely.


We can first experience this by creating an integrated Unit Test to see how Spring DI works. SpringDIProductDAOTest.java is created for this purpose. HibernateProductDAO is annotated as @Autowired, means it will rely on the Spring framework to provide this depended object at run time. Thanks Spring for the provided abstract testing class, my unit test can specify the testing context without the deployment of the full blow Spring framework. @Transactional is another great feather in Spring, the declarative transaction management, thanks for Spring AOP.


spirngDemoContext.xml, the Spring configuration file need to be adjusted to embed hibernate, datasource, and transaction handling.


<?xml version="1.0" encoding="UTF-8"?>
<!-- Application context definition for Spring Demo -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<!-- ======= RESOURCE DEFINITIONS ======== -->

<!-- enables annotation based configuration -->
<context:annotation-config />

<!-- scans for annotated classes in the com.company package -->
<context:component-scan base-package="store.entity" />
<context:component-scan base-package="store.manager" />
<tx:annotation-driven />

<!-- Bean Definitions -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="org.hsqldb.jdbcDriver" />
<property name="url" value="jdbc:hsqldb:hsql://localhost:9001" />
<property name="username" value="sa" />
<property name="password" value="" />
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.generate_statistics">true</prop>
</props>
</property>
<property name="configLocation">
<value>classpath:spring.hibernate.cfg.xml</value>
</property>
<property name="configurationClass">
<value>org.hibernate.cfg.AnnotationConfiguration</value>
</property>
</bean>
<bean id="productDAO" class="store.manager.HibernateProductDAO">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="orderDAO" class="store.manager.HibernateOrderDAO">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="orderFunc" class="store.manager.OrderFunctionImp">
<property name="productDAO" ref="productDAO" />
<property name="orderDAO" ref="orderDAO" />
</bean>
</beans>


SpringDIProductDAOTest.java


package store.manager;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests;
import org.springframework.transaction.annotation.Transactional;

import store.entity.Product;

public class SpringDIProductDAOTest extends AbstractTransactionalDataSourceSpringContextTests {

@Autowired
private HibernateProductDAO productDAO;

public void setProductDAO( HibernateProductDAO productDAO ) {
this.productDAO = productDAO;
}

protected String[] getConfigLocations() {
return new String[]{ "classpath:springDemoContext.xml" };
}

@Transactional
public void testProductCRUD() {
//create
Product p = new Product();
p.setName( "SD Card" );
p.setDescription( "32 G" );
p.setUnitPrice( 169 );
p.setThreshold( 5 );
p.setThresholdPrice( 139 );
productDAO.saveProduct( p );
//session.persist( p );
org.junit.Assert.assertTrue( p.getId() != 0 );
//session.clear();
//read
Product p1 = productDAO.getProduct( p.getId() );
org.junit.Assert.assertEquals( p1.getDescription(), p.getDescription() );
//update
p1.setThresholdPrice( 149 );
productDAO.saveProduct( p1 );
//session.saveOrUpdate( p1 );
//session.flush();
//session.clear();
//Product p2 = (Product)session.load( Product.class, p.getId() );
Product p2 = productDAO.getProduct( p.getId() );
org.junit.Assert.assertEquals( p1.getThresholdPrice(), p2.getThresholdPrice(), 0.0001 );
//delete
//session.delete( p2 );
//session.flush();
//session.clear();
//Product p3 = (Product)session.get( Product.class, p.getId() );
productDAO.deleteProduct( p2 );
Product p3 = productDAO.getProduct( p.getId() );
org.junit.Assert.assertNull( p3 );

}
}


Now continue with the function façade. Spring strongly promote code to interface concept. Interface I_OrderFunction was created along with the implementing class OrderFunctionImp.java. I can presumably certain that the required DAO object will be there thanks for the IoC or DI, the magic of Spring. Unit test is similar to the above one.


I_OrderFunction.java Ignore the annotation for now.


package store.manager;

import java.util.List;

import org.springframework.transaction.annotation.Transactional;

import store.entity.Order;
import store.entity.Product;

public interface I_OrderFunction {
@Transactional
public Order saveOrder(Order order);

@Transactional
public Product saveProduct(Product product);

@Transactional
public Order getOrder(int orderId);

@Transactional
public Product getProduct(int productId);

@Transactional
//get all available products.
public List getProducts();
}

And OrderFunctionImp.java


package store.manager;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;

import store.entity.Order;
import store.entity.Product;

public class OrderFunctionImp implements I_OrderFunction {
@Autowired
private HibernateOrderDAO orderDAO;
@Autowired
private HibernateProductDAO productDAO;

public void setOrderDAO( HibernateOrderDAO orderDAO ) {
this.orderDAO = orderDAO;
}

public void setProductDAO( HibernateProductDAO productDAO ) {
this.productDAO = productDAO;
}

@Override
public Order getOrder( int orderId ) {
return orderDAO.getOrder( orderId );
}

@Override
public Product getProduct( int productId ) {
return productDAO.getProduct( productId );
}

@Override
public Order saveOrder( Order order ) {
return orderDAO.saveOrder( order );
}

@Override
public Product saveProduct( Product product ) {
return productDAO.saveProduct( product );
}

@Override
public List getProducts() {
return productDAO.getAllProducts();
}
}


Now with the backend services completed, it is time to build the web component by applying Spring MVC module. Actually this development can be parallel to the backend as long as there is clean interface defined. Spring MVC Controller, can be made of POJO, thanks the magic of annotation, that is ideal for testing; yet is still so flexible, that can access HttpServlet objects if necessary, makes it a powerful tool. The View, mainly made of jsp, while Model, need additional tuning to match the view. In this project, a few model objects, which represent a portion of the entity objects, are created to match the view requirement. In some case, the POJO entity object can be used as model object directly credit to not having platform dependency in those classes.


Key files need to be configured are: web.xml which is pretty standard to all Spring web application, and the default servlet mapping file, springdemo-servlet.xml, that defines view resolver as well as annotation configuration.


POJO controller can be tested by its own just like the above test of DAO DI.


Let’s put all three components together, deploy Spring container inside Tomcat web server, and run the test from the UI. Exception was thrown complaining “No Hibernate Session” in “non-transactional” process. What is wrong? Remember in DAO we delegate the transaction handle to the caller while in unit tests, I add transaction management either programmatically or declaratively so no complains. In this latest integrated test, from the web module to service layer, explicitly declarative transaction management is required. All methods in I_OrderFunction are decorated with @Transactional solves the problem.


I_OrderFunction.java Now the annotations make sense.



Till now we have build a small application consists of several layers: web, service, DAO, data source. Let’s revisit this to see if there is anything can be refactored according to standard Java EE approach. One thing comes to mind here is the separation of the web layer and the service layer. In standard Java EE spec, the distributed web and service layer is transparent to the application developer. Spring, on the other hand, provides proxy wrapper so through simple configuration the separation can be easily achieved. In this project that implement remote access though RMI, it means nothing more than expose RMI service on the service side while define remote beans on the client side.


springDemoContext.xml


<!-- add RMI Decoration for the services -->
<bean id="remoteOrderFunc" class="org.springframework.remoting.rmi.RmiServiceExporter">
<property name="serviceName" value="orderFunc" />
<property name="service" ref="orderFunc" />
<property name="serviceInterface" value="store.manager.I_OrderFunction" />
<!-- defaults to 1099 -->
<property name="registryPort" value="1199" />
</bean>


Unit test for this remote configured Spring RMI service is created. Tricky part for this is to start Spring as a standalone application. Spring ApplicationContext implementation takes care of this.


RemoteOrderFunctionTest.java



public class RemoteOrderFunctionTest {
private static final ApplicationContext ac = new ClassPathXmlApplicationContext( "remoteContext.xml" );;

private I_OrderFunction orderFunc;

@Before
public void initialize() {
I_OrderFunction orderFuncBean = (I_OrderFunction)ac.getBean( "orderFunc" );
setOrderFunc(orderFuncBean);
}
...
}


remoteContext.xml


<?xml version="1.0" encoding="UTF-8"?>
<!-- Application context definition for Spring Demo -->
<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">
<!-- ======= RESOURCE DEFINITIONS ======== -->

<!-- Bean Definitions -->
<bean id="orderFunc" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceUrl" value="rmi://localhost:1199/orderFunc" />
<property name="serviceInterface" value="store.manager.I_OrderFunction" />
</bean>
</beans>


Web layer reconfigured to match the above changes.


Modify web.xml to reflect this configuration file changes.



<ontext-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:remoteDemoContext.xml</param-value>
</context-param>


With the product ordering functionalities in place, let’s get back to the report functionalities purposely left behind. Report request, differentiates itself from other functionalities, for its characteristic slowness. Message Oriented Midware, MOM, provide asynchronized approach is ideal for this kind of request. In addition, MOM broker provide reliability in case of system failure, which is a huge plus for report. Therefore, I will deploy report services via message exchange through MOM broker.


In this project, the open source ActiveMQ broker will be embedded in the app server. In Spring configuration file broker’s configuration is added. Message driven bean, which in terms call the report execution, is created and linked to the certain queue in the configuration.


add the following configuration inspringDemoContext.xml to embed ActiveMQ



<!-- Embed ActiveMQ in Spring -->
<amq:broker useJmx="false" persistent="true">
<amq:transportConnectors>
<amq:transportConnector uri="tcp://localhost:51234" />
</amq:transportConnectors>
</amq:broker>
<amq:queue id="destination" physicalName="embedded" />
<amq:connectionFactory id="jmsFactory" brokerURL="vm://localhost:51234" />
<bean id="connectionFactory"
class="org.springframework.jms.connection.CachingConnectionFactory">
<constructor-arg ref="jmsFactory" />
<property name="sessionCacheSize" value="100" />
</bean>
<!--
listener container definition using the jms namespace, concurrency is
the max number of concurrent listeners that can be started
-->
<jms:listener-container concurrency="4">
<jms:listener id="rptMsgListener" destination="Report.Queue" ref="reportMessageWorker" />
</jms:listener-container>
<!-- end of ActiveMQ -->


Unit test takes advantage of the JmsTemplate provided by Spring for converting and sending message.


ReportJMSTest.java


package store.report;

import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

import javax.jms.ConnectionFactory;
import javax.jms.JMSException;

import org.apache.activemq.ActiveMQConnectionFactory;
import org.junit.Test;
import org.springframework.jms.core.JmsTemplate;

public class ReportJMSTest {

@Test
public void saveReportAttribute() throws JMSException {
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:51234");
//Connection con = connectionFactory.createConnection();
//Session session = con.createSession( true, Session.AUTO_ACKNOWLEDGE );
//Destination d = session.createQueue( "Report.Attr.Queue" );

Map condition = new HashMap();
condition.put( "STARTDATE", "'2010-02-23 00:00:00'" );
condition.put( "ENDDATE", "'2010-02-23 23:59:59'" );

JmsTemplate template = new JmsTemplate(connectionFactory);
ReportInstance instance = new ReportInstance();
instance.setReportId( 0 );
instance.setRequestTime( Calendar.getInstance() );
instance.setReportConditionMap( condition );

template.setDefaultDestinationName( "Report.Queue" );
template.convertAndSend( instance );
}
}


Similar approaches used in the controller to submit report request.



Now finish up the report functionalities. One thing worth point out is the display of report in Excel format. Since Spring allows directly access to HttpServletRequest/Response object, this can be achieved by direct writing to the response.


ReportJMSTest.java



@RequestMapping("/viewReport.do")
public void getProduct(@RequestParam("reportId") int id, HttpServletResponse response) throws Exception {
ReportInstance ri = reportFunc.getReport( id );
BufferedInputStream is = new BufferedInputStream(new FileInputStream(new File(ri.getFileName())));

response.setContentType( "application/vnd.ms-excel" );
ServletOutputStream stream = response.getOutputStream();

int readBytes = 0;
while ((readBytes = is.read()) != -1)
stream.write(readBytes);

stream.close();
is.close();
}


Till this point we have successfully created a distributed enterprise application. Thanks to Spring, we achieved this goal without touching cumbersome EJBs. Though there are indeed still plenty of commonly used enterprise components like Email, WS, JMX, etc. not explored here, this mini-series demonstrated the power of Spring and its general approach target Java EE application. There are still plenty of places can be refactored. For example, uniform DAO access to database persisted object (Order) and static defined object (Report); Standard converter between Entity object and UI data beans. These are clearly not critical to the Spring so I left them to the audiences.



In summary, in this article I demonstrated how easy Spring can be used to develop enterprise application. Technologies addressed here are:


Spring MVC, Spring DI, Spring AOP, Spring JMS, Spring RMI, Hibernate, DAO, ActiveMQ, HSQLDB.



Let me know if there is any suggestions.

0 Comments:

Post a Comment

Subscribe to Post Comments [Atom]

<< Home