Latest Posts

Archives [+]

JCR with Scala

Having seen Scala being mentioned in a recent comment I thought I'd give it a try with JCR.

First I installed the Scala Plugin for Eclipse. Alternatively I could have also used Scala's command line tools. Then I created a new Scala project from within the Eclipse IDE and put the Jackrabbit jars on the classpath.

Now I set out to implement Hop 3 from the article First Hops with Jackrabbit. I therefore downloaded the test.xml file and put it into a subfolder of my Scala project called data. Translating the java code to Scala is straight forward. However, I decided that I wanted to benefit from some of Scala's goodies which required a little more work. Essentially I wanted to be able to use for comprehensions to iterate the child nodes and properties of a given node. Let's take a look at the code first. I will comment on the crucial parts afterward.

package com.day.scalademo;

import javax.jcr.{Node, Property, SimpleCredentials, 
  ImportUUIDBehavior}
import org.apache.jackrabbit.core.TransientRepository
import java.io.FileInputStream

object Hop3 {

  def main(args: Array[String]) {
    val repository = new TransientRepository
    val session = repository.login(
      new SimpleCredentials("username", 
      "password".toCharArray));
        
    try {
      val root = session.getRootNode

      if (!root.hasNode("importxml")) {
        print("Importing xml... ")

        val node = root.addNode("importxml", 
          "nt:unstructured")
        val xml = new 
          FileInputStream("data/test.xml")
        session.importXML("/importxml", xml, 
          ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW)
        xml.close

        session.save
        println("done.")
      }

      dump(root.getNode("importxml"))
    } 
    finally {
      session.logout
    }    
  }
        
  private def dump(node: Node) {
    import Extensions._
    println(node.getPath)

    for (property <- node.properties) {
      if (property.getDefinition.isMultiple)      
        for(value <- property.getValues)
          println(property.getPath + " = " + value)
      else 
        println(property.getPath + " = " +
          property.getString)
    }
        
    for (child <- node.childNodes)
      dump(child)
  }
}

object Extensions {
  implicit def extendNode(node: Node) =
    new NodeExtender(node)    

  class NodeExtender(node: Node) {

    def childNodes = new Iterator[Node] {
      val it = node.getNodes
      def hasNext = it.hasNext
      def next = it.nextNode
    }
    
    def properties = new Iterator[Property] {
      val it = node.getProperties
      def hasNext = it.hasNext
      def next = it.nextProperty
    }
  }
}

The main method is a straight forward translation from java. It acquires a Repository instance, imports the content of test.xml if it hasn't been imported before, and finally outputs the subtree generated from the import by a call to the dump method.

The dump method takes a Node argument and recursively outputs its properties and its child nodes. There are two noteworthy things here: the import directive and the for loops. This form of import basically imports all definitions from the Extensions object. The for loops iterate over the child nodes and properties of a Node. But note, Node objects neither have a childNodes nor a properties member but nevertheless exactly these are accessed here. Why didn't I use the getNodes and getProperties members? The reason is that in order to be usable in a for loop an object must implement scala.Iterator[T] for some type T. Both methods getNodes and getProperties return an iterator of the wrong type.

I employed Scala's implicit parameters mechanism* to emulate something like C#'s extension methods. Definitions in scope which are marked with the implicit keyword are candidates for implicit type conversion. In our case we import everything from the Extensions object. So extendNode is in scope at the location of the for loops. Since the Scala compiler cannot find the properties and childNodes members it employs extendNode passing the current node as argument. extendNode returns a NodeExtender which has properties and childNodes methods which return a scala.Iterator[Property] and a scala.Iterator[Node], respectively. Both iterators simply delegate to the iterators returned by the getProperties and the getNodes methods of the underlying node.

Using Scala for JCR programming seems very promising. It adds little to no overhead to accessing the repository from java. Moreover it possesses highly sophisticated features which lead to elegant solutions for many common programming tasks. I will try to elaborate more on using JCR from Scala soon.

(*) <rant>This is in some ways similar to implicit type conversion in C++ but done right (i.e. more powerful and safer).<rant/>

 

COMMENTS

  • By Bertrand Delacretaz - 3:59 PM on Feb 26, 2009   Reply
    Note to readers of this page: Sling (http://incubator.apache.org/sling) now includes a Scala engine, which allows Scala scripts to be used for processing HTTP requests.