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:
- builder.blog('title':'The Depressed') {
means that a node named 'blog' is created and then an attribute named title is defined. - 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 :)

Oh, the free beer proposal is still valid ... :)
Just a thought :)