How-To: Grails, GORM and SimpleDB

I went to build a new Grails-based app today, and I wanted to use SimpleDB as a backend (the app is an internal-use administration app, to configure a suite of AWS-deployed apps). So I went looking on how to use GORM with SimpleDB. This turned out to be a non-trivial task, so I thought I’d share the process with everyone.

Step 1: Install Grails.

I assume you know how to do that; if not, there’s a good installation guide over at the Grails site.

Step 2: Create an application

You do this using grails create-app

grails create-app helloworld

(All future commands will be run from within the newly-created application directory)

Step 3: Remove the Hibernate plugin.

After all, this is going to use SimpleDB as the backend, so we don’t need Hibernate. To do that, we edit the file grails-app/conf/BuildConfig.groovy

...
grails.project.dependency.resolution = {
...
    plugins {
...        // Don't need caching
//        compile ':cache:1.1.1'

        // plugins needed at runtime but not for compilation
        // Don't need Hibernate, as we'll use SimpleDB
//        runtime ":hibernate:3.6.10.7" // or ":hibernate4:4.1.11.6"
...
    }
}

I’ve also disabled the caching plugin. This is fine for me, as I’m building an app for internal use and I don’t want caching. If you do want caching, however, you’ll need to track down some dependencies – the cache plugin requires some dependencies that were being provided by hibernate.

Step 4: Enable the SimpleDB GORM plugin.

So it turns out there’s an official SimpleDB plugin for GORM. All you have to do is drop a reference to it in your grails-app/conf/BuildConfig.groovy file and you’re done! That was easy. Or was it?

plugins {
  ...
  runtime ":simpledb:0.5"
}
$ grails run-app
| Error Resolve error obtaining dependencies: The following artifacts could not be resolved: org.grails:grails-datastore-web:jar:1.0.0.RELEASE, org.grails:grails-datastore-gorm-plugin-support:jar:1.0.0.RELEASE: Could not find artifact org.grails:grails-datastore-web:jar:1.0.0.RELEASE in grailsCentral (http://repo.grails.org/grails/plugins) (Use --stacktrace to see the full trace)

Bummer – this version of the SimpleDB plugin needs an older version of the grails-datastore-web library – one that isn’t published on Grails Central or Maven Central. Well, that’s easily fixed, by editing the grails-app/conf/BuildConfig.groovy file again:

    dependencies {
...
        runtime 'org.grails:grails-datastore-web:jar:2.0.7.RELEASE'      
        runtime 'org.grails:grails-datastore-gorm-plugin-support:jar:2.0.7.RELEASE'
    }

Step 5: Creating your first Domain Class

Now, when you run the app, you get this error:

$ grails run-app
| Error 2014-01-28 23:40:54,441 [localhost-startStop-1] ERROR context.GrailsContextLoader  - Error initializing the application: Cannot invoke method listDomains() on null object
Message: Cannot invoke method listDomains() on null object
    Line | Method
->>   98 | doCall    in org.grails.datastore.gorm.simpledb.plugin.support.SimpleDBApplicationContextConfigurer$_closure2
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|     51 | configure in org.grails.datastore.gorm.simpledb.plugin.support.SimpleDBApplicationContextConfigurer
|     52 | doCall .  in SimpledbGrailsPlugin$_closure2
|    262 | run       in java.util.concurrent.FutureTask
|   1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
|    615 | run       in java.util.concurrent.ThreadPoolExecutor$Worker
^    744 | run . . . in java.lang.Thread

This isn’t that bad, though – if you have access to the source code, you can find out that this is because the SimpleDB template isn’t setup. Further reading of the code reveals that the setup is only done if there is a domain object. (Guess that’s a bug that was missed – but yay for open source for letting me figure that out). So we need to make a domain object – which we do by calling grails create-domain-class Book.

So we’re good to go, right? No.

Step 6: Updating the SimpleDB plugin

Unfortunately, you now get this error:

| Error 2014-01-28 23:47:52,786 [localhost-startStop-1] ERROR context.GrailsContextLoader  - Error initializing the application: Error creating bean with name 'transactionManagerPostProcessor': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'simpledbTransactionManager': Cannot resolve reference to bean 'simpledbDatastore' while setting bean property 'datastore'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'simpledbDatastore': Cannot resolve reference to bean 'simpledbMappingContext' while setting bean property 'mappingContext'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'simpledbMappingContext': FactoryBean threw exception on object creation; nested exception is java.lang.NoSuchMethodError: org.grails.datastore.mapping.model.MappingFactory.createMappedForm(Lorg/grails/datastore/mapping/model/PersistentEntity;)Ljava/lang/Object;
Message: Error creating bean with name 'transactionManagerPostProcessor': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'simpledbTransactionManager': Cannot resolve reference to bean 'simpledbDatastore' while setting bean property 'datastore'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'simpledbDatastore': Cannot resolve reference to bean 'simpledbMappingContext' while setting bean property 'mappingContext'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'simpledbMappingContext': FactoryBean threw exception on object creation; nested exception is java.lang.NoSuchMethodError: org.grails.datastore.mapping.model.MappingFactory.createMappedForm(Lorg/grails/datastore/mapping/model/PersistentEntity;)Ljava/lang/Object;
    Line | Method
->>  262 | run       in java.util.concurrent.FutureTask
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|   1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
|    615 | run . . . in java.util.concurrent.ThreadPoolExecutor$Worker
^    744 | run       in java.lang.Thread
Caused by BeanCreationException: Error creating bean with name 'simpledbTransactionManager': Cannot resolve reference to bean 'simpledbDatastore' while setting bean property 'datastore'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'simpledbDatastore': Cannot resolve reference to bean 'simpledbMappingContext' while setting bean property 'mappingContext'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'simpledbMappingContext': FactoryBean threw exception on object creation; nested exception is java.lang.NoSuchMethodError: org.grails.datastore.mapping.model.MappingFactory.createMappedForm(Lorg/grails/datastore/mapping/model/PersistentEntity;)Ljava/lang/Object;
->>  262 | run       in java.util.concurrent.FutureTask
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|   1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
|    615 | run . . . in java.util.concurrent.ThreadPoolExecutor$Worker
^    744 | run       in java.lang.Thread
Caused by BeanCreationException: Error creating bean with name 'simpledbDatastore': Cannot resolve reference to bean 'simpledbMappingContext' while setting bean property 'mappingContext'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'simpledbMappingContext': FactoryBean threw exception on object creation; nested exception is java.lang.NoSuchMethodError: org.grails.datastore.mapping.model.MappingFactory.createMappedForm(Lorg/grails/datastore/mapping/model/PersistentEntity;)Ljava/lang/Object;
->>  262 | run       in java.util.concurrent.FutureTask
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|   1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
|    615 | run . . . in java.util.concurrent.ThreadPoolExecutor$Worker
^    744 | run       in java.lang.Thread
Caused by BeanCreationException: Error creating bean with name 'simpledbMappingContext': FactoryBean threw exception on object creation; nested exception is java.lang.NoSuchMethodError: org.grails.datastore.mapping.model.MappingFactory.createMappedForm(Lorg/grails/datastore/mapping/model/PersistentEntity;)Ljava/lang/Object;
->>  262 | run       in java.util.concurrent.FutureTask
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|   1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
|    615 | run . . . in java.util.concurrent.ThreadPoolExecutor$Worker
^    744 | run       in java.lang.Thread
Caused by NoSuchMethodError: org.grails.datastore.mapping.model.MappingFactory.createMappedForm(Lorg/grails/datastore/mapping/model/PersistentEntity;)Ljava/lang/Object;
->>   52 | createPersistentEntity in org.grails.datastore.mapping.simpledb.config.SimpleDBMappingContext
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|    213 | addPersistentEntityInternal in org.grails.datastore.mapping.model.AbstractMappingContext
|    206 | addPersistentEntity in     ''
|     63 | getObject in org.grails.datastore.gorm.bean.factory.AbstractMappingContextFactoryBean
|    262 | run . . . in java.util.concurrent.FutureTask
|   1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
|    615 | run . . . in java.util.concurrent.ThreadPoolExecutor$Worker
^    744 | run       in java.lang.Thread
| Error Forked Grails VM exited with error

It turns out that the published version of the SimpleDB plugin is too old to work with the newer GORM code. This isn’t surprising, but it’s annoying. To solve this – at least until a new version gets published – we need to build our own. Good thing that source code was available!

# Clone the Grails Data Mapping repository
$ git clone https://github.com/grails/grails-data-mapping.git
Cloning into 'grails-data-mapping'...
remote: Counting objects: 51046, done.
remote: Compressing objects: 100% (20296/20296), done.
remote: Total 51046 (delta 13750), reused 51046 (delta 13750)
Receiving objects: 100% (51046/51046), 15.74 MiB | 308.00 KiB/s, done.
Resolving deltas: 100% (13750/13750), done.
Checking connectivity... done.

$ cd grails-data-mapping/
# Switch to the latest stable tag - v2.0.7 at this time.
$ git checkout v2.0.7
Note: checking out 'v2.0.7'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at 3e91b6d... change version to 2.0.7 release
23:56:53-robertdw~/util/grails-data-mapping ((v2.0.7))

# Build the project.
$ export GRADLE_OPTS="-Xmx512m -XX:MaxPermSize=512m" # you need more permgen memory than the default
$ ./gradlew install
... lots of output. Takes a bit of time, as it pulls down the internet to get the dependencies it needs...
... Note that you will get errors from the hibernate plugin. That doesn't matter for what we're doing here...

# Copy the two libraries that we need to the lib directory of our project
$ cp grails-datastore-simpledb/build/libs/grails-datastore-simpledb-0.6.BUILD-SNAPSHOT.jar ~/tmp/helloworld/lib/
$ cp grails-datastore-gorm-simpledb/build/libs/grails-datastore-gorm-simpledb-0.6.BUILD-SNAPSHOT.jar ~/tmp/helloworld/lib/

# Go into the grails-plugin directory and build the SimpleDB plugin
$ cd grails-plugins/simpledb/
# You need to upgrade the plugin, unless you're using an older version of Grails. Just one of the quirks of Grails
$ grails upgrade
$ grails package-plugin --binary
$ cp target/grails-plugin-simpledb-0.6.BUILD-SNAPSHOT.jar ~/tmp/helloworld/lib/
$ cd ~/tmp/helloworld

Remove the reference to the older version of the SimpleDB plugin from the BuildConfig.groovy file, and it’s time to move on to the next error!

Step 7: Setup AWS Configuration

Which is this one:

$ grails run-app
| Uninstalled plugin [simpledb]
| Running Grails application
objc[69323]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.7.0_45.jdk/Contents/Home/bin/java and /Library/Java/JavaVirtualMachines/jdk1.7.0_45.jdk/Contents/Home/jre/lib/libinstrument.dylib. One of the two will be used. Which one is undefined.
| Error 2014-01-29 00:14:22,617 [localhost-startStop-1] ERROR context.GrailsContextLoader  - Error initializing the application: Error creating bean with name 'transactionManagerPostProcessor': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'simpledbTransactionManager': Cannot resolve reference to bean 'simpledbDatastore' while setting bean property 'datastore'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'simpledbDatastore': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalArgumentException: Please provide accessKey and secretKey
Message: Error creating bean with name 'transactionManagerPostProcessor': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'simpledbTransactionManager': Cannot resolve reference to bean 'simpledbDatastore' while setting bean property 'datastore'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'simpledbDatastore': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalArgumentException: Please provide accessKey and secretKey
    Line | Method
->>  262 | run       in java.util.concurrent.FutureTask
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|   1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
|    615 | run . . . in java.util.concurrent.ThreadPoolExecutor$Worker
^    744 | run       in java.lang.Thread
Caused by BeanCreationException: Error creating bean with name 'simpledbTransactionManager': Cannot resolve reference to bean 'simpledbDatastore' while setting bean property 'datastore'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'simpledbDatastore': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalArgumentException: Please provide accessKey and secretKey
->>  262 | run       in java.util.concurrent.FutureTask
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|   1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
|    615 | run . . . in java.util.concurrent.ThreadPoolExecutor$Worker
^    744 | run       in java.lang.Thread
Caused by BeanCreationException: Error creating bean with name 'simpledbDatastore': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalArgumentException: Please provide accessKey and secretKey
->>  262 | run       in java.util.concurrent.FutureTask
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|   1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
|    615 | run . . . in java.util.concurrent.ThreadPoolExecutor$Worker
^    744 | run       in java.lang.Thread
Caused by IllegalArgumentException: Please provide accessKey and secretKey
->>   65 | <init>    in org.grails.datastore.mapping.simpledb.util.SimpleDBTemplateImpl
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|    138 | createSimpleDBTemplate in org.grails.datastore.mapping.simpledb.SimpleDBDatastore
|    123 | afterPropertiesSet in     ''
|     49 | getObject in org.grails.datastore.gorm.simpledb.bean.factory.SimpleDBDatastoreFactoryBean
|    262 | run . . . in java.util.concurrent.FutureTask
|   1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
|    615 | run . . . in java.util.concurrent.ThreadPoolExecutor$Worker
^    744 | run       in java.lang.Thread
| Error Forked Grails VM exited with error

This error is simple enough – we need to provide the AWS identifiers. Unfortunately, the plugin doesn’t follow the convention of getting the environment variables AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. Still, it’s not hard to configure that. Off to the grails-app/conf/Config.groovy file we go:

environments {
    development {
        grails {
            simpledb {
                accessKey = System.getenv('AWS_ACCESS_KEY_ID')
                secretKey = System.getenv('AWS_SECRET_ACCESS_KEY')
                domainNamePrefix = "${System.properties.getProperty('user.name')}_bookworld_" //this setting is optional, used when the same AWS account is shared between more than one environment
                dbCreate = 'drop-create' // one of 'drop, 'create', 'drop-create'
            }
        }
        
    }
    production {
        grails {
            simpledb {
                accessKey = System.getenv('AWS_ACCESS_KEY_ID')
                secretKey = System.getenv('AWS_SECRET_ACCESS_KEY')
                domainNamePrefix = 'PROD_' //this setting is optional, used when the same AWS account is shared between more than one environment
                dbCreate = 'create' // one of 'drop, 'create', 'drop-create'
            }
        }
    }
}


We're also going to need to have the <a href="http://aws.amazon.com/sdkforjava/>AWS SDK for Java</a> on the class path. To do that, I've chosen to use the <a href="http://grails.org/plugin/aws-sdk">Grails AWS-SDK plugin</a>, which gives a nice Groovy facade to the AWS classes. Back to the <code>BuildConfig.groovy</code> file.


    plugins {
...
	runtime ':aws-sdk:1.6.12'
    }

And now the application starts!

Step 8: Getting data in

So now to get some data in. Let's modify the previously-created Book class - we'll give it a title and an author. Also, because the default ID generator creates UUIDs, we need to declare the type of the id field to be a string. That gives us a class like this:

package helloworld

class Book {

    static constraints = {
    }
    
    String id
    String title
    String author
}

And then we can add some data into the Bootstrap.groovy file

import helloworld.Book

class BootStrap {

    def init = { servletContext ->
        if (!Book.count()) {
            println 'Creating test data'
            new Book(title: 'Alice in Wonderland', author: 'Lewis Carroll').save(failOnError: true)
            new Book(title: 'The Wizard of Oz', author: 'Frank L. Baum').save(failOnError: true)
        }
    }
    def destroy = {
    }
}

Quickly restarting the server - and creating a REST-style controller using grails generate-controller helloworld.Book, we can then prove that the data is present by looking at the controller page - in the JSON format.

$ curl http://localhost:8080/helloworld/book/index.json
[{"class":"helloworld.Book","id":"ef2cd65b-3fb6-435f-9fe0-ad0bdf0be3c3",
  "author":"Lewis Carroll","title":"Alice in Wonderland"},
 {"class":"helloworld.Book","id":"88da9cdd-1dce-45ff-a9a5-24a3cb84aec8",
  "author":"Frank L. Baum","title":"The Wizard of Oz"}]

And that is Grails, using SimpleDB via GORM.

Advertisements

Author: Robert Watkins

My name is Robert Watkins. I am a software developer and have been for over 18 years now. I currently work for people, but my opinions here are in no way endorsed by them (which is cool; their opinions aren’t endorsed by me either). My main professional interests are in Java development, using Agile methods, with a historical focus on building web based applications. I’m also a Mac-fan and love my iPhone, which I’m currently learning how to code for. I live and work in Brisbane, Australia, but I grew up in the Northern Territory, and still find Brisbane too cold (after 16 years here). I’m married, with two children and one cat. My politics are socialist in tendency, my religious affiliation is atheist (aka “none of the above”), my attitude is condescending and my moral standing is lying down.

One thought on “How-To: Grails, GORM and SimpleDB”

  1. After all that, though, I ended up not using it, as it doesn’t support multi-value attributes (aka ‘sets’).

    Instead, I wrote a thin service that uses the AWS SDK plugin I mentioned at the end.

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