Saturday, April 14, 2012

Apache Axis2 Tutorial: Integrating with Spring 3 and Hibernate 3

Overview:
This article looks at how you can integrate Spring and Hibernate with Apache Axis2. Axis2 also enables making use of Spring and Hibernate mush similar to Apache CXF( Tutorial on Apache CXF with Spring and Hibernate). This is a simple but a comprehensive tutorial. We'll be testing the service's REST endpoint using CURL.

The sample code for this project can be found here.

Prerequisite:
JDK >= 1.5.x
Maven >= 2.2.x
MySQL

Project Structure: Following is the project structure I have used (Maven WAR project).
Java class structure:


Resource structure:


Step 1: Creating the Database Table
 CREATE TABLE PERSON (
PERSON_ID BIGINT AUTO_INCREMENT,
FIRST_NAME VARCHAR(30),
LAST_NAME VARCHAR(30),
PRIMARY KEY (PERSON_ID)
)

Step 2: Maven Dependencies (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>org.fazlan</groupId>
<artifactId>org.fazlan.employee.axis2.orm.spring.service</artifactId>
<packaging>aar</packaging>
<version>1.0-SNAPSHOT</version>
<name>org.fazlan.employee.axis2.orm.spring.service</name>
<url>http://maven.apache.org</url>
<build>
<plugins>
  <plugin>
    <groupId>org.apache.axis2</groupId>
    <artifactId>axis2-aar-maven-plugin</artifactId>
    <version>1.6.1</version>
    <!--<version>${orbit.version.axis2}</version>-->
    <extensions>true</extensions>
    <configuration>
      <aarName>org.fazlan.employee.axis2.orm.spring.service</aarName>
    </configuration>
  </plugin>
</plugins>
</build>
<dependencies>
<!-- Axis2 -->
<dependency>
  <groupId>org.apache.axis2</groupId>
  <artifactId>axis2</artifactId>
  <version>${org.apache.axis2.version}</version>
</dependency>
<dependency>
      <groupId>org.apache.axis2</groupId>
      <artifactId>axis2-spring</artifactId>
      <version>${org.apache.axis2.version}</version>
    </dependency>
<!-- Spring framework -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-core</artifactId>
  <version>${org.springframework.version}</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>${org.springframework.version}</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aop</artifactId>
  <version>${org.springframework.version}</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-beans</artifactId>
  <version>${org.springframework.version}</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context-support</artifactId>
  <version>${org.springframework.version}</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-tx</artifactId>
  <version>${org.springframework.version}</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-jdbc</artifactId>
  <version>${org.springframework.version}</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-orm</artifactId>
  <version>${org.springframework.version}</version>
</dependency>
<!-- MySQL database driver -->
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.9</version>
</dependency>
<!-- Hibernate framework -->
<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-core</artifactId>
  <version>${org.hibernate.version}</version>
</dependency>
<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-entitymanager</artifactId>
  <version>${org.hibernate.version}</version>
</dependency>
<!-- Hibernate library dependecy start -->
<dependency>
  <groupId>dom4j</groupId>
  <artifactId>dom4j</artifactId>
  <version>1.6.1</version>
</dependency>
<dependency>
  <groupId>commons-logging</groupId>
  <artifactId>commons-logging</artifactId>
  <version>1.1.1</version>
</dependency>
<dependency>
  <groupId>commons-collections</groupId>
  <artifactId>commons-collections</artifactId>
  <version>3.2.1</version>
</dependency>
<dependency>
  <groupId>antlr</groupId>
  <artifactId>antlr</artifactId>
  <version>2.7.7</version>
</dependency>
<!-- Hibernate library dependency end -->
<!--AspectJ dependency-->
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjrt</artifactId>
  <version>${org.aspectj.version}</version>
</dependency>
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>${org.aspectj.version}</version>
</dependency>
 <!-- JUnit testing framework -->
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.8.1</version>
</dependency>
</dependencies>
<properties>
<org.apache.axis2.version>1.6.1</org.apache.axis2.version>
<org.aspectj.version>1.6.11</org.aspectj.version>
<org.hibernate.version>3.6.0.Final</org.hibernate.version>
<org.springframework.version>3.1.1.RELEASE</org.springframework.version>
</properties>
</project>

Step 3: Defining the POJO, DAO and Service Implementation
- Employee.java

 package org.fazlan.employee.axis2.orm.spring.service.beans;
import java.io.Serializable;
public class Employee implements Serializable {
private Long id;
private String firstName;
private String lastName;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Employee)) return false;
Employee employee = (Employee) o;
return !(firstName != null ? !firstName.equals(employee.firstName) : employee.firstName != null) && !(id != null ? !id.equals(employee.id) : employee.id != null) && !(lastName != null ? !lastName.equals(employee.lastName) : employee.lastName != null);
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (firstName != null ? firstName.hashCode() : 0);
result = 31 * result + (lastName != null ? lastName.hashCode() : 0);
return result;
}
}

- DAO Interface and Implementation

IDao.java
 package org.fazlan.employee.axis2.orm.spring.service.dao;
import java.util.List;
public interface IDao<T> {
Long save(T t);
boolean update(T t);
boolean delete(T t);
T get(Long id);
List<T> getAll();
}

EmployeeDao.java

 package org.fazlan.employee.axis2.orm.spring.service.dao;
import org.fazlan.employee.axis2.orm.spring.service.beans.Employee;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import java.util.List;
public class EmployeeDao extends HibernateDaoSupport implements IDao<Employee>{
public Long save(Employee employee) {
return (Long) getHibernateTemplate().save(employee);
}
public boolean update(Employee employee) {
getHibernateTemplate().update(employee);
return true;
}
public boolean delete(Employee employee) {
getHibernateTemplate().delete(employee);
return true;
}
public Employee get(Long id) {
return getHibernateTemplate().get(Employee.class, id); //To change body of implemented methods use File | Settings | File Templates.
}
public List<Employee> getAll() {
return getHibernateTemplate().find("from Employee");
}
}

- Service Interface and Implementation

IService.java
 package org.fazlan.employee.axis2.orm.spring.service.services;
import java.util.List;
/**
* Created by IntelliJ IDEA.
* User: fazlan
* Date: 3/9/12
* Time: 4:36 PM
* To change this template use File | Settings | File Templates.
*/
public interface IService<T> {
Long add(T t);
boolean update(T t);
boolean delete(T t);
T get(Long id);
List<T> getAll();
}

EmployeeService.java

 package org.fazlan.employee.axis2.orm.spring.service.services;
import org.fazlan.employee.axis2.orm.spring.service.beans.Employee;
import org.fazlan.employee.axis2.orm.spring.service.dao.IDao;
import java.util.List;
public class EmployeeService implements IService<Employee> {
private IDao<Employee> employeeDao;
public Long add(Employee employee) {
return employeeDao.save(employee);
}
public boolean update(Employee employee) {
employeeDao.update(employee);
return true;
}
public boolean delete(Employee employee) {
employeeDao.delete(employee);
return true;
}
public Employee get(Long id) {
return employeeDao.get(id);
}
public List<Employee> getAll() {
return employeeDao.getAll();
}
public void setEmployeeDao(IDao<Employee> employeeDao) {
this.employeeDao = employeeDao;
}
}

Step 4: Defining the Service class to load the Spring Context in Axis2 for this *.aar
EmployeeSpringInit.java

 package org.fazlan.employee.axis2.orm.spring.service.services;
import org.apache.axis2.engine.ServiceLifeCycle;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.description.AxisService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class EmployeeSpringInit implements ServiceLifeCycle {
private static Log logger = LogFactory
.getLog(EmployeeSpringInit.class);
/**
* this will be called during the deployement time of the service. irrespective
* of the service scope this method will be called
*/
public void startUp(ConfigurationContext ignore, AxisService service) {
ClassLoader classLoader = service.getClassLoader();
ClassPathXmlApplicationContext appCtx = new
  ClassPathXmlApplicationContext(new String[] {"classpath:spring/spring-app-context.xml"}, false);
appCtx.setClassLoader(classLoader);
appCtx.refresh();
if (logger.isDebugEnabled()) {
  logger.debug("\n\nstartUp() set spring classloader via axisService.getClassLoader() ... ");
}
}
/**
* this will be called during the deployement time of the service. irrespective
* of the service scope this method will be called
*/
public void shutDown(ConfigurationContext ignore, AxisService service) {
}
}

Step 5: MySQL Database Connection Properties (hibernate/hbm/db.properties)
 jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/springhibernate
jdbc.username=root
jdbc.password=root123

Step 6: Hibernate Mapping of Employee POJO (hibernate/hbm/Employee.hbm.xml)
 <?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="org.fazlan.employee.axis2.orm.spring.service.beans.Employee" table="PERSON">
<id name="id" column="PERSON_ID" type="long" access="field">
  <generator class="increment" />
</id>
<property name="firstName" type="string">
  <column name="FIRST_NAME" />
</property>
<property name="lastName" type="string">
  <column name="LAST_NAME" />
</property>
</class>
</hibernate-mapping>

Step 7: Defining the Spring Configuration for the Application (spring/spring-app-context.xml)

This is the Spring bean definition file in our application containing all the beans required to run the application.

The following is very important, this class and the spring bean needed to wire it could be used as an alternative to getting the ApplicationContext from the ServletContext.

 <bean id="applicationContext"
class="org.apache.axis2.extensions.spring.receivers.ApplicationContextHolder" />

Putting all together,
 <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-3.0.xsd">
<bean id="applicationContext"
class="org.apache.axis2.extensions.spring.receivers.ApplicationContextHolder" />
<bean class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer">
<property name="location" value="classpath:hibernate/properties/db.properties"/>
</bean>
<!-- MySQL datasource -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 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>
  </props>
</property>
<property name="mappingLocations">
  <value>classpath:hibernate/hbm/Employee.hbm.xml</value>
</property>
</bean>
<bean id="employeeDao" class="org.fazlan.employee.axis2.orm.spring.service.dao.EmployeeDao">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<bean id="employeeSpringService" class="org.fazlan.employee.axis2.orm.spring.service.services.EmployeeService">
<property name="employeeDao" ref="employeeDao"/>
</bean>
</beans>

Step 8: Defining your Axis2 Service Configuration (META-INF/services.xml)

EmployeeSpringInit Service: Loading the Spring context
 <service name="EmployeeSpringInit" class="org.fazlan.employee.axis2.orm.spring.service.services.EmployeeSpringInit">
<description>
  This web service initializes Spring.
</description>
<parameter name="ServiceClass">org.fazlan.employee.axis2.orm.spring.service.services.EmployeeSpringInit
</parameter>
<parameter name="ServiceTCCL">composite</parameter>
<parameter name="load-on-startup">true</parameter>
<!--<operation name="springInit">
 <messageReceiver class="org.apache.axis2.receivers.RawXMLINOutMessageReceiver"/>
</operation>-->
</service>
Here, we use this service to load the Spring context at the startup of the application.

EmployeeService - The actual service definition.
 <service name="EmployeeService">
<description>
  Weather Spring POJO Axis2 AAR deployment
</description>
<parameter name="ServiceClass">org.fazlan.employee.axis2.orm.spring.service.services.EmployeeService</parameter>
<parameter name="ServiceObjectSupplier">
  org.apache.axis2.extensions.spring.receivers.SpringAppContextAwareObjectSupplier
</parameter>
<excludeOperations>
  <operation>setEmployeeDao</operation>
</excludeOperations>
<parameter name="SpringBeanName">employeeSpringService</parameter>
<parameter name="SpringContextLocation">spring/spring-app-context.xml</parameter>
<messageReceivers>
  <messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-only"
           class="org.apache.axis2.rpc.receivers.RPCInOnlyMessageReceiver"/>
  <messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-out"
           class="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/>
</messageReceivers>
</service>

By default, the Axis2 Engine will look for <your.aar>/applicationContext.xml to load the Spring bean configurations.

You can change this by either defining the following property.
 <parameter name="SpringContextLocation">spring/spring-app-context.xml</parameter>

This tells the Axis2 Engine where to locate the Spring application context file for this service. This is very useful when you have multiple Spring application services in the same Axis3 Engine.
Or,
You can place the Spring configuration file alongside the service.xml as in the following format. Axis2 Engine will implicitly detect this.

META-INF/<service-name>-application-context.xml

Putting all together,
 <serviceGroup>
<service name="EmployeeSpringInit" class="org.fazlan.employee.axis2.orm.spring.service.services.EmployeeSpringInit">
<description>
  This web service initializes Spring.
</description>
<parameter name="ServiceClass">org.fazlan.employee.axis2.orm.spring.service.services.EmployeeSpringInit
</parameter>
<parameter name="ServiceTCCL">composite</parameter>
<parameter name="load-on-startup">true</parameter>
<!--<operation name="springInit">
 <messageReceiver class="org.apache.axis2.receivers.RawXMLINOutMessageReceiver"/>
</operation>-->
</service>
<service name="EmployeeService">
<description>
  Weather Spring POJO Axis2 AAR deployment
</description>
<parameter name="ServiceClass">org.fazlan.employee.axis2.orm.spring.service.services.EmployeeService</parameter>
<parameter name="ServiceObjectSupplier">
  org.apache.axis2.extensions.spring.receivers.SpringAppContextAwareObjectSupplier
</parameter>
<excludeOperations>
  <operation>setEmployeeDao</operation>
</excludeOperations>
<parameter name="SpringBeanName">employeeSpringService</parameter>
<parameter name="SpringContextLocation">spring/spring-app-context.xml</parameter>
<messageReceivers>
  <messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-only"
           class="org.apache.axis2.rpc.receivers.RPCInOnlyMessageReceiver"/>
  <messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-out"
           class="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/>
</messageReceivers>
</service>
</serviceGroup>

Step 9: Building the Project
mvn clean install

Step 10: Deploying the Service
Now you can deploy this service on to any container. Here we'll deploy this on Tomcat server.

You need to copy the following *.jars to <TOMCAT_HOME>/axis2/WEB-INF/lib/ directory (e;i: /home/fazlan/apache-tomcat-6.0.32/webapps/axis2/WEB-INF/lib/)in your Axis2 WAR already deployed on Tomcat.

Spring 3.1.1.RELEASE jars
spring-aop-3.1.1.RELEASE.jar
spring-asm-3.1.1.RELEASE.jar
spring-beans-3.1.1.RELEASE.jar
spring-context-3.1.1.RELEASE.jar
spring-context-support-3.1.1.RELEASE.jar
spring-core-3.1.1.RELEASE.jar
spring-expression-3.1.1.RELEASE.jar
spring-jdbc-3.1.1.RELEASE.jar
spring-orm-3.1.1.RELEASE.jar
spring-tx-3.1.1.RELEASE.jar
spring-web-3.1.1.RELEASE.jar

Hibernate3 jars
hibernate-commons-annotations-3.2.0.Final.jar
hibernate-core-3.6.0.Final.jar
hibernate-entitymanager-3.6.0.Final.jar
hibernate-jpa-2.0-api-1.0.0.Final.jar

Hibernate3 dependent jars
dom4j-1.6.1.jar
slf4j-api-1.6.1.jar
commons-collections-3.2.1.jar
javassist-3.12.0.GA.jar

This is due to the fact Axis2 Engine tries to load the classes from the jars from /axis2/WEB-INF/lib/ rather than locally in the *.aar file. Although, I tried with the following property in the services.xml, it didn't start the service.
 <parameter name="EnableChildFirstClassLoading">true</parameter>

Once, all the above *.jars are copied to /axis2/WEB-INF/lib/ on Tomcat. We are good to deploy the service and test.

Step 11: Testing the Service

Option 1: Testing the SOAP EPR with SOAP UI.

You can use SOAP UI and test the service with http://localhost:8080/axis2/services/EmployeeService?wsdl SOAP endpoint

Option 2: Testing REFTful endpoint using CURL

Install CURL
sudo apt-get install curl

Now, after successfully installing CURL, we are ready to test the services.
Issue the following commands on the terminal.

POST a Resource: Adding a new employee resource.
- Adding the new employee record in the add.xml file.
 <ns:add xmlns:ns="http://services.service.spring.orm.axis2.employee.fazlan.org">
  <ns:employee xmlns:ax21="http://beans.service.spring.orm.axis2.employee.fazlan.org/xsd"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ax21:Employee">
       <ax21:firstName>My FirstName</ax21:firstName>
       <ax21:id>1</ax21:id>
       <ax21:lastName>My LastName</ax21:lastName>
  </ns:employee>
</ns:add>

 curl -v -H "Accept: application/xml" -H "Content-type: application/xml" -X POST -d @add.xml http://localhost:8080/axis2/services/EmployeeService/add

GET Resources:
- Retrieving all the employee resources.
 curl -v -H "Accept: application/xml" http://localhost:8080/axis2/services/EmployeeService/getAll

- Retrieving a employee resource for a given id(id = 1).
 curl -v -H "Accept: application/xml" http://localhost:8080/axis2/services/EmployeeService/get?id=1


PUT a Resource: Updating an existing employee resource.
- Updating an existing employee record in the update.xml file.
 <ns:update xmlns:ns="http://services.service.spring.orm.axis2.employee.fazlan.org">
  <ns:employee xmlns:ax21="http://beans.service.spring.orm.axis2.employee.fazlan.org/xsd"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ax21:Employee">
       <ax21:firstName>Update FirstName</ax21:firstName>
       <ax21:id>1</ax21:id>
       <ax21:lastName>Update LastName</ax21:lastName>
  </ns:employee>
</ns:update>

 curl -v -H "Accept: application/xml" -H "Content-type: application/xml" -X PUT -d @update.xml http://localhost:8080/axis2/services/EmployeeService/update

DELETE a Resource: Deleting an employee for a given id (id=1)
- Deleting an existing employee record in the delete.xml file.
 <ns:delete xmlns:ns="http://services.service.spring.orm.axis2.employee.fazlan.org">
  <ns:employee xmlns:ax21="http://beans.service.spring.orm.axis2.employee.fazlan.org/xsd"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ax21:Employee">
       <ax21:firstName>Update FirstName</ax21:firstName>
       <ax21:id>1</ax21:id>
       <ax21:lastName>Update LastName</ax21:lastName>
  </ns:employee>
</ns:delete>

 curl -i -H "Accept: application/xml" -H "Content-type: application/xml" -X PUT -d @delete.xml http://localhost:8080/axis2/services/EmployeeService/delete

Summary:
This tutorial looked at how we can integrate Spring and Hibernate with Apache Axis2.

The sample code for this project can be found here.

4 comments:

  1. Thanks a lot, I was searching for such types of Example from long time.

    ReplyDelete
  2. Muy bien explicado el post. Solo unos detalles:
    1.- Hay que copiar el driver (en este caso, mysql-connector-java-5.1.9.jar) y la librería jta-1.1.jar al WEB-INF/lib de axis2
    2.- En el fichero de Spring se hace referencia a hibernate/properties/db.properties y en realidad es hibernate/hbm/db.properties

    ReplyDelete
  3. dear Fazlan!
    thanks a lot. i was read article above and do it. i downloaded source code and run but have a lot error. you can sent full source code this demo for me into address: tunt.bk@gmail.com. thanks for you!

    ReplyDelete
  4. Hi Fazlan,

    Thank you for the valuable content.
    One suggestion: maybe you could fix broken pictures links.

    Regards.

    ReplyDelete