More Groovy power

After my first real encounter with Groovy I got really excited about it and decided to spend some of my personal training days to get more experience with Groovy. As study material I read the Programming Groovy book. While reading I really wanted to get my hands dirty, so as a study case I decided to solve one of my longest and biggest frustrations as a developer and that is working behind a proxy server. The idea I had is to create a groovy program that will allow to turn the proxy settings on or off for applications like (Maven, Subversion, Internet Explorer, Firefox and so on) by running a single command: groovy com/xebia/proxy/SwitchProxy on/off. Here is a short summary of some of the techniques I used while creating this program.

Dynamically invoke methods
Since Groovy's dynamic nature I decided not use Interfaces, why have interfaces in a dynamic language anyway? Each class representing an application for which a proxy needs to be switched just needs to have a method called "on" and "off". Groovy allows you to use a String as a method name, and since "on" and "off" are passed in as a String in the main method of SwitchProxy why not use this as the name of the method. Here is the short version of SwitchProxy:

    static void main(String[] args) {
        def operation = args[0]
        proxySwitcher."$operation"();
    }

This will call the method that has the same name as the parameter you pass in, in our case "on" or "off". So easy that it is almost not noteworthy...

Instantiate groovy classes at runtime
The script I wrote reads the applications to be proxied from a config file. Since I want my script to just run via the groovy command and only use groovy files I needed to find a way to instantiate a groovy class via its name. From a programmers perspective I want to be able to say: "com.xebia.proxy.ie.InternetExplorer".newInstance(). You can use Groovy's MetaClass capabilities to introduce a new method on String:

String.metaClass.newInstance = {
  //implementation here.
}

My initial thought was to use the good old Class.forName for this, but this is of course looking for compiled java classes which in my case aren't there. I could have decided to just compile my code on forehand and be done with it... but where is the fun in that?

The solution I came up with is to compile the classes, that I need to instantiate, at runtime. This appeared to be very easy in Groovy (what isn't by the way...) :

String.metaClass.newInstance = {
  def clazz 
  try {
    //will fail the first time.
    clazz = Class.forName(delegate)
  } catch(ClassNotFoundException e) {
    def compiler = new Compiler()
    def groovyFile = delegate.replaceAll("\\.", "/") + ".groovy"
    compiler.compile(new File(groovyFile))
    // now try again...
    clazz = Class.forName(delegate)
  }
  return clazz.newInstance()
}

The delegate keyword refers to the String object itself in this case.

Categories
Besides using Groovy metaClass capabilities for injecting methods as shown above, you can also use Categories. Categories are classes that allow you to enhance a certain class within a specific scope. I found this technique very handy for testing. Here is a simplified example: I wanted to test that a message is printed to the console when a user forgets to pass in either "on" of "off". To accomplish this with categories you first define your category:

class MockPrinter {
    static def printedText
    
    def static println(self, text) {printedText = text}
}

This will override Groovy's println method in a specified scope, via the use keyword, and sets the text to be printed to the property printedText. So to use the category in your test:

    void testShouldFailWhenNoParametersArePassedIn() {
        use(MockPrinter) {
                SwitchProxy.main()
        }
        assertEquals(SwitchProxy.USAGE, MockPrinter.printedText)
    }

That's it for now, if you want you can checkout the complete source code of the proxy script here. The code is of course self documenting :-).

Lars

Comments (3)

  1. Robert O'Connor - Reply

    July 6, 2008 at 7:07 pm

    for your main method:

    static void main(args) { ... }

    is also valid.

  2. friarminor - Reply

    July 7, 2008 at 3:45 am

    Hey, Lars.

    Dunno if you've heard of it but you may want to test your Groovy skills on the cloud-based platform at Mor.ph

    Best.
    alain

  3. [...] offers rich dynamic capabilities via its MetaClass support. In this blog post, I will take advantage of this feature to have a [...]

Add a Comment