Blog

Getting the Java out of your Scala

03 Apr, 2011

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.

Developing software and infrastructure in teams, doing whatever it takes to get stable, safe and efficient systems in production.

Explore related posts