Quick and Easy Groovy for the Web

22 June 2011

Groovy can be used pretty easily to spin up some simple web pages in almost the same way one would hack out some PHP or JSP without going to the trouble to do an all-out Grails project.

The Groovy Servlet allows you to pack up the groovy-all-*.jar, a simple web.xml, and whatever *.groovy scripts you want and deploy it right into Tomcat as a plain WAR file. The Groovy Servlet page

Here's a bit of a script I put together to jump start a simple Groovlet project by packaging a WAR file from a directory of scripts. This isn't Groovy Servlet code itself, but just a command-line tool. (The Groovy Servlet page linked previously has examples for writing your own servlets.) This script will copy in the Groovy JAR and generate the basic web.xml to wire up the GroovyServlet to dynamically execute your scripts. I also have a downloadable copy of package_groovlet.groovy.

#!/usr/bin/env groovy

if (args.size() < 1) {
    print """\
        |Usage: package_groovlet.groovy <war-name>
        |Package the current directory into a Groovy Servlet war.
        |""".stripMargin()
    return
}

def war = args[0]
def embed = "${System.getenv()['GROOVY_HOME']}/embeddable"

def ant = new AntBuilder()

ant.sequential {
    delete(dir: 'build')
    mkdir(dir: 'build/WEB-INF/lib')
    copy(toDir: 'build/WEB-INF/lib') {
        fileset(dir: embed) {
            include(name: 'groovy-all-*.jar')
        }
    }
    copy(toDir: 'build') {
        fileset(dir: '.') {
            exclude(name: 'build/**')
        }
    }
}

new FileOutputStream('build/WEB-INF/web.xml').withWriter { webxml ->
    webxml.print """\
        <!DOCTYPE web-app PUBLIC
          "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
          "http://java.sun.com/dtd/web-app_2_3.dtd" >
        <web-app>
            <servlet>
                <servlet-name>Groovy</servlet-name>
                <servlet-class>groovy.servlet.GroovyServlet</servlet-class>
            </servlet>
            <servlet-mapping>
                <servlet-name>Groovy</servlet-name>
                <url-pattern>*.groovy</url-pattern>
            </servlet-mapping>
         </web-app>
     """.stripIndent()
}

ant.jar(destfile: "build/${war}", basedir: 'build')
println "Created build/${war}"


A Groovy First Monday

22 September 2010

The new K-Prep website is implemented in Grails now, so I was looking for a Groovy way to let the computer do the heavy lifting of figuring out dates for each week (starting with the first Monday) of the school year and matching them to a list of themes.

I got the algorithm working in about 8 lines of Groovy code, and then generalized it a bit more for you here to allow you to ask for any day of any week of any month. e.g. 3rd Wednesday in December.

To get the date for the 3rd Wednesday in December, my call looks like this:

dayByWeek(2010, DECEMBER, 2, WEDNESDAY, 0)

And the implementation looks like this:

import static java.util.Calendar.*

def dayByWeek = { year, month, week, day, shift ->
    def c = Calendar.instance
    c.minimalDaysInFirstWeek = c.firstDayOfWeek + 7 - day
    c[YEAR] = year
    c[MONTH] = month
    c[WEEK_OF_MONTH] = week + 1 + (shift?:0)
    c[DAY_OF_WEEK] = day
    c.time
}

The shift parameter allows me to optionally start a theme on the previous week in cases where that day is the end of the previous month. The static import allows me to conveniently refer to the Calendar constants without qualifying them, and finally, setting the minimalDaysInFirstWeek allows days early or late in the week to still be found when the Calendar would have otherwise preferred a longer week to start searching.


Ubuntu Linux on the MacBook Pro

12 January 2010

OS X on my MacBook Pro was getting sort of screwy, and I was preparing to reinstall, so I thought I'd see how far I could get with Ubuntu 9.04 on the MacBook Pro.

Booting EFI

First things first, I read a bunch about lilo and grub and the Mac's EFI instead of a normal BIOS. I was expecting some real trouble here, but the Ubuntu install CD booted up fine, and after the install finished, it was able to just boot from the hard drive with no extra work.

Wireless Networking

I was happy to see the wireless work and even our Juniper VPN software seemed to work pretty easily -- it uses Linux' built-in tunnel interfaces. I had initially found the wireless to be a bit flakey, and it would freeze up shortly after connecting. It was sort of similar to the trouble I was having on the EeePC, so I installed the current linux-modules-backports package, and it got much better.

Software for Work

One of my first concerns was going to be accessing the corporate Exchange servers for my email, address book, and calendar, but Evolution with the Exchange Connector (straight from the Ubuntu repository) can handle this. It's not all that stable, but it works well enough with a couple restarts of the client through the day.

Evolution sometimes gets a bit slow or otherwise wonky, and that in turn causes the clock/calendar applet on the Gnome Panel to freeze up. Sometimes the whole panel freezes. A quick kill of gnome-panel gets it all moving again, but having a stopped clock can get really inconvenient. Evolution's notifier daemon is also unreliable, so I've gotten accustomed to being aware of my schedule and the time, and not relying on a pop-up reminder.

I installed Eclipse myself, so I can keep it more up-to-date, so I had a 1.6 version of Subclipse. This immediately caused me some trouble when Ubuntu 9.04 was only shipping Subversion 1.5. I ended up installing Subversion 1.6 from source, and then later going back to packaged versions when I upgraded to Ubuntu 9.10.

Function Keys

Using Eclipse, I really need the top function keys to work as function keys, and not volume, brightness, etc as is default for the Mac keyboard.

To get the keyboard to default to F-keys, and require the Fn key combo for the other controls, I needed to add a module parameter, so add create the /etc/modprobe.d/hid_apple.conf file with the single line:

options hid_apple fnmode=2

Upgrade to Ubuntu 9.10

The upgrade became available, so I jumped on it right away -- that's what I do. It went rather fabulously -- I had almost no trouble. Only Eclipse gave me trouble with the GTK+ update. I twiddled the environment in my Eclipse start-up script, per some advice, and the components in Eclipse almost always work now. I hear this'll be more completely fixed when newer versions of Eclipse are released.

The upgrade to Ubuntu 9.10 also took away Java 5, since Sun officially dropped support for it about the time Ubuntu released. Most of our projects just built fine with Java 6, but a few needed to be updated a bit to get them compliant with Java 6. I chose the OpenJDK installs, bounced back to Sun a couple times, but have now pretty reliably settled into OpenJDK with no problems.

Power Management

One of my favorite perks of running Linux is getting back control of the machine. I configured CPU frequency scaling to use the "conservative" CPU governor to save battery, I can sleep or suspend the machine (which works!) with a push of the power button, and it stays awake when I close the lid to walk to a meeting.

Running Linux is like coming home again, and it's serving my daily needs well at work. The MacBook hardware is holding up nicely too, which has not been my experience with older plastic-cased PCs.


Unit Testing Grails Custom Taglibs

12 January 2010

I built out the initial iteration of my photography website a couple months ago, and I've been coming back to it recently to put some enhancements into place. I really must get the photography blog up and running, but until then, I poke around at doing the simpler things -- like a convenient copyright taglib.

I beat people at work over the head with TDD all the time, so I obviously want to follow my own example. I started out searching around a bit on unit testing custom taglibs in a Grails project. I didn't come up with much, and what I did find wasn't really exercising the parameters that come into a taglib closure.

Let's start out asking Grails to create a shell of a taglib for me in the conventional locations:

% grails create-tag-lib copyright

Now I have a shiny new test/unit/CopyrightTagLibTests.groovy and a grails-app/taglib/CopyrightTagLib.groovy, and they're empty.

I want to be able to use it like this in my GSP:

<g:copyright startYear = "2009">John Flinchbaugh</g:copyright>

I'll be giving it a start year, it should use the current year as the end year, and the body is the name to whom it should attribute copyright. If the current year and the startYear match, then it should display only one. Let's get that down in the unit test. Fortunately, the tag name used in the GSP is just the name of a closure in the taglib.

Taglibs take a map of attibutes and a closure for the body of the tag, and they render their output to the out property. My first assumption says we should see the current year only and the body text when no tag attributes are specified (empty map). I cheat a little and just derive the current year in the test to form my expectation instead of trying to inject the current date.

    void testCopyrightNoStart() {
        tagLib.copyright([:]) { "xyz" }
        def expectedDate = Calendar.getInstance().get(Calendar.YEAR)
        assertEquals("Copyright ${expectedDate} xyz", tagLib.out.toString())
    }

Next, I assume if the startYear is specified and it matches the current year, it'll only display one year, and not a range. Make special note, the value of the startYear attribute being passed into the copyright method is a String. When taglibs are called by the GSP engine, they will pass in String values, so do yourself the favor and make sure you're doing that here.

    void testCopyrightStartThisYear() {
        def expectedDate = Calendar.getInstance().get(Calendar.YEAR)
        tagLib.copyright(
            startYear: "${expectedDate}") { "xyz" }
        assertEquals("Copyright ${expectedDate} xyz", tagLib.out.toString())
    }

I do a little math for the test of the full functionality and pass in last year for the startYear and demonstrate the taglib in all its glory -- showing a range of years.

    void testCopyrightStartLastYear() {
        def expectedDate = Calendar.getInstance().get(Calendar.YEAR)
        tagLib.copyright(startYear: "${expectedDate - 1}") { "xyz" }
        assertEquals("Copyright ${expectedDate - 1} - ${expectedDate} xyz",
                tagLib.out.toString())
    }

I like to nail down expectations about what would be misuses as well, so I'm going to say that a bad startDate will still run fine, and it'll just be shown in the output of the taglib.

    void testCopyrightBadStart() {
        def expectedDate = Calendar.getInstance().get(Calendar.YEAR)
        tagLib.copyright(startYear: "badYear") { "xyz" }
        assertEquals("Copyright badYear - ${expectedDate} xyz", tagLib.out.toString())
    }

Now that I have all my tests done up front, let's run grails test-app to see what we have! All my shiny new tests fail spectacularly, because I've not written the taglib yet, so let's go write enough of this new copyright closure on the CopyrightTaglib to get these tests to pass.

I address one test at at time, starting with just giving myself an empty closure assigned to copyright, and adding and modifying bits of the taglib code until it passes all my tests. That's how I know I've done everything I wanted and nothing unnecessary.

class CopyrightTagLib {
    def copyright = { attrs, body ->
        def thisYear = Calendar.getInstance().get(Calendar.YEAR) as String
        def startYear = attrs.startYear ?: thisYear
        out << "Copyright"

        if (thisYear != startYear) {
            out << " ${startYear} -"
        }

        out << " ${thisYear} ${body()}"
    }
}

And there it is, some simple taglib code which satisfies my every expectation as laid down in the tests. The last thing to do, is go add my shiny new taglib to my footer.gsp and I'm done. The code for the test and taglib are available in their entirety here: copyright.zip.


All the Posts

June 2011

September 2010

January 2010

February 2009

June 2008

March 2008

January 2008

December 2007

October 2007

August 2007

June 2007

May 2007

April 2007

March 2007

February 2007

January 2007

December 2006

November 2006

October 2006

September 2006

August 2006

July 2006

June 2006

May 2006

April 2006

March 2006

February 2006

January 2006

November 2005

October 2005

September 2005

August 2005

July 2005

June 2005

May 2005

April 2005

March 2005

February 2005

January 2005

December 2004

November 2004

October 2004

September 2004

August 2004

July 2004

June 2004

May 2004

April 2004

March 2004

February 2004

January 2004

December 2003

November 2003

September 2003