Dynamic Controllers in Grails

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.

6 Responses to “Dynamic Controllers in Grails”

  1. Paul says:

    Hi Burt,
    I’m a bit confused about versioning. In your post you say you released version 0.3 on November 18th, 2011. Within my application the list of installed plugins says “appInfo – 0.4.3”.

    While http://grails.org/plugin/app-info shows:
    Last Updated: 2011-09-13 00:22:38.0 by burtbeckwith

    Versions:

    • Version 0 (Updated by admin)
    • Version 1 (Updated by burtbeckwith)
    • Version 2 (Updated by burtbeckwith)
    • Version 3 (Updated by burtbeckwith)
    • Version 4 (Updated by jbarmash)
    • Version 5 (Updated by burtbeckwith)

    Where should a developer find the definitive version number of the plugin?

    Thanks.

    • Burt says:

      Paul – sorry for the confusion. I was only talking about app-info to give context for why I created the dynamic-controller plugin. I updated dynamic-controller from v0.2.1 to v0.3 but haven’t updated app-info in a while.

  2. TZ says:

    where can we submit issue? do u had a github url?
    I have a question:
    fooController has an action someAction
    and ControllerMixin also has the same name action.
    then if mixin which controller’s action is used for /foo/some?

  3. Ivan says:

    Hi Burt,

    So what is the advantage comparing to use URLMapping comes alone with Grails directly?

    What you are trying to resolve with the dynamic controller plugin?

    Ivan

  4. Justin says:

    Burt,

    I found the code that you wrote for dynamic domain classes. I need a way to create and compile a controller for that dynamic class that doesn’t have to have a database back end. I just need it registered so that I can navigate to it. My back end is a device that I am sending commands to. My controller will be calling a new implementation of gorm which will know how to talk to the device. I tried modifying your dynamic domain class code but I could never get a controller to work correctly. Can you at least point me in the right direction?

    Thanks,
    Justin

  5. Jiri says:

    Hi Burt,

    is there any way how to access method or variable which is declared e.g. in FooControllerMixin directly from controller to which it was “copied”?
    For example when FooControllerMixin will have method helloWorld(), it is accessible as action from browser directly but it is not possible to call it as method from targeg controller (except e.g. forward method)

    Or from opposite direction – is it possible to read some variables directly in FooControllerMixin from target controller?

    I would need to execute some code defined in FooControllerMixin using parameter defined inside target controller.

    Thanks

    Jiri

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