Version 14

    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.

     

     

    !seam-ogm.png!

     

    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

     

    https://raw.github.com/ajf8/seam-booking-ogm/master/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.

     

    https://github.com/ajf8/seam-booking-ogm/blob/master/src/main/java/org/jboss/seam/examples/booking/cdi/FTEMProvider.java

     

    {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:

     

    https://raw.github.com/ajf8/seam-booking-ogm/master/src/main/java/org/jboss/seam/examples/booking/model/Booking.java

     

    {code:java}

    @Entity

    @Veto

    @Indexed

    public class Booking implements Serializable {

        private Long id;

        @IndexedEmbedded

        private User user;

    {code}

     

    Hotel entity:

     

    https://raw.github.com/ajf8/seam-booking-ogm/master/src/main/java/org/jboss/seam/examples/booking/model/Hotel.java

     

    {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:

     

    https://raw.github.com/ajf8/seam-booking-ogm/master/src/main/java/org/jboss/seam/examples/booking/model/User.java

     

    {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).

     

    https://github.com/ajf8/seam-booking-ogm/raw/master/src/main/java/org/jboss/seam/examples/booking/inventory/HotelSearch.java

     

    {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

     

    https://github.com/ajf8/seam-booking-ogm/raw/master/src/main/java/org/jboss/seam/examples/booking/booking/BookingHistory.java

     

    {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

     

     

    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"
    }