2012-12-24

Testing Grails Filters that use Services

For SourceNode, I was writing a filter to redirect the user when the application is in "configuration-only" mode, as controlled by the AdministrationService. Let's say the code looks something like this:

grails-app/controllers/blogfiltertest/BookController.groovy:

package blogfiltertest

class BookController {
    def index() { }
}

grails-app/controllers/blogfiltertest/InitialConfigurationController.groovy:

package blogfiltertest

class InitialConfigurationController {
    def configurationRequired() {}
}

grails-app/services/blogfiltertest/AdministrationService.groovy:

package blogfiltertest

class AdministrationService {
    static transactional = false

    boolean isConfigurationOnly() {
        return false
    }
}

grails-app/conf/blogfiltertest/AdministrationFilters.groovy:

package blogfiltertest

class AdministrationFilters {
    def administrationService

    def filters = {
        configOnly(controller: 'initialConfiguration', invert: true) {
            before = {
                if (administrationService.isConfigurationOnly()) {
                    redirect(controller: 'initialConfiguration', action: 'configurationRequired')
                    return false
                } else {
                    return true
                }
            }
        }
    }
}

I wanted to write a unit test for this filter. It started something like this:

test/unit/blogfiltertest/AdministrationFiltersTest.groovy:

package blogfiltertest

import grails.test.mixin.*

@TestFor(BookController)
@Mock(AdministrationFilters)
class AdministrationFiltersTests {
    void testConfigurationOnlyRedirected() {
        withFilters(controller: 'book', action: 'index') {
            controller.index()
        }
        assert response.redirectedUrl == '/initialConfiguration/configurationRequired'
    }
}

The first roadblock I ran into was this:

java.lang.NullPointerException: Cannot invoke method isConfigurationOnly() on null object

This appears to be a Grails bug, GRAILS-8976. At least as of Grails 2.1.2, Grails unit tests don't properly perform dependency injection into filters. So, a workaround: manually have the filter get the appropriate service from the application context. This will also be run at run-time, resulting in a double-lookup of the bean definition. Here's the patched vesion:

package blogfiltertest

class AdministrationFilters {
    def administrationService

    def filters = {
        configOnly(controller: 'initialConfiguration', invert: true) {
            before = {
                administrationService = applicationContext.getBean('administrationService')
                if (administrationService.isConfigurationOnly()) {
                    redirect(controller: 'initialConfiguration', action: 'configurationRequired')
                    return false
                } else {
                    return true
                }
            }
        }
    }
}

It appears that the bean lookup needs to happen in the before closure. If you put it directly in filters, it doesn't have access to the applicationContext, and if you put it in configOnly, it won't find beans defined in the test.

Now there's a different error:

org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'administrationService' is defined

That's because, as per the documentation in the section "Testing Spring Beans", only a subset of the Spring beans are available for testing use, and you use defineBeans to make more available. Here's the updated version of the test using Grails mocking support to inject a mock version of the service.

package blogfiltertest

import grails.test.GrailsMock
import grails.test.mixin.*

@TestFor(BookController)
@Mock(AdministrationFilters)
class AdministrationFiltersTests {
    void testConfigurationOnlyRedirected() {
        defineBeans {
            administrationServiceControl(GrailsMock, AdministrationService)
            administrationService(administrationServiceControl: "createMock")
        }

        def administrationServiceControl = applicationContext.getBean("administrationServiceControl")
        administrationServiceControl.demand.isConfigurationOnly { true }

        withFilters(controller: 'book', action: 'index') {
            controller.index()
        }
        assert response.redirectedUrl == '/initialConfiguration/configurationRequired'
    }
}

When I ran into errors like below, it meant that I was trying to do bean lookup by type, which doesn't seem to be working with this style of mocking. The fix was to do lookup by name (which is more consistent with how Grails will do the injection anyway).

org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [blogfiltertest.AdministrationService] is defined: expected single bean but found 0:

Update 12/12/2012: If your filter uses URI scope (even if it's for all URIs) rather than controller/action, you'll need to set a URI on the request in the test for it to match using withFilters. Here's an example of what it looks like:

request.forwardURI = '/book/index'

2012-12-14

Grails Release Plugin NoClassDefFoundError: groovyx.net.http.HTTPBuilder

A few days ago, I was trying to release an update to the grails-page-resources plugin, when I ran into an error:

...
Notifying plugin portal 'http://grails.org/plugin/page-resources' of release...
| Error Error executing script PublishPlugin: groovyx.net.http.HTTPBuilder
java.lang.NoClassDefFoundError: groovyx.net.http.HTTPBuilder
    at PublishPlugin$_run_closure1.class$(PublishPlugin)
    at PublishPlugin$_run_closure1.$get$$class$groovyx$net$http$HTTPBuilder(PublishPlugin)
    at PublishPlugin$_run_closure1.doCall(PublishPlugin:421)
    ...

Searching Google for help didn't seem to turn up anything helpful, nor did turning on additional output with --stacktrace --verbose. At the time, this was already with the most recent version of grails-release-plugin, 2.2.0. So, I submitted a JIRA ticket, GPRELEASE-40.

Yesterday, I had the bright idea of checking the Grails mailing lists to see if there was anything useful there. And sure enough, the answer.

What it boils down to is that Grails wasn't actually running the specified version of the plugin, and some arcane steps were needed to get it to do so.

I love Grails, but my biggest frustration with it is when I run into one of these mystical incantations that are needed without a readily apparent reason, and not found in the otherwise great documentation, but rather only by stumbling upon them elsewhere.

For now, I'm going with a setting recommended by Peter Ledbrook here:

grails.project.work.dir = "target/$grailsVersion"

2012-11-24

Application Data Directories on Windows/Mac

I'm writing an application that will support both Windows and Mac, and it's arrived at the point in its development where it needs its first data file. What directory should it be stored it? For the purposes of this post, let's assume that the files are not something that the user is supposed to generally interact with directly (as in that case, it might be more appropriate to put it in a Documents folder of some sort).

It looks like in modern Windows (as of this writing, Windows 8/Server 2008 R2), there are several options recommended by Microsoft for storing application data, each with a slightly different purpose. Their locations can be looked up via SHGetKnownFolderPath if you pass in the appropriate KNOWNFOLDERID. Many of these folders are hidden (so tweak your display settings if needed to see them).

  • RoamingAppData: User-specific application data this is intended to "roam" (synchronize with a central user profile server).
    • This can be accessed vis the %APPDATA% environment variable.
    • In Windows Vista and later, this defaults to C:\Users\USERNAME\AppData\Roaming.
    • In Windows XP and earlier, this defaults to C:\Documents and Settings\USERNAME\Local Settings\Application Data (and wasn't separate from LocalAppData).
  • LocalAppData: User-specific application data that is not intended to "roam" due to either large size or being machine-specific.
    • This can be accessed via the %LOCALAPPDATA% environment variable in Windows Vista and later. In Windows XP and earlier, you can fall back to %APPDATA%.
    • In Windows Vista and later, this defaults to C:\Users\USERNAME\AppData\Local.
    • In Windows XP and earlier, this defaults to C:\Documents and Settings\USERNAME\Local Settings\Application Data (and wasn't separate from RoamingAppData).
  • LocalAppDataLow: Similar to LocalAppData but for "low integrity data", meaning that the originating user cannot be trusted. It's pretty rare to have reason to use this one.
    • In Windows Vista and later, this defaults to C:\Users\USERNAME\AppData\LocalLow.
  • ProgramData: Non-user-specific application data.
    • This can be accessed via the %ProgramData% environment variable in Windows Vista and later. In Windows XP and earlier, you can fall back to %ALLUSERSPROFILE%\Application Data.
    • In Windows Vista and later, this defaults to C:\ProgramData.
    • In Windows XP and earlier, this defalts to C:\Documents and Settings\All Users\Application Data.

From this, we now know what questions to ask. Are the files user-specific? If not, use ProgramData. If so, should they roam? If so, use RoamingAppData, otherwise use LocalAppData. If you're only targetting Windows Vista/Server 2008 and later, you can use either SHGetKnownFolderPath or an environment variable to get the right path. If you want to support Windows XP/Server 2003, it gets a bit more complicated. You can use environment variables, but for some of them, you need to add fallback logic for the appropriate XP environment variable. If you're using the Windows API, SHGetFolderPath can be used in Windows 2000 or later, with the CSIDL equivalent of the FOLDERID constants. In Java, probably the easiest way to do so is to use JNA's wrapper of this function, like this:

Shell32Util.getFolderPath(ShlObj.CSIDL_LOCAL_APPDATA)

You should use either a CompanyName\ApplicationName or ApplicationName subdirectory in these known folders rather than writing files in them directly. Note that the ProgramData folder is intended to be writable only by admin users. When User Account Control (UAC) is in play, that means that even admin users can't write to it unless they're running in a permission-elevated context. If you want your program to be able to write to an application-specific ProgramData subfolder without requiring elevation, have your installer create the directory and set an appropriate ACL to grant the needed permissions.

Now that we've got Windows squared away, what about Mac OS X? The Library folder in various domains is used for application data. More specifically:

  • ~/Library (default of /Users/USERID/Library): User-specific application data.
  • /Library: Non-user-specific application data.

The Mac OS X documentation specifies using particular subdirectories within a Library directory. For most purposes, you'll want to use the Application Support directory, which is to be used for "all app data files except those associated with the user's documents".

You should use a subdirectory of Application Support rather than writing files in it directly. The documentation says to use your bundle identifier (com.example.MyApp), but it looks to me like it is more common to use the application name instead. In Lion, the default permissions on /Library/Application Support changed such that users can't write to this directory without elevated permissions. If you want your program to be able to write to a directory within that area without requiring elevation, have your installer create the directory and set appropriate permissions.

The API provided locate known directories appears to be NSSearchPathForDirectoriesInDomains (which can be called via Rococoa). That would allow handling cases such as searching for a file across multiple domains. In many cases, it may be sufficient to simply look in a single directory, however.

Related articles: