Micah Ransdell About | Code | Contact

Groovy Property Missing

I'd like to dive a bit deeper in to the internals of our Groovy job architecture, and explore some of the things I did to make it easier to develop, package up, and deploy code that is responsible for keeping our systems running smoothly. I'll start with a personal favorite of mine, our use of Groovy's propertyMissing.

One thing that I've picked up from my time playing with Ruby is it's metaprogramming abilities. Specifically, the ability for an object to respond to method invocations without being defined at runtime, using method_missing. Groovy has something similar called methodMissing and it's sister propertyMissing. I was most interested in streamlining property loading because there was a large amount of boilerplate around fetching a property from the appropriate files.

In the interest of removing boilerplate, I implemented a basic propertyMissing method to allow fetching properties seamlessly. There is also a hierarchy of property files, first check the config for the environment (dev, stg, prod), next check the job level config, and finally check the global level config. Below is the propertyMissing method, minus the code that picks up the config files.

def propertyMissing(String name)
{
    // Check if there is a local version first
    def locals = config.get(environment)
    if(locals?.get(name))
    {
        return locals.get(name)
    }

    // Get properties that are the same for the entire 
    // job, thus not needing repeating
    def jobProperties = config.get("job")
    if(jobProperties?.get(name))
    {
        return jobProperties.get(name)
    }

    // Fallback to the global version
    def globals = globalConfig.get(environment)
    if(globals?.get(name))
    {
        return globals.get(name)
    }

    // didn't find the property... return null
    // this triggers a property missing error
    return null
}

Our property files are not much different than standard Java property files, with the exception of having a hierarchy. There are two levels in each job property file, the "job" level properties, and the environment specific ones. Here is a sample configuration with both.

########
# JOB  #
########
# Job level properties, starting with job
# Example: job.source_db_username=SOURCE_READER
job.video_file_extension=mov
job.pixel_buffer=30
job.db_username=CLIENT
job.event_username=app

########
# DEV  #
########
# Configure with properties, prefixed with dev
# Example: dev.ftp_username=test
dev.temp_folder = C:/dev/RingPhotos/temp/
dev.error_folder = C:/dev/RingPhotos/error/
dev.complete_folder = C:/dev/RingPhotos/complete/

It's possible to have a config variable that is defined at the job level, but is overridden for a specific environment, for example a password to a third party service. Finally, in order to make use of the property value, the developer simply needs to refer to the property by its key.

BuiltRingVideoJob(String env, String[] arguments)
{
    // Job related constructor actions to follow
    tempFolder = temp_folder
    completeFolder = complete_folder
    postEffectsFolder = posteffects_folder
    int pixelBuffer = pixel_buffer

    // ... snip ...
}

This, along with several other changes, has greatly simplified things in our job system, and makes for faster development and deployment.