Spring, Hibernate, and XFire
A bit of back story:
I had to tie in web services to a hibernate-based backend for the upcoming release of FireBright's SaaS on Demand platform. The problem: Doing it in such a way that:
Web services are quite interesting beasts. Not something to be taken lightly, if you consider the implementation that Axis2 provides. That being said, there is another way, privided by the friendly folks at XFire (which, incidentally, is where I found out about FishEye?). But, there seems to be a bit of a disconnect.. I've got objects persisted by Hibernate, and those same objects need to be rendered as XML Data, and with over 50 entity types and relationships, I really don't want to have to write populators for everything.
Again, introduce XFire. Why? Because it makes this stuff simple. WSDL Generation, auto-typing using aegis or JAXB (PERFECT!), and it even uses jws annotations, so it limits the amount of implementation specific code in my own code. So how this is done.
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring-service.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>XFireServlet</servlet-name> <display-name>XFire Servlet</display-name> <servlet-class>org.codehaus.xfire.spring.XFireSpringServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>XFireServlet</servlet-name> <url-pattern>/servlet/XFireServlet/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>XFireServlet</servlet-name> <url-pattern>/services/*</url-pattern> </servlet-mapping> </web-app>
Next, let's start building out the XFire side of the spring config. This allows XFire to talk to service beans configured through spring, and allows it to read Jsr181 annotations (javax.jws).
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <import resource="classpath:org/codehaus/xfire/spring/xfire.xml"/> <bean id="jaxbTypeMappingRegistry" class="org.codehaus.xfire.jaxb2.JaxbTypeRegistry" init-method="createDefaultMappings" singleton="true"/> <bean id="webAnnotations" class="org.codehaus.xfire.annotations.jsr181.Jsr181WebAnnotations"/> <bean id="handlerMapping" class="org.codehaus.xfire.spring.remoting.Jsr181HandlerMapping"> <property name="typeMappingRegistry"> <ref bean="jaxbTypeMappingRegistry"/> </property> <property name="xfire"> <ref bean="xfire"/> </property> <property name="webAnnotations"> <ref bean="webAnnotations"/> </property> </bean> <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="urlMap"> <map> <entry key="/"> <ref bean="handlerMapping"/> </entry> </map> </property> </bean> </beans>
Next, we add a spring bean for each of our services. In this case, I have an AOP proxy that checks authentication before the method gets executed, so I will wrap each service bean with a ProxyFactoryBean:
<bean id="userService" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target"> <bean class="com.firebright.service.impl.EchoDatabaseTestServiceImpl"/> </property> <property name="interceptorNames"> <list> <value>authSessionAdvisor</value> </list> </property> </bean>
Looking at EchoDatabaseTestServiceImpl:
package com.firebright.service.impl; import com.firebright.common.persistence.manager.IdentifiableManager; import com.firebright.service.EchoDatabaseTestService; import com.firebright.service.support.entity.Foo; import javax.jws.WebService; @WebService(name="EchoDatabaseTestService", endpointInterface = "com.firebright.service.EchoDatabaseTestService") public class EchoDatabaseTestServiceImpl implements EchoDatabaseTestService { private IdentifiableManager manager = null; public void setManager(IdentifiableManager manager) { this.manager = manager; } public Foo getFoo(Long id) { return (Foo) manager.getOne(id); } }
Now, what's happening is this:
The interface is where we will put all of our annotations to describe what is actually available in our SOAP API:
package com.firebright.service; import com.firebright.service.support.entity.Foo; import javax.jws.WebMethod; import javax.jws.WebParam; import javax.jws.WebResult; import javax.jws.WebService; @WebService(name="EchoDatabaseTestService", targetNamespace = "http://service.firebright.com") public interface UserService { @WebMethod @WebResult(name="Foo") public Foo getFoo(@WebParam(name="id") Long id); }
And that's it for the XFire side of things. XFire will automatically enumerate each of your beans, grab their annotations, and generate a wsdl and complex types for, in this case, our "Foo" object (which we'll get to later in this article).
However, we will have an issue, since when we make the request, the Foo object shows up in the namespace "entity.support.service.firebright.com" which is a bit silly, so let's set it to the proper namespace:
package com.firebright.service.support.entity; import org.codehaus.xfire.aegis.type.java5.XmlType; @Entity @XmlType(name="Foo", namespace="http://service.firebright.com") public class Foo { ... }
Next, let's set up hibernate. First we'll add a filter to web.xml to bind the current hibernate session to our services:
<filter> <filter-name>sessionFilter</filter-name> <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class> </filter> <filter-mapping> <filter-name>sessionFilter</filter-name> <url-pattern>/services/*</url-pattern> </filter-mapping>
Next, set up our spring-context.xml to include required hibernate elements, as well as configuring it to look at EJB3 annotated entity beans.
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <property name="dataSource"> <ref bean="postgresDataSource"/> </property> <property name="hibernateProperties"> <ref bean="hibernateProperties"/> </property> <property name="configurationClass"> <value>org.hibernate.cfg.AnnotationConfiguration</value> </property> <property name="configLocation"> <value>/WEB-INF/classes/hibernate.cfg.xml</value> </property> </bean> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory"> <ref bean="sessionFactory"/> </property> </bean> <bean id="transactionProxyTemplate" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager"> <ref bean="transactionManager"/> </property> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> <bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate"> <property name="sessionFactory"> <ref bean="sessionFactory"/> </property> </bean> <bean id="hibernateInterceptor" class="org.springframework.orm.hibernate3.HibernateInterceptor"> <property name="sessionFactory"> <ref bean="sessionFactory"/> </property> </bean> <bean id="hibernateProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean"> <property name="properties"> <props> <prop key="hibernate.hbm2ddl.auto">update</prop> <prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</prop> <prop key="hibernate.query.substitutions">true 'T', false 'F'</prop> <prop key="hibernate.show_sql">@@hibernate.show_sql@@</prop> <prop key="hibernate.c3p0.minPoolSize">5</prop> <prop key="hibernate.c3p0.maxPoolSize">20</prop> <prop key="hibernate.c3p0.timeout">600</prop> <prop key="hibernate.c3p0.max_statement">50</prop> <prop key="hibernate.c3p0.testConnectionOnCheckout">false</prop> </props> </property> </bean> <bean id="postgresDataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName"> <value>org.postgresql.Driver</value> </property> <property name="url"> <value>@@db.connection.url@@</value> </property> <property name="username"> <value>@@db.connection.username@@</value> </property> <property name="password"> <value>@@db.connection.password@@</value> </property> </bean> </beans>
Each of our managers will use a DAO that is configured to talk to hibernate through the hibernate session.
The DAO interface looks like this. We'll get into what Identifiable is later on.
package com.firebright.common.persistence.dao; import com.firebright.common.entity.Identifiable; import org.hibernate.criterion.Criterion; import org.hibernate.criterion.Order; import java.util.List; public interface IdentifiableDao { public void refresh(Identifiable identifiable); public void save(Identifiable identifiable); public void delete(Identifiable identifiable); public List<Identifiable> getAll(Class clazz); public List<Identifiable> getAll(Class clazz, List<Criterion> criterion); public List<Identifiable> getAll(Class clazz, List<Criterion> criterion, Order order); public Identifiable getOne(Class clazz, Long id); public int getCount(Class clazz, List<Criterion> criterion); int getCount(Class managedClass, List<Criterion> criteria, Order order); }
And our hibernate specific dao:
ackage com.firebright.common.persistence.dao.impl; import com.firebright.common.entity.Identifiable; import com.firebright.common.persistence.dao.IdentifiableDao; import com.firebright.common.persistence.util.PersistentObjectNotFoundException; import org.hibernate.criterion.*; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; import java.util.List; public class HibernateIdentifiableDaoImpl extends HibernateDaoSupport implements IdentifiableDao { public void refresh(Identifiable identifiable) { getHibernateTemplate().refresh(identifiable); } public void save(Identifiable identifiable) { getHibernateTemplate().saveOrUpdate(identifiable); } public void delete(Identifiable identifiable) { getHibernateTemplate().delete(identifiable); } public Identifiable getOne(Class clazz, Long id) { return getOne(clazz, id, Order.asc("id")); } public Identifiable getOne(Class clazz, Long id, Order order) { return (Identifiable) getHibernateTemplate().get(clazz, id); } public List<Identifiable> getAll(Class clazz) { List list = getHibernateTemplate().loadAll(clazz); return list; } public List<Identifiable> getAll(Class clazz, List<Criterion> criterion) { return getAll(clazz, criterion, Order.asc("id")); }
The spring config for the DAO:
<bean id="identifiableDao" class="com.firebright.common.persistence.dao.impl.HibernateIdentifiableDaoImpl"> <property name="hibernateTemplate"> <ref bean="hibernateTemplate"/> </property> <property name="sessionFactory"> <ref bean="sessionFactory"/> </property> </bean>
This is pretty simple. Note that it, too, has the namespace defined on it.
package com.firebright.common.entity; import org.codehaus.xfire.aegis.type.java5.XmlType; @XmlType(name = "Identifiable", namespace = "http://service.firebright.com") public interface Identifiable { public Long getId(); public void setId(Long id); }
So there are a few things left.
We will be releasing a "starting point" project which includes ant scripts/tasks to build hibernate.cfg.xml from annotated classes, and a basic "Hello World" application that can retrieve data from the database. Stay tuned!