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:

[scala]

val configData: Elem = XML.loadFile(jmsConfigFileName)

[/scala]

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:

[xml]

<queue name="queueTo">

<sub-deployment-name>domain</sub-deployment-name>

<jndi-name>domain/jms/qto</jndi-name>

</queue>

<topic name="domainJMSTopic">

<sub-deployment-name>domain</sub-deployment-name>

<jndi-name>domain/jms/topic</jndi-name>

</topic>

[/xml]

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

[scala]

def findTopics(configData: Elem): Set[JmsObject] = {

val jmsObjects = for (topic <- (configData \\ "topic"))

yield (new JmsTopic((topic \ "@name").text, (topic \ "jndi-name").text))

jmsObjects.toSet

}

[/scala]

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:

[scala]

def findQueues(configData: Elem): Set[JmsObject] = {

val jmsObjects = for (queue <- (configData \\ "queue"))

yield (new JmsQueue((queue \ "@name").text, (queue \ "jndi-name").text))

jmsObjects.toSet

}

[/scala]

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:

[scala]

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])

jmsObjects.toSet

}

[/scala]

Which you can then call like this:

[scala]

buildListOfJmsObjectsFromConfigData (configData, classOf[JmsConnectionFactory]

, "connection-factory")

[/scala]

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:

[scala]

def buildListOfJmsObjectsFromConfigData[T]

(configData:Elem, startNode:String) (f:(NodeSeq) => T): Set[T] = {

(configData \\ startNode).map(f).toSet

}

[/scala]

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

[scala]

buildListOfJmsObjectsFromConfigData (configData, "topic") {

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

}

[/scala]

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.