Latest Posts

Archives [+]

Categories [+]

Authors [+]

Groovy JCR

I was rather impressed by Michaels samples on Scala and JCR. Myself, I am currently fiddling around with a Groovy/Grails project and a JCR backend. There has been some work within Grails to have a JCR implemented as a persistence possibility alternative to Hibernate/RDBM. While this effort (which is currently stalled) is surely interesting in itself (a Grails CMS backed by a JCR might really be a killer app!), there is another fascinating technique in the Groovy ecosystem: Builders

Builders are the Groovy implementation of the same named pattern. If you don't remember this pattern, lets put it simple: Builders allow you to create stuff in a declarative manner. The Groovy incarnation is especially well suited for tree like structures. One of the most prominent implementation is the SwingBuilder (to learn more about Builders follow the link, the example there is strikingly simple).

So, JCR... trees... builders... you see where it goes: it is easy to create a JCR tree builder with Groovy. But first, let's have a look at a test script:

import javax.jcr.Repository
import javax.jcr.Session
import org.apache.jackrabbit.core.TransientRepository
import javax.jcr.SimpleCredentials;
import javax.jcr.Node

class JCRBuilderTests extends GroovyTestCase {
   static Session session

   static {
     Repository repository = new 
       TransientRepository()
     //Session session = repository.login()
     session = repository.login(
               new SimpleCredentials("username",
                "password".toCharArray()))
       }

       void testBasicStuff() {
       JCRBuilder builder = new JCRBuilder()
       builder.session = session
       try {
           def node = 
	      builder.blog('title':'The Depressed') {
               entry() {
                 first('title': 'Monday again') {
                   attachment('nt:file') {
                     'jcr:content'('nt:resource',
                     ['jcr:mimeType':'text/plain',
                     'jcr:data':
		         'Monday morning ...again',
                     'jcr:lastModified':
		         Calendar.instance])
                    }
                 }
             }
           }
           println "node:  ${node.name}"
           assert session.rootNode.getNode('blog').
               getProperty('title').string==
	          'The Depressed'
           assert
             session.rootNode.
             getNode('blog/entry/first/attachment').
             primaryNodeType.name == 'nt:file'
       }finally {
           session.rootNode.getNode('blog').remove()
           session.save()

       }
    }
}

The testBasicStuff method shows how a builder is used. Two things need to be mentioned:

  1. builder.blog('title':'The Depressed') {
    means that a node named 'blog' is created and then an attribute named title is defined.
  2. attachment('nt:file') { ...
    means that a node with the primary type nt:file is defined

Now to the meat. The builder itself looks like this:


import javax.jcr.Session
import javax.jcr.Node


class JCRBuilder extends BuilderSupport {
       Session session
       Node parentNode

   public JCRBuilder() {
       super()
   }

       //trigger: foo()
       //return: Node
       def createNode(name) {
               checkParentNode()
               parentNode = parentNode.
	          addNode(name)
               return parentNode
       }

       //trigger: foo('x')
       //return: Object
       // creates node with value=nodetype
       def createNode(name, value) {
               checkParentNode()
               parentNode = parentNode.
	          addNode(name, (String)value)
               return parentNode
       }

       //trigger: foo(a:1)
       //return: Object
       def createNode(name, Map attributes) {

       Node node = createNode(name)
               attributes.each { key, value ->
                       node.setProperty(key, value)
               }
               return node
       }

       //trigger: foo(a:1, 'x')
       //return: Object
       def createNode(name, Map attributes, value) {
               checkParentNode()
       Node node = createNode(name, value)
       attributes.each { key, myvalue ->
           node.setProperty(key, myvalue)
       }
               return null;
       }

       //trigger: createNode(...) finished
       def void setParent(parent, child) {

       }

       //trigger: recursive closure call finished
       void nodeCompleted(parent, node) {
       session.save()
       parentNode = parentNode.getParent()
       }

       private checkParentNode() {
               if(parentNode==null) parentNode = 
	          session.getRootNode()
       }
}

This is a most basic form of a possible builder, but it hopefully clarifies the whole concept nevertheless. Groovy builders are a superb way to create hierarchical object trees, hence a "natural one" to create JCR tree fragments. Of course there might be better ways to create a JCR builder, with more of a DSL-like approach. The example above is not perfect, and one major issue remains: after executing the test for the second time, an exception "java.lang.IllegalStateException: Index already present" is thrown. A free beer to the one who finds out this one :)

 
(optional)
5 comments
Really awesome :-) Would love to include something like this in JCR plugin wants i get a change to work on it again.
0 Replies » Reply
Ever since I've come across JCR a long while ago, I also believed Groovy had some great strengths that could be leveraged to access / interact with JCR repositories. It's a great example of the usage of builders! Also, even for traversing node hierarchies, using GPath-like notation will really make it a breathe to do things like blog.entry.@title, or blog.entry.first.attachment and it returns your file, etc. Groovy is really ideal for that kind of use case. Congratulations for this neat little builder!
0 Replies » Reply
Well, thx. Please note that this is all amateur work, i.e. reading you guys' books, beeing interessted in JCR, seeing tree structures everywhere, thinking "this should work" and having fun trying out stuff.


Oh, the free beer proposal is still valid ... :)
0 Replies » Reply
Does the Index already present go away if you call session.logout() at the end instead of just shutting down?

Just a thought :)
0 Replies » Reply
yeah! free beer for you, if you are basel based, we definitely should meet :)
0 Replies » Reply