Purpose:
This article documents the process of porting the seam 3 hotel booking example to use Hibernate OGM with MongoDB as the datastore.
Code:
The code is available at: https://github.com/ajf8/seam-booking-ogm
Note that this will need the parent 'examples' pom and the toplevel seam pom below that for the maven build to work. See below.
Environment:
- JBoss Enterprise Application Platform 6 beta. These instructions should work for the community AS 7.
- I'm using the MongoDB OGM dialect, but these instructions should apply to the infinispan or ehcache backends with some minor changes to the configuration.
- This is based on the booking example in the seam github at the time of writing. The 3.1.0.Final example had issues.
- Using Hibernate OGM git master at the time of writing (the mongo dialect has not been released yet).
Instructions:
- Install MongoDB. No real configuration is required on Fedora - just 'yum install mongodb-server' and 'service mongod start'.
- Clone the OGM sources, build and install into the local maven repository.
[alan@ajf-rh jboss]$ git clone https://github.com/hibernate/hibernate-ogm.git
[alan@ajf-rh jboss]$ cd hibernate-ogm/
[alan@ajf-rh hibernate-ogm]$ mvn clean install -Pmongodb -s settings-example.xml
- Extract the EAP (or AS7) distribution:
[alan@ajf-rh jboss]$ unzip jboss-eap-6.0.0.Beta1.zip
- Create a directory for the OGM module.
[alan@ajf-rh jboss]$ cd jboss-eap-6.0/modules/org/hibernate/
[alan@ajf-rh hibernate]$ mkdir ogm
[alan@ajf-rh hibernate]$ cd ogm/
- Copy OGM core, the dialects, and the MongoDB java driver into this module directory.
[alan@ajf-rh ogm]$ cp ~/jboss/hibernate-ogm/hibernate-ogm-core/target/hibernate-ogm-core-4.0.0-SNAPSHOT.jar .
[alan@ajf-rh ogm]$ cp ~/jboss/hibernate-ogm/hibernate-ogm-infinispan/target/hibernate-ogm-infinispan-4.0.0-SNAPSHOT.jar .
[alan@ajf-rh ogm]$ cp ~/jboss/hibernate-ogm/hibernate-ogm-mongodb/target/hibernate-ogm-mongodb-4.0.0-SNAPSHOT.jar .
[alan@ajf-rh ogm]$ cp ~/.m2/repository/org/mongodb/mongo-java-driver/2.7.2/mongo-java-driver-2.7.2.jar .
- Create the module.xml (in the same ogm module directory). Ideally we would probably split the dialects into separate modules, but lets keep it simple.
https://raw.github.com/ajf8/seam-booking-ogm/master/module.xml
{code:xml}
<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.1" name="org.hibernate" slot="ogm">
<resources>
<resource-root path="hibernate-ogm-core-4.0.0-SNAPSHOT.jar"/>
<resource-root path="hibernate-ogm-mongodb-4.0.0-SNAPSHOT.jar"/>
<resource-root path="hibernate-ogm-infinispan-4.0.0-SNAPSHOT.jar"/>
<resource-root path="mongo-java-driver-2.7.2.jar"/>
</resources>
<dependencies>
<module name="org.jboss.as.jpa.hibernate" slot="4"/>
<module name="org.hibernate" slot="main" export="true" />
<module name="javax.api"/>
<module name="javax.persistence.api"/>
<module name="javax.transaction.api"/>
<module name="javax.validation.api"/>
<module name="org.infinispan"/>
<module name="org.javassist"/>
<module name="org.jboss.logging"/>
</dependencies>
</module>
{code}
- Clone the seam booking example from the seam github. There was an issue with the 3.1.0.Final example at the time of writing. Maybe a future release will be ok.
[alan@ajf-rh jboss]$ git clone https://github.com/seam/parent.git seam
[alan@ajf-rh jboss]$ cd seam/
[alan@ajf-rh seam]$ git clone https://github.com/seam/dist.git
[alan@ajf-rh seam]$ git clone https://github.com/seam/examples.git
[alan@ajf-rh seam]$ cd examples/
[alan@ajf-rh examples]$ cp -r booking/ mongobooking
[alan@ajf-rh examples]$ cd mongobooking/
If you don't feel like doing the refactorings and just want to try it out, you should be able to clone the project from my github into the examples directory.
- Add the OGM, OGM dialect and Hibernate search dependencies to the pom.xml
https://raw.github.com/ajf8/seam-booking-ogm/master/pom.xml
{code:xml}
<dependency>
<groupId>org.hibernate.ogm</groupId>
<artifactId>hibernate-ogm-core</artifactId>
<version>4.0.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.hibernate.ogm</groupId>
<artifactId>hibernate-ogm-infinispan</artifactId>
<version>4.0.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency><groupId>org.hibernate.ogm</groupId>
<artifactId>hibernate-ogm-mongodb</artifactId>
<version>4.0.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-search</artifactId>
<version>4.1.0.Final</version>
</dependency>
{code}
- Create an empty directory for lucene indexes, writable by the application server process:
[alan@ajf-rh mongobooking]$ mkdir ~/.luceneindexes
- Update persistence.xml. Note the lucene index directory will need changing.
src/main/resources-jbossas7/WEB-INF/classes/META-INF/persistence.xml
{code:xml}
<?xml version="1.0" encoding="UTF-8"?>
<persistence 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_2_0.xsd" version="2.0">
<persistence-unit name="booking">
<class>org.jboss.seam.examples.booking.model.User</class>
<class>org.jboss.seam.examples.booking.model.Booking</class>
<class>org.jboss.seam.examples.booking.model.Hotel</class>
<properties>
<property name="hibernate.transaction.jta.platform" value="org.hibernate.service.jta.platform.internal.JBossAppServerJtaPlatform"/>
<property name="jboss.as.jpa.providerModule" value="org.hibernate:ogm"/>
<!-- Properties for Hibernate (default provider for JBoss AS) -->
<property name="hibernate.ogm.datastore.provider" value="org.hibernate.ogm.datastore.mongodb.impl.MongoDBDatastoreProvider"/>
<property name="hibernate.ogm.mongodb.database" value="booking"/>
<property name="hibernate.ogm.mongodb.host" value="localhost"/>
<property name="hibernate.search.default.directory_provider" value="filesystem"/>
<property name="hibernate.search.default.indexBase" value="/home/alan/.luceneindexes"/>
</properties>
<provider>org.hibernate.ogm.jpa.HibernateOgmPersistence</provider>
</persistence-unit>
</persistence>
{code}
- OGM does not currently support criteria queries, which the booking example uses. There are two queries in the example - the action which searches for hotels, and the one which finds a users bookings. Create an eclipse project and then import it to make the code changes.
[alan@ajf-rh mongobooking]$ mvn eclipse:eclipse
- Create this class, org.jboss.seam.examples.booking.cdi.FMEMProvider. This allows a FullTextEntityManager to be injected into our action classes.
{code:java}
package org.jboss.seam.examples.booking.cdi;
import javax.enterprise.inject.Produces;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.Search;
/**
* @author Emmanuel Bernard
*/
public class FTEMProvider {
@PersistenceContext
EntityManager em;
@Produces
public FullTextEntityManager getFTEM() {
return Search.getFullTextEntityManager( em );
}
}
{code}
- We need to add @Indexed annotations to the entities which need indexing by Hibernate Search. @Field on the fields which need indexing, and @IndexedEmbedded to create the embedded index allowing us to search on booking.user.
Booking entity:
{code:java}
@Entity
@Veto
@Indexed
public class Booking implements Serializable {
private Long id;
@IndexedEmbedded
private User user;
{code}
Hotel entity:
{code:java}
@Entity
@Table(name = "hotel")
@Veto
@Indexed
public class Hotel implements Serializable {
private Long id;
@Field
private String name;
@Field
private String address;
@Field
private String city;
@Field
private String state;
@Field
private String zip;
@Field
private String country;{code}
User entity:
{code:java}
@Entity
@Table(name = "traveler")
@Veto
@Indexed
public class User implements Serializable {
private static final long serialVersionUID = -602733026033932730L;
@Field
private String username;
{code}
- Refactor the HotelSearch class to use hibernate search. Start by adding the FullTextEntityManager (which is injected by the FMEMProvider defined earlier using CDI/weld).
{code:java}
@Inject
private Provider<FullTextEntityManager> lazyFEM;
{code}
- Replace the queryHotels method with one which uses Hibernate search and lucene. You could experiment with the lucene queries here.
{code:java}
private void queryHotels(final SearchCriteria criteria) {
if (criteria.getQuery().length() < 1) {
nextPageAvailable = false;
hotels = new ArrayList<Hotel>();
return;
}
FullTextEntityManager em = lazyFEM.get();
final QueryBuilder builder = em.getSearchFactory().buildQueryBuilder()
.forEntity(Hotel.class).get();
final Query luceneQuery = builder.keyword()
.onField("name").andField("address").andField("city")
.andField("state").andField("zip").andField("country")
.matching(criteria.getQuery()).createQuery();
System.out.println(luceneQuery.toString());
final FullTextQuery query = em.createFullTextQuery(luceneQuery,
Hotel.class);
query.initializeObjectsWith(ObjectLookupMethod.SKIP,
DatabaseRetrievalMethod.FIND_BY_ID);
final List<Hotel> results = query
.setFirstResult(criteria.getFetchOffset())
.setMaxResults(criteria.getFetchSize()).getResultList();
nextPageAvailable = results.size() > criteria.getPageSize();
if (nextPageAvailable) {
// NOTE create new ArrayList since subList creates unserializable
// list
hotels = new ArrayList<Hotel>(results.subList(0,
criteria.getPageSize()));
} else {
hotels = results;
}
log.info(messageBuilder
.get()
.text("Found {0} hotel(s) matching search term [ {1} ] (limit {2})")
.textParams(hotels.size(), criteria.getQuery(),
criteria.getPageSize()).build().getText());
}
{code}
- A similar is refactoring is needed for the BookingHistory class. Add the FullTextEntityManager (as per above), then replace the fetchBookingsForCurrentUser method
{code:java}
private void fetchBookingsForCurrentUser() {
String username = currentUserInstance.get().getUsername();
FullTextEntityManager em = lazyFEM.get();
final QueryBuilder builder = em.getSearchFactory().buildQueryBuilder()
.forEntity(Booking.class).get();
final Query luceneQuery = builder.keyword().onField("user.username")
.matching(username).createQuery();
System.out.println(luceneQuery.toString());
final FullTextQuery query = em.createFullTextQuery(luceneQuery.
Booking.class);
query.initializeObjectsWith(ObjectLookupMethod.SKIP,
DatabaseRetrievalMethod.FIND_BY_ID);
bookingsForUser = query.getResultList();
}
{code}
- Build the war and deploy it to the application server.
[alan@ajf-rh mongobooking]$ mvn clean package
[alan@ajf-rh mongobooking]$ cp target/seam-booking.war ~/jboss/jboss-eap-6.0/standalone/deployments/
- Start the application server. I found there are a lot of harmless exceptions caused by CDI/weld, which can be ignored.
[alan@ajf-rh mongobooking]$ cd ~/jboss/jboss-eap-6.0/bin
[alan@ajf-rh bin]$ ./standalone.s
- The application should be available at http://localhost:8080/seam-booking
Note that when you search for hotels, unlike the original example, this only matches full keywords (case insensitive). Try typing "hotel" or "marriott"
- After the application has been started and seed data has been imported, we can see entities in the MongoDB:
[alan@ajf-rh mongobooking]$ mongo booking
MongoDB shell version: 2.0.2
connecting to: booking
> db.traveler.findOne()
{
"_id" : "shane",
"email" : "shane@example.com",
"name" : "Shane Bryzak",
"password" : "brisbane",
"username" : "shane"
}
Comments