Archive for November 18th, 2011

Dynamic Controllers in Grails

Friday, November 18th, 2011

When I was working on the app-info plugin I was thinking about making it a modular plugin like I was then planning for the Spring Security plugins. In particular I wanted to keep the Hibernate aspects separate, but also make it convenient for other plugins to bolt onto this one and add extra pages. That led to the development of the dynamic-controller plugin which lets you create virtual controllers from various sources, breaking out of the restriction that controllers have to follow the standard Grails conventions (i.e. it has to be under grails-app/controllers, has to be written in Groovy, the name has to end in “Controller”, etc.)

I’ve seen reports that this wasn’t working in Grails 2.0, so I started looking at what the issues were. It turns out they were minor so I released version 0.3 today; it works with Grails 1.3 or 2.0 applications. While I was testing it I discovered that I had done a bad job documenting how to use it (and I couldn’t find the test application I used when developing it) so I had to re-learn how to use my own plugin. Doh.

How it works

The process is similar to an AOP mixin, where an existing controller is augmented with one or more additional actions. The plugin is flexible in how these actions are defined; currently you can create a ControllerMixin, “link” an action from an existing controller, or load a Closure from the database or a Resource (e.g. a file in the classpath). And you’re free to create your own source; just implement the ClosureSource interface or extend AbstractClosureSource.

In addition, you don’t even need an existing destination controller to mix into. If you declare the destination class and it doesn’t exist, the plugin will compile one for you and mix into that.

Controller Mixins

The easiest and most intuitive approach is to create a ControllerMixin. The plugin adds the grails-app/controllerMixins folder when it’s installed, and you create classes there like regular controllers. You define actions as Closures and have access to all of the MetaClass methods and properties added to “real” controllers (e.g. params, request, response, render, redirect, etc.). Dependency injection and reloading are also supported since a ControllerMixin is a custom Artifact.

While testing I also noticed that you can define actions in a ControllerMixin as methods in a Grails 1.3 application like you can in Grails 2.0. This is because the plugin only works with Closures, so to support Grails 2.0 method actions I create a Closure that calls the method, so it works in any supported version of Grails.

You need to define the destination(s) for each mixin, and this is done in Config.groovy. The syntax is a Map, where the keys are the full class name of the mixin, and the values are either a single String or a list of Strings defining which controller(s) to mix into.

For example with this configuration:

grails.plugins.dynamicController.mixins = [
   'test.FooControllerMixin': 'com.myapp.SomeController',
   'test.BarControllerMixin': ['com.myapp.SomeController',
                               'com.myapp.OtherController']
]

all of the actions in FooControllerMixin will be added to SomeController (and available under /appname/some/... URLs), and all of the actions of BarControllerMixin will be mixed into both SomeController and OtherController (available under /appname/some/... and /appname/other/... URLs). If SomeController and OtherController exist they’ll be added to, and if not they’ll be generated in-memory.

Linking to existing Controller Actions

Another option is to link to existing controller actions. This is a bit more work since it’s done programmatically; BootStrap.groovy is probably the best place to do this. You need access to the GrailsApplication but that’s easy to dependency-inject into BootStrap.groovy.

So given this existing controller:

package com.yourapp

class MyController {

   def actionAsClosure = {
      ...
   }

   def actionAsMethod() {
      ...
   }
}

you would link its actions into com.myapp.OtherController like this:

import com.burtbeckwith.grails.plugins.dynamiccontroller.ControllerClosureSource
import com.burtbeckwith.grails.plugins.dynamiccontroller.DynamicControllerManager

class BootStrap {

   def grailsApplication

   def init = { servletContext ->

      def controllerClassClosures = [
         foo: new ControllerClosureSource(
              SourceController.name, 'actionAsClosure', grailsApplication),

         bar: new ControllerClosureSource(
              SourceController.name, 'actionAsMethod', grailsApplication)
      ]

      DynamicControllerManager.registerClosures(
              'com.myapp.OtherController',
              controllerClassClosures, null, grailsApplication)
   }
}

The keys of the controllerClassClosures Map are the destination action names (they can be the same as the original or different – your choice) and the values are ControllerClosureSource instances that declare the class and action name to link.

Then just call DynamicControllerManager.registerClosures with the name of the destination controller and the map you created.

This will create new /appname/other/foo and /appname/other/bar URLs.

Actions stored in a database

You can also define action Closures as source code saved in a database table. The default implementation of the ClosureSource is DatabaseClosureSource and it expects that the table name is “closures” and that there are “action” and “controller” columns to specify the destination action name and controller class, and a “closure” column to hold the code. You can customize this by using your own subclass of DatabaseClosureSource or creating your own.

To make working with database records easier you can create a domain class that has the expected table and column names, e.g.

package com.yourapp

class DatabaseClosure {

   String action
   String controller
   String closure

   static mapping = {
      table 'closures'
      closure type: 'text'
   }
}

This is completely optional, and you can use JDBC or whatever you want to manage the database records. Note that the class name doesn’t matter since the table name is specified in the mapping block.

Database closures are parsed using a ConfigSlurper, which means they can be environment-aware if you like. So for example if you insert this data:

String closure = '''
environments {
   development {
      foo = {
         ...
      }
   }
   test {
      foo = {
         ...
      }
   }
   qa {
      foo = {
         ...
      }
   }
   production {
      foo = {
         ...
      }
   }
}
'''

new DatabaseClosure(action: 'actionName',
                    controller: 'com.myapp.OtherController',
                    closure: closure).save()

then the correct version for the current environment will be used (even with a custom environment like “qa”).

This example will create a new /appname/other/foo URL.

You don’t have to use environments though:

String closure = '''
foo = {
   ...
}
'''

new DatabaseClosure(action: 'actionName',
                    controller: 'com.myapp.OtherController',
                    closure: closure).save()

Once you have your closures defined in the database, you register them with DatabaseClosureSource.registerAll, typically in BootStrap.groovy:

import com.burtbeckwith.grails.plugins.dynamiccontroller.DatabaseClosureSource

class BootStrap {

   def dataSource
   def grailsApplication

   def init = { servletContext ->
      DatabaseClosureSource.registerAll dataSource, grailsApplication
   }
}

Resource-based actions

The final option that comes with the plugin is loading actions from text files or other resources. Like the database option above, the files are parsed with ConfigSlurper so they’re also environment-aware. The files can be in any reachable location that can be referenced as a org.springframework.core.io.Resource, with the most likely case being a org.springframework.core.io.ClassPathResource. This lets you to have actions defined in regular text files.

You can declare as many actions per file as you want. If you put them in src/java or grails-app/conf they’ll get copied into the classpath, so that’s your best bet. You could also store them in a jar file.

For example, if you have a file named resourceClosures.txt in the src/java/com/yourcompany folder with this content:

foo = {
   ...
}

bar = {
   ...
}

then you can register them in BootStrap.groovy like this:

import org.springframework.core.io.ClassPathResource

import com.burtbeckwith.grails.plugins.dynamiccontroller.DynamicControllerManager

class BootStrap {

   def grailsApplication

   def init = { servletContext ->

      DynamicControllerManager.registerClosures(
           'com.myapp.OtherController',
           new ClassPathResource('com/yourcompany/resourceClosures.txt'),
           null, grailsApplication)
   }
}

then the two actions will be available as /appname/other/foo and /appname/other/bar.


Check out the plugin page or the source code. Let me know if you find a creative use for this plugin.

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