Fork me on GitHub

25 Feb 2010

Using a custom data binder with Grails domain objects

Yesterday I read a post by Stefan Armbruster on how to register a custom data binder to lookup Grails domain objects by any arbitrary property. I wanted to go a little further so that I could not only bind existing domain instances but create new ones as well.

For example, let's say I have Artist and Album domain classes where Artist hasMany Albums and Album belongsTo Artist. Artist has a name property that is unique. On my create album page I want to be able to type in the artist name and have the save action in the controller automatically lookup an existing Artist instance or create a new one if it doesn't exist. Doing this I don't want to have to add or change anything in the save action itself - I could theoretically use dynamic scaffolding.

Adapting Stefan's PropertyEditor implementation I created this:
The crucial change is the if (!value) block which creates the new instance and populates the relevant property.

To make everything work I just need to:
  1. Add the PropertyEditorRegistrar and place it in resources.groovy as per Stefan's post.
  2. Have a text input or autocompleter with the name "artist" in my create album form.
  3. Add artist cascade: "save-update" to the mapping block in Album so that when the Album is saved the new Album will get saved as well.

23 Feb 2010

Full page caching in Grails with Ehcache-Web

I'm on the verge of releasing a new version of the Springcache plugin that will feature a pretty cool new annotation-driven page fragment caching feature based on Ehcache-web. However one of the things that came up during discussions on the mailing list was full page caching. I mentioned that it was pretty straightforward and promised to blog about how to do it, so…

Install Ehcache-web

Add the following to your dependencies in BuildConfig.groovy:
runtime("net.sf.ehcache:ehcache-web:2.0.0") {
 excludes "ehcache-core"
}

Install Grails templates

Run: grails install-templates

Set up a page caching filter

Edit src/templates/war/web.xml and add this to the filters section:
<filter>
 <filter-name>MyPageCachingFilter</filter-name>
 <filter-class>net.sf.ehcache.constructs.web.filter.SimplePageCachingFilter</filter-class>
 <init-param>
  <param-name>cacheName</param-name>
  <param-value>myPageCache</param-value>
 </init-param>
</filter>
And this as the first entry in the filter-mappings section:
<filter-mapping>
 <filter-name>MyPageCachingFilter</filter-name>
 <url-pattern>/controller/action/*</url-pattern>
 <dispatcher>REQUEST</dispatcher>
</filter-mapping>

Configure the cache

Unfortunately you can't configure the cache in grails-app/conf/spring/resources.groovy using EhCacheFactoryBean instances as the servlet filter initialises before the Spring context. The cache definitions need to be added to a ehcache.xml which would normally be placed in grails-app/conf. Here is an example that works for the filter definitions above:
<ehcache>

    <diskStore path="java.io.tmpdir"/>

    <defaultCache
            maxElementsInMemory="10"
            eternal="false"
            timeToIdleSeconds="5"
            timeToLiveSeconds="10"
            overflowToDisk="true"
            />

    <cache name="myPageCache"
           maxElementsInMemory="10"
           eternal="false"
           timeToIdleSeconds="10000"
           timeToLiveSeconds="10000"
           overflowToDisk="true">
    </cache>

</ehcache>

You will need to add another filter and filter-mapping and potentially another cache for each URL pattern you want to cache separately.

Full page caching is fine for simple pages but isn't as flexible as fragment caching. For example, any authenticated state (such as a "Welcome back xxx" label) on the page would get cached across all users. Because Grails uses Sitemesh and has the <g:include> tag for including output from other controllers a fragment caching solution is a good fit. The 1.2 release of the Springcache plugin will add fragment caching functionality. However, I can imagine using full-page caching like this for things like RSS feeds.