To get some grip on the configuration of the Weblogic domains and servers at my current client, I created a tool that reads domain config files and translates them in a graph. I decided to solve this problem in Scala, mainly because I read about its powerful native XML parsing capabilities. Parsing XML turned out to be a total no-brainer, but I managed to learn something about how to solve problems the Scala way, so this is a story about Scala rather than parsing XML in Scala.

To solve my problem I had to parse the configuration file for a Weblogic domain, or more specifically (in my case) the file where Weblogic stores information about JMS resources. Parsing one of those files is done like this:

    val configData: Elem = XML.loadFile(jmsConfigFileName)

configData now holds a parsed version of the file whose name is stored in jmsConfigFileName. This file contains stuff like queue and topic definitions like the fragment below:

<queue name="queueTo">
  <topic name="domainJMSTopic">

My first attempt at retrieving all topic definitions from this config file was this:

  def findTopics(configData: Elem): Set[JmsObject] = {
    val jmsObjects = for (topic <- (configData \\ "topic"))
      yield (new JmsTopic((topic \ "@name").text, (topic \ "jndi-name").text))

I felt really happy about having written this powerful and concise loop. Scala's for construct is powerful, as well as its XML parsing. To retrieve the queues I added the method below:

  def findQueues(configData: Elem): Set[JmsObject] = {
    val jmsObjects = for (queue <- (configData \\ "queue"))
      yield (new JmsQueue((queue \ "@name").text, (queue \ "jndi-name").text))

The duplication was obvious, so I felt a little less happy with my solution but I couldn't see an easy way out. Enter my colleague and Scala trainer Urs Peter. He showed me two ways to improve the code.
Our first attempt looks like this:

def buildListOfJmsObjectsFromConfigData[T] 
           (configData:Elem, clazz:Class[T], startNode:String): Set[T] = {
    val jmsObjects = for (node <- (configData \\ startNode))
      yield ( clazz.getConstructors.apply(0).newInstance(
                             (node \ "@name").text
                           , (node \ "jndi-name").text).asInstanceOf[T])

Which you can then call like this:

buildListOfJmsObjectsFromConfigData (configData, classOf[JmsConnectionFactory]
           , "connection-factory")

This looked quite Java-ish to me, as well as complex and obfuscated.

Our second attempt removes the loop and uses currying. It looks like this:

def buildListOfJmsObjectsFromConfigData[T]
            (configData:Elem, startNode:String) (f:(NodeSeq) => T): Set[T] = {
     (configData \\ startNode).map(f).toSet

This second variant of buildListOfJmsObjectsFromConfigData[T] can be called like this:

      buildListOfJmsObjectsFromConfigData (configData, "topic") {
        node => new JmsTopic((node \ "@name").text, (node \ "jndi-name").text)

This works because the (configData \\ startNode) yields a set of objects on which we call map. Map takes a function as a parameter. The function shown above extracts a JmsTopic object, so we end up with a collection of JmsTopic instances.

I'm happy with our final version. It is concise and clear as well as extensible; way better than my first Java-ish version.

The code is attached here.
I created three versions that are exercised in a unit test. JmsConfigReaderV1 is the first Java-ish version, JmsConfigReaderV2 is the compact but obfuscated solution and JmsConfigReaderV3 is my final attempt.
I'm sure there are lots of opportunities to improve, so I welcome your opinions ans comments.