How to Port the Elasticsearch-Plugin to Grails 2.3.0 and Hibernate 4.x

If you want to use Grails 2.3.0 and Hibernate 4.x, you will notice that a lot of the Grails plugins cannot be used, because they depend on Hibernate 3.x.

One of these candidates is the Grails ElasticSearch plugin.

Here I present my attempt to modify the ElasticSearch plugin, so it will be usable with Grails 2.3.0 and Hibernate 4.x.

Port of the ElasticSearch Plugin

Unfortunately, we need a new codebase for the ElasticSearch plugin, because Hibernate 4 is not backwards compatible with Hibernate 3.

1. Download/fork the ElasticSearch plugin from github.

2. Modify BuildConfig.groovy

// BuildConfig.groovy
...    dependencies {
        // specify dependencies here under either 'build', 'compile', 'runtime', 'test' or 'provided' scopes eg.
        // runtime "org.elasticsearch:elasticsearch:0.90.3" //  change to
        runtime "org.elasticsearch:elasticsearch:0.90.5"
        runtime "org.elasticsearch:elasticsearch-lang-groovy:1.5.0"
        runtime 'com.spatial4j:spatial4j:0.3'
	test("org.spockframework:spock-grails-support:0.7-groovy-2.0"){
           export = false        }
   }
    plugins {
	// runtime ":hibernate:$grailsVersion"
        //build (":release:2.2.1", ":rest-client-builder:1.0.3") {
        //    export = false
        //}
        // change to
        build ":tomcat:7.0.42"
        runtime ":hibernate4:4.1.11.1"
        test(":spock:0.7") {
          export = false
          exclude "spock-grails-support" }
    }
}

3. Modify DataSource.groovy

// DataSource.groovy
hibernate {
    cache.use_second_level_cache = true
    cache.use_query_cache = true
    // cache.region.factory_class = 'net.sf.ehcache.hibernate.EhCacheRegionFactory' // Hibernate 3
    cache.region.factory_class = 'org.hibernate.cache.ehcache.EhCacheRegionFactory' // Hibernate 4
}

Eventually, add junit-4.11 and hamcrest-core-1.3 to the classpath.

4. Run SearchableClassMappingTest

Running the SearchableClassMappingTest with grails ver. 2.3.0, you will see a lot of “unable to resolve class“-errors:

| Error Compilation error: startup failed:
.../plugins/elasticsearch/AuditEventListener.groovy: 19: unable to resolve class org.hibernate.event.PostCollectionUpdateEvent
@ line 19, column 1. import org.hibernate.event.PostCollectionUpdateEvent
.../plugins/elasticsearch/AuditEventListener.groovy: 24: unable to resolve class org.hibernate.event.PostInsertEvent
@ line 24, column 1.   import org.hibernate.event.PostInsertEvent

These error messages are a little bit misleading. The real underlying cause is, that with Hibernate 4 loading/registration of the listeners changed.

5. Modify ElasticsearchGrailsPlugin.groovy

In a first naive approach, I disabled the explicit loading of the listeners. The way listeners will be registered has completely changed in Hibernate4 (e.g. Hibernate 4 Integrator-Pattern).

// ElasticsearchGrailsPlugin.groovy
...
// import org.grails.plugins.elasticsearch.AuditEventListener
...
    // the plugin version
    def version = "0.90.5"
    // the version or versions of Grails the plugin is designed for
    def grailsVersion = "2.3.0 > *"
    // the other plugins this plugin depends on
    def dependsOn = [
            domainClass: "1.0 > *",
           hibernate4: "4.0 > *"
    ]
...
        // auditListener(AuditEventListener) {
        //    elasticSearchContextHolder = ref("elasticSearchContextHolder")
        // }
       /*        if (!esConfig.disableAutoIndex) {
            // do not install audit listener if auto-indexing is disabled.
            hibernateEventListeners(HibernateEventListeners) {
               listenerMap = [
                       'post-delete': auditListener,
                       'post-collection-update': auditListener,
//                        'save-update': auditListener,
                       'post-update': auditListener,
                        'post-insert': auditListener,
                       'flush': auditListener
              ]
            }
       }
       */

At this stage, you might want to delete AuditEventListener.groovy, it is currently not longer needed.

Later on, we will need a different approach for loading the listeners (e.g. Hibernate 4 Intergrator-Pattern) to get the auto indexing working. How to do that, is briefly described here.

6. Rerun the Test SearchableClassMappingTest

Now I got an error:

| Error Fatal error running tests: org/junit/internal/AssumptionViolatedException (NOTE: Stack trace has been filtered. Use --verbose to see entire trace.)
java.lang.NoClassDefFoundError: org/junit/internal/AssumptionViolatedException
| Error Error executing script TestApp: org/junit/internal/AssumptionViolatedException

What the hell is this? Junit is in the classpath!
Ok,

  • delete the subdirectories of “/target”
  • run refresh-dependencies
  • run clean
  • rerun the test

and now the test passes.

Test1

7. Run the Integation Test ElasticSearchServiceSpec

So far so good, let’s proceed with the integration test ElasticSearchServiceSpec:
But surprisingly, it ends with a strange error message:

Error starting Grails: nulljava.lang.ExceptionInInitializerError
	at org.codehaus.groovy.runtime.InvokerHelper.(InvokerHelper.java:62)
	at groovy.lang.GroovyObjectSupport.(GroovyObjectSupport.java:32)
	at groovy.lang.Closure.(Closure.java:219)
	at groovy.lang.Closure.(Closure.java:236)
	at groovy.lang.Closure$1.(Closure.java:203)
	at groovy.lang.Closure.(Closure.java:203)
	at org.codehaus.groovy.grails.cli.GrailsScriptRunner.(GrailsScriptRunner.java:76)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:601)
	at org.codehaus.groovy.grails.cli.support.GrailsStarter.rootLoader(GrailsStarter.java:234)
	at org.codehaus.groovy.grails.cli.support.GrailsStarter.main(GrailsStarter.java:262)
Caused by: groovy.lang.GroovyRuntimeException: Conflicting module versions. Module [groovy-all is loaded in version 2.0.7 and you are trying to load version 2.1.6

A module conflict?
After hours of searching in the internet, I found a note, that a “Run -> Run configurations -> Run” in GGTS might help. Done so, magically the module conflict disappeared.
Sometimes I am really not a Grails lover, especially when too much magic happens.

8. Modify IndexRequestQueue.java and ThreadWithSession.groovy

// IndexRequestQueue.java
...
//import org.springframework.orm.hibernate3.SessionFactoryUtils;
import org.springframework.orm.hibernate4.SessionFactoryUtils;
...
    if (isNewSession) {
       persistenceInterceptor = createInterceptor();
       //session = SessionFactoryUtils.getSession(sessionFactory, true); // for Hibernate 3
       session = sessionFactory.getCurrentSession(); // for Hibernate 4
        }

and

// ThreadWithSession.groovy
...
// import org.springframework.orm.hibernate3.SessionFactoryUtils
// import org.springframework.orm.hibernate3.SessionHolder
import org.springframework.orm.hibernate4.SessionFactoryUtils
import org.springframework.orm.hibernate4.SessionHolder
...
  if (inStorage != null) {
      ((SessionHolder) inStorage).session.flush()
      return false
    } else {
      //Session session = SessionFactoryUtils.getSession(sessionFactory, true) // for Hibernate 3
      Session session = sessionFactory.getCurrentSession() // for Hibernate 4, to be tested!! did we get the transactional session?
      session.setFlushMode(FlushMode.AUTO)
      TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session))
     return true
    }

The next error message i got was:

| Running 1 integration test... 1 of 1
| Failure:  Index a domain object(test.ElasticSearchServiceSpec)
|  java.lang.IllegalStateException: Cannot activate transaction synchronization - already active

This has nothing to do with ElasticSearch, but with Spock and Grails.
Therefore, modify ElasticSearchServiceSpec.groovy:

// ElasticSearchServiceSpec
import grails.test.spock.IntegrationSpec
//import grails.plugin.spock.IntegrationSpec

The Result

Now, do a grails clean and rerun ElasticSearchServiceSpec.
Test2

If you want to crosscheck the embedded ElasticSearch, you can do this by calling:
http://localhost:9200

and its response:

{
"ok" : true,
"status" : 200,
"name" : "Copperhead",
"version" : {
"number" : "0.90.5",
"build_hash" : "c8714e8e0620b62638f660f6144831792b9dedee",
"build_timestamp" : "2013-09-17T12:50:20Z",
"build_snapshot" : false,
"lucene_version" : "4.4"
},
"tagline" : "You Know, for Search"
}

As you see, ElasticSearch is running locally in embedded mode, you can call it and the integration test finished successfully.
If you want to explore your ElasticSearch database, have a look at the Sense plugin for Chrome.

Summary

As shown above, it was possible to modify the Grails ElasticSearch plugin for use with ElasticSearch Ver. 0.90.5,  Grails 2.3.0 and Hibernate4.x.
At least, the plugin’s tests run successfully.

Next steps are the implementation of the event listener registrations, so auto indexing will be available again.

Hope this was useful for somebody somewhere. 🙂
Johannes

Advertisements
This entry was posted in Development, Grails and tagged , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s