As I described in this post I use Jukka Zittings JCRStoreBean to write emails into my content repository. In this bean an incoming mail with only one recipient is stored with a single-valued property named "to" whereas mails with multiple recipients are stored in a multi-valued property named "to". This might cause problems for the consuming application because the API calls to read a single-valued or a multi-valued properties are different.
Of course, this can easily be fixed in the JCRStoreBean, but this fact can also be used as an excuse to further explore scripting a JCR with JRuby. The aim is to convert the "to" property into a multi-valued property for all mails that I have already stored in my CRX repository.
If you have set up JRuby like in this previous post you need to have the additional jar crx-rmi-1.3.2.jar file on your classpath to access a CRX repository through RMI. Find it in your CRX installation here server\runtime\0\_crx\WEB-INF\lib.
Afterwards, getting a connection to your CRX repository is simple:
require 'java'
include_class('java.lang.String')
{|package,name| "J#{name}" }
include_class 'javax.jcr.Repository'
include_class 'javax.jcr.SimpleCredentials'
include_class 'org.apache.jackrabbit.rmi.client.
ClientRepositoryFactory'
factory = ClientRepositoryFactory.new
repository = factory.
getRepository('//localhost:1234/crx')
session = repository.login(SimpleCredentials.
new("admin", JString.new("adminpassword").
toCharArray))
name = repository.
getDescriptor(Repository::REP_NAME_DESC);
user = session.getUserID
puts "logged in as " + user + " in " + name
Running this like described here will yield something like
logged in as admin in CRX
The code should be straight-foward apart from the weird JString include which is also explained in my previous post. Now, for converting the "to" property from a single-valued to a multi-valued property I intend to
- copy the value of "to" to a new multi-valued property "tom"
- delete the single-valued "to"
- create a new multi-valued "to"
- copy the value of "tom" to "to"
- and delete "tom" for all mail nodes
Deleting a node: null fun
Deleting a JCR property is (unfortunately) not done through a method like removeProperty(). Instead, you need to use setValue and pass null as a parameter. However, setValue is overloaded. So, in Java the code would look like:
mailNode.getNode("jcr:content").
setProperty("tom",
new String[]{mailNode.getNode("jcr:content").
getProperty("to").getString()});
mailNode.getNode("jcr:content").
setProperty("to", (String)null);
mailNode.getNode("jcr:content").
setProperty("to",
mailNode.getNode("jcr:content").
getProperty("tom").getValues());
mailNode.getNode("jcr:content").
setProperty("tom", (String[])null);
Note that in the 2nd and the 4th line the null is casted into a String and a String[] array respectively (to be honest, until recently I did not know that null can be casted). This needs to be done in order to let the compiler know which version of the overloaded method setValue to use.
If you want to do this in JRuby you need to map Ruby strings to Java strings, null to nil and Java arrays to Ruby arrays.
mail_node.getNode("jcr:content").
setProperty("tom",
[mail_node.getNode("jcr:content").
getProperty("to").getString].to_java(JString))
mail_node.getNode("jcr:content").
setProperty("to", nil)
mail_node.getNode("jcr:content").
setProperty("to", mail_node.
getNode("jcr:content").
getProperty("tom").getValues)
mail_node.getNode("jcr:content").
setProperty("tom", [nil].to_java(JString))
If you look at the second line you note that nil is OK to pass instead of Java's (String)null. In the first and the forth line, however, you can see that JRuby's to_java method must be used to convert an array of Ruby strings to an array of Java strings (JStrings). R.J. Lorimer has written a tutorial on how to convert arrays between JRuby and Java.
The code above needs to be wrapped into a iterator loops (depending on how you structured your mail archive) which is quite simple, but for completeness here it is:
list = session.getRootNode.
getNode("incubator-sling-dev")
month_iterator = list.getNodes
while month_iterator.hasNext
month_node_iterator = month_iterator.
nextNode.getNodes
while month_node_iterator.hasNext
mail_node = month_node_iterator.nextNode
to = mail_node.getNode("jcr:content")
.getProperty("to")
if to.getDefinition.isMultiple
puts mail_node.getPath+" is OK"
else
# code from above
session.save
end
end
end
Adding a Sling Resource Type
In order to use Sling to render a JCR node it is often beneficial if the node has a "sling:resourceType" property set. Sling can also use the node type to find the appropriate rendering script. But in our case with unstructured nodes we need the resourceType property. So, let's loop over the mail nodes and set it.
Rather than invoking boring old setProperty on a node I would like to show how to utilize (J)Ruby's dynamic language features to achieve this goal. In Ruby you can dynamically extend a class. This even works for Java's built-in classes. For example:
class ArrayList def foo; "Hello!"; end end ArrayList.new.foo => "Hello!"
javax.jcr.Node is an interface. For interfaces the syntax looks slightly different:
JavaUtilities.extend_proxy("java.util.List") do
def bar; "Goodbye!"; end
end
ArrayList.new.bar => "Goodbye!"
So for our mail nodes this looks like:
JavaUtilities.extend_proxy("javax.jcr.Node") do
def resource_type=value
self.getNode("jcr:content").
setProperty("sling:resourceType", value)
end
def has_resource_type?
begin
self.getNode("jcr:content").
getProperty("sling:resourceType").
length > 0
rescue
false
end
end
end
end
resource_type=value is the setter method for the resource type. has_resource_type? returns a boolean value if the resource type is set already (Ruby method names can contain characters like "?"). This allows us to set the property "sling:resourceType" like this:
if !mail_node.has_resource_type? mail_node.resource_type = "email" end
Ruby has also another trick up its sleeves that allows us to do funny things with JCR nodes: method_missing. Override the method_missing method to catch all method calls to undefined methods. Like this we can create a new setter that catches attempts to set a new property and write it down into our mail node as a string property. The definition looks like this:
JavaUtilities.extend_proxy("javax.jcr.Node") do
def method_missing(name, *args)
# get the name of the called method
# and check if it is a setter
name = name.to_s
setter = /=$/ === name
expected_args = 0
if setter
name = name[0...-1]
expected_args = 1
end
# this is soemthing we do not want to catch
unless args.size == expected_args
err = "wrong number of arguments
(#{args.size} for #{expected_args})"
raise ArgumentError.new(err)
end
# write it to the repository
self.getNode("jcr:content").
setProperty(name.to_s, args.to_java(JString))
end
end
In the above code name is the name of the variable the user wants to set. setter is a boolean that checks whether it is actually a setter method which is called (rather than another method). args is an array that contains the passed arguments. Armed with this extension we can add properties to the node like this:
mail_node.mynewprop = "myvalue"
Hmm, syntactic sugar.
If you are interested in more Ruby goodies: Daniel Spiewak has written a piece on JRuby scripting in Java projects and Ola Bini has a nice overview post on Ruby's meta programming techniques.

PS. The current version of JCRStoreBean now always uses multi-valued properties for properties that can have multiple values.