Getting the Java out of your Scala

03 Apr, 2011
Xebia Background Header Wave

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.

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

Get in touch with us to learn more about the subject and related solutions

Explore related posts