Grails Binary Artifacts Plugin

Last week at the Boston Grails Meetup we were talking about the state of Grails plugins (the plugin collective, certified plugins, etc.) and the idea of binary plugins came up. This has been discussed as a way to deploy closed-source plugins – with the current approach the plugin zip file contains all of the source code and the plugin descriptor, and there’s no way to use compiled classes as controllers, services, etc. I suppose it’s because of the work I did with the Dynamic Controller plugin (used by the App Info plugin) and the dynamic domain class stuff that I worked on (which is used in the Dynamic Domain Class plugin) but this stuck in my head and I ended up spending most of last weekend working on a plugin for this and finished it up this weekend.

The approach I took is to write a plugin (binary-artifacts) that proxies your plugin(s) which contain compiled classes instead of source code. Grails has some fixed rules about what it means for a class to be an artifact of a specific type, so these needed to be worked around, but there really aren’t any hacks here. For example a service must be under the grails-app/services folder and the name must end in Service, and controllers, taglibs, filters, have similar rules. Domain classes have no name restrictions but must be under grails-app/domain.

Instead of using these conventions, artifacts are configured with a properties file which must be named <appname>-binaryartifacts.properties and be in the classpath (so it’s best to put it in grails-app/conf or src/java).

Plugin descriptor

The plugin descriptor is created by the create-plugin script and contains several plugin properties (version, grailsVersion, author, title, etc.) and six closures (all optional) that are called either at startup during a particular initialization phase (doWithWebDescriptor, doWithSpring, doWithDynamicMethods, and doWithApplicationContext) or when the configuration or a watched resource is modified in dev mode (onChange and onConfigChange).

When using binary artifacts you still specify the property values the traditional way and can implement any of the six closures inline, but you can instead register a plugin ‘stub’ that’s called at each phase. This is a Groovy class containing any or all of the six supported closures. It will be instantiated and each closure called with the appropriate delegate set, so you can put Spring bean creation, metaclass enhancements, etc. in a compiled Groovy class. Register the class name in the properties file using the stub key.

Be sure to register a dependency on this plugin in your plugin descriptor, e.g.

def dependsOn = ['binaryArtifacts': '1.0 > *']

Codecs

Codecs can be written in Groovy or Java and must follow the standard naming convention. Code them just like you do traditional codecs, but put the source under src/groovy or src/java and register the comma-delimited class names in the properties file using the codecs key.

Controllers

Controllers must be written in Groovy (since they’re implemented with Closures) and must follow the standard naming convention. Code them just like you do traditional controllers, but put the source under src/groovy and register the comma-delimited class names in the properties file using the controllers key.

Domain classes

Domain classes must be written in Groovy (to support the mapping and constraints closures). Code them just like you do traditional domain classes, but put the source under src/groovy and register the comma-delimited class names in the properties file using the domainClasses key.

Filters

Filters must be written in Groovy (since they’re implemented with Closures) but you can use whatever class names you want. Code them just like you do traditional filter classes, but put the source under src/groovy and register the comma-delimited class names in the properties file using the filters key.

Services

Services can be written in Groovy or Java (but if you’re doing database work with GORM classes it’ll be a lot more convenient to use Groovy) but you can use whatever class names you want. Code them just like you do traditional services, but put the source under src/groovy or src/java and register the comma-delimited class names in the properties file using the services key.

In addition you have flexibility with the Spring bean name that the service is registered under. Traditionally FooService is registered as fooService but using this plugin you can use whatever valid name you want. Register the class name and Spring bean name using the classname:beanname syntax in the properties file.

Taglibs

Taglibs must be written in Groovy (since they’re implemented with Closures) but you can use whatever class names you want. Code them just like you do traditional taglib classes, but put the source under src/groovy and register the comma-delimited class names in the properties file using the taglibs key.

Scripts

Scripts are still implemented as Gant scripts under the scripts folder, but you can delegate the execution of a target to a compiled class. Include the plugin’s _RunBinaryScript.groovy script and do whatever initialization you need (e.g. using depends()) and call runBinaryScript(classname). The specified class will be instantiated and must have a closure named executeClosure. The closure’s delegate will be configured from the calling script’s delegate and invoked.

GSPs

GSP support isn’t 100% complete since compiled GSPs aren’t supported in Grails in run-app. This means that you can either keep your GSPs under the grails-app/views folder and ship the source, or compile them (this is handled by the plugin) but then they’ll only work when deployed in a war.

Code them just like you do traditional GSPs, and put the source wherever you want. If you want them to work in run-app mode keep them under grails-app/views, but if you’re precompiling then you can put them elsewhere and configure the location using the com.burtbeckwith.binaryartifacts.gspFolder config option in Config.groovy

You don’t register individual GSPs in the properties file; instead the GSP precompilation script creates a properties file that is specified. The script will be named <appname>-gsp-views.properties and you specify that name under the gspPropertyFile.

package-binary-plugin

Instead of running package-plugin like with a traditional plugin, you must use package-binary-plugin instead. The script doesn't take any arguments, and it compiles your code (and GSPs if configured) and configures the required properties file, and zips the plugin in the same format as a tradtional plugin. If you unpack the zip file you'll notice that the compiled classes are kept in a jar file in the lib folder.

Configuration

There are only two configuration options that control plugin behavior and they're both for GSPs. Both are set in Config.groovy. The first is com.burtbeckwith.binaryartifacts.gspFolder and it allows you to override the default location of your GSPs (grails-app/views). The other is com.burtbeckwith.binaryartifacts.compileGsp which can be used to disable precompilation of GSPs (defaults to true).


You can download a sample plugin that demonstrates the process. It has two domain classes, two services (one in Groovy and one in Java), a codec (in Java, although Groovy works too), a controller and two GSPs, a filters class, and a script. The plugin stub registers a context and session listener in web.xml and adds a bark method to String's metaclass.

Package the plugin by calling grails package-binary-plugin (or use the included build.xml) and install it the usual way, e.g. grails install-plugin /path/to/grails-binary-artifacts-test-0.1.zip.

5 Responses to “Grails Binary Artifacts Plugin”

  1. [...] información sobre Grails Binary Artifacts Plugin (traducido al [...]

  2. Vincent says:

    Hello Burt,

    I got an exception when trying to package-binary-plugin (with your sample plugin) :

    groovy.lang.MissingPropertyException: No such property: cache for class: com.burtbeckwith.binaryartifacts.BinaryAwarePluginBuildSettings
    grails.util.PluginBuildSettings$_getPluginSourceFiles_closure10.doCall(PluginBuildSettings.groovy:302)
    grails.util.PluginBuildSettings.resolvePluginResourcesAndAdd(PluginBuildSettings.groovy:583)

    I’m using grails 1.3.7

  3. Ben says:

    Burt,

    Thanks for writing this plugin. I’ve been looking for this for quite some time. I was wondering how difficult it would be to include *UrlMappings.groovy files in the types of artifacts it would support? If I create a FooUrlMappings.groovy in my plugin, it works when I do run-app from the plugin. But if I then do a binary packaging on my plugin, the compiled mappings file doesn’t make it into the binary jar and isn’t available in the consuming grails application. Thanks!

    Ben

Creative Commons License
This work is licensed under a Creative Commons Attribution 3.0 License.