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.