Latest Posts

Archives [+]

Categories [+]

Authors [+]

JCR with Scala: Adding Properties in ASCII Art

In two earlier posts (1, 2) I showed how to define a Scala operator in a way such that building a JCR tree structure results in code which - when properly indented - resembles an ASCII art drawing of the tree. Up to now this only works for nodes. In this post I show how to define an operator for adding properties. A full working example is available from my personal blog.

Basically adding properties is no different to adding nodes: just define an operator and overload it for every type of property. At this point it is tempting to not even define a new operator but to further overload the ¦- (add node) operator. But there is a problem. Consider the signature for adding a string typed property:

def ¦-(name: String, value: String) 

This happens to be exactly the same as the signature for adding a node of a given type

def ¦-(relPath: String, tyqe: String)

We could fix this by adding a dummy parameter for disambiguation. But I consider this a hack rather than a solution. Furthermore overloading ¦- for adding properties contradicts the principle of separation of concerns.

Using ¦= as operator for adding properties to a node, the following definitions could be added to NodeExtender effectively overloading ¦= for every property type.

def ¦=(name: String, value: long) =                        node.setProperty(name, value)def ¦=(name: String, value: String) =                        node.setProperty(name, value)...

With these definitions in place adding properties works as expected. The following code adds a title and a length property to the movie node.

movie ¦= ("title", "Night on Earth")movie ¦= ("length", 123L)

However, how should we handle multi-value properties? Of course, we could add another overload of ¦= like this:

def ¦=(name: String, value: Array[Value]) =                       node.setProperty(name, value)

This is a simple solution and works well, but it doesn't really seem to fit into the existing framework: the array of Values has to be constructed in advance before it can be passed to the ¦= operator. It would be much nicer if we could use Scala's List constructors and write

movie ¦= ("ratings", 9L::8L::5L::Nil) movie ¦= ("languages", "en"::"it"::"fi"::"fr"::Nil) 

instead.

So let's try to add corresponding overloads for ¦=

def ¦=(name: String, value: List[long]) = ...def ¦=(name: String, value: List[String]) = ......

This seems all too easy, but unfortunately it does not work. When compiling, Scala complains about the overloaded methods having the same type erasure:

double definition:method ¦=:(String,List[String]) andmethod ¦=:(String,List[long]) have same type after erasure.

What is going on? Scala - as Java - implements generics by erasure. In a nutshell this means that at compile time the compiler removes the type information from generic types. When such types are referenced, it automatically infers the correct type and adds a casts if necessary. Removing the type arguments from above methods leaves us with the same signature for both: the erasure of the type of the value argument is always the type List.

Of course we could chicken out of this dilemma by using a generic method for multi-valued properties like this:

def ¦=[T](name: String, value: List[T]) = ... 

An implementation would query the actual type of the list elements at runtime and then call the right overload of setProperty. However, such an approach is not type safe: if T is of a type for which there is no overload of setProperty, the implementation would have to throw an exception.

As it turns out, we can do better. There is a way to define the ¦= operator without abandoning type safety. Basically we want to define a method

def ¦=[T](name: String, value: T) = ...

applicable to every type T for which either an overload of the setProperty method exists or for which a setProperty method taking an array of Values of type T exists. We can achieve this by employing Scala's view bounds and implicits:

 

def ¦=[T <% Extensions.Val[T]](name: String,       value: T) =value.setOnNode(node, name, valueFactory)

This signature basically says that ¦= is applicable to every type T which is convertible to the type Extensions.Val[T]. All we have to do now is to provide conversion methods to Extensions.Val[T] for the right types of T.

object Extensions {   import javax.jcr.{Value, ValueFactory}  implicit def extendNode(node: Node) =     new NodeExtender(node)       abstract class Val[-T](value: T) {    def setOnNode(node: Node, name: String,      factory: ValueFactory)    def toValue(factory: ValueFactory): Value  }      implicit def longToVal(l: long) = new Val(l) {    def setOnNode(node: Node, name: String,      factory: ValueFactory) =       node.setProperty(name, factory.createValue(l))                def toValue(factory: ValueFactory) =       factory.createValue(l)  }  implicit def stringToVal(s: String) = new Val(s) {    def setOnNode(node: Node, name: String,      factory: ValueFactory) =       node.setProperty(name, factory.createValue(s))            def toValue(factory: ValueFactory) =        factory.createValue(s)  }      implicit def listToVal[T](seq: Seq[T])    (implicit toVal: T => Val[T]) = new Val(seq) {    def setOnNode(node: Node, name: String,      factory: ValueFactory) = {      val values = seq.map(toVal(_).toValue(factory))      .toArray        node.setProperty(name, values)    }            def toValue(factory: ValueFactory) =       error("not applicable")  }}

Each Extensions.Val[T] has a setOnNode method, which is eventually responsible for calling setProperty passing along a Value of type T. It uses the valueFactory supplied by the caller to construct this Value. The toValue method converts the individual elements of a multi-valued property to the corresponding Value objects. It is called once for each element of such a property from the setOnNode method of an Extensions.Val[Seq[T]] instance.

The implicit method listToValue is responsible for converting a sequence of Ts into an Extensions.Val[Seq[T]] instance. Note that the parameter toVal of listToValue is implicit itself. This parameter is a converter for the elements of the sequence. (i.e. we can convert a sequence if we have a converter for it's elements). The conversion of the list uses my implicit double dispatch pattern: the setOnNode method passes each element of the sequence (by means of map) to the converter toVal obtaining a Val object whose toValue method is used in turn to obtain a Value object. These Values are then collected into an array by toArray and finally passed to the setProperty method.

At first this technique might seem awfully convoluted. However, it turns out to be an instance of a more general pattern, which allows for effectively overcoming a limitation of the type system: overloading of methods with arguments having the same type erasure. While Java has the same limitation, there is no such solution in Java. For more details see Implicit double dispatch and Implicit double dispatch revisited on my personal blog.

Reality check: after finally having overcome all these difficulties, we are now in the position to add properties - both single and multi-valued - using ASCII-Art syntax:

n ¦- "movies" -+ { n: Node =>             n ¦- "cast" -+ { n: Node =>                          n ¦= ("Gena Rowlands",                                "Victoria Snelling")                           n ¦= ("Winona Ryder",                                 "Corky")                           n ¦= ("Roberto Benigni",                                 "Taxi Driver")                             }             n ¦= ("title", "Night on Earth")               n ¦= ("length", 123L)               n ¦= ("ratings", 9L::8L::5L::Nil)               n ¦= ("languages",              "en"::"it"::"fi"::"fr"::Nil)                }n ¦- "movies" -+ { n: Node =>             n ¦- "cast" -+ { n: Node =>                          n ¦= ("James Stewart",                                 "Det. Ferguson")                           n ¦= ("Tom Helmore",                                 "Gavin Elster")                           n ¦= ("Henry Jones",                                 "Coroner")                             }             n ¦= ("title", "Vertigo")               n ¦= ("length", 124L)               n ¦= ("ratings", 10L::8L::7L::Nil)               n ¦= ("languages", "en"::Nil)                  }
 
(optional)
No comments yet