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).
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
Be sure to register a dependency on this plugin in your plugin descriptor, e.g.
def dependsOn = ['binaryArtifacts': '1.0 > *']
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
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
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
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
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
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 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
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.
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
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.
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
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.