(Quick Reference)

8 Controllers and Services - Reference Documentation

Authors: Andres Almiray

Version: 1.5.0

8 Controllers and Services

This section describes the artifacts that hold the logic of a Griffon application.

8.1 Controllers

Controllers are the entry point for your application's logic. Each controller has access to their model and view instances from their respective MVC group.

Controller actions are usually defined using a closure property form, like the following one

class MyController {
    def someAction = { evt = null ->
        // do some stuff
    }
}

It is also possible to define actions as methods, however the closure property form is preferred (but not enforced). The caveat is that you would need to translate the method into a MethodClosure when referencing them form a View script. In the following example the action 'action1' is defined as a closure property, whereas the action 'action2' is defined as a method

application(title: 'Action sample', pack: true) {
    gridLayout(cols: 2, rows: 1) {
        button 'Action 1', actionPerformed: controller.action1
        button 'Action 2', actionPerformed: controller.&action2
    }
}

Actions must follow these rules in order to be considered as such:

  • must have public (Java) or default (Groovy) visibility modifier.
  • name does not match an event handler, i.e, it does not begin with on.
  • must pass GriffonClassUtils.isPlainMethod() if it's a method.
  • must have void as return type if it's a method.
  • value must be a closure (including curried method pointers) if it's a property.

Controllers can perform other tasks:

8.1.1 Actions and Threads

A key aspect that you must always keep in mind is proper threading. Often times controller actions will be bound in response to an event driven by the UI. Those actions will usually be invoked in the same thread that triggered the event, which would be the UI thread. When that happens you must make sure that the executed code is short and that it quickly returns control to the UI thread. Failure to do so may result in unresponsive applications.

The following example is the typical use case that must be avoided

class BadController {
    def badAction = {
        def sql = Sql.newInstance(
            app.config.datasource.url,
            model.username,
            model.password,
            app.config.datasource.driver
        )
        model.products.clear()
        sql.eachRow("select * from products") { product ->
            model.products << [product.id, product.name, product.price]
        }
        sql.close()
    }
}

There are two problems here. First the database connection is established inside the UI thread (which takes precious milliseconds or even longer), then a table (which could be arbitrarily large) is queried and each result sent to a List belonging to the model. Assuming that the list is bound to a Table Model then the UI will be updated constantly by each added row; which happens to be done all inside the UI thread. The application will certainly behave slow and sluggish, and to top it off the user won't be able to click on another button or select a menu item until this actions has been processed entirely.

The Threading chapter discusses with further detail the options that you have at your disposal to make use of proper threading constructs. Here's a quick fix for the previous controller

class GoodController {
    def goodAction = {
        execOutsideUI {
            def sql = null
            try {
                sql = Sql.newInstance(
                    app.config.datasource.url,
                    model.username,
                    model.password,
                    app.config.datasource.driver
                )
                List results = []
                sql.eachRow("select * from products") { product ->
                    results << [product.id, product.name, product.price]
                }
                execInsideUIAsync {
                    model.products.clear()
                    model.addAll(results)
                }
            } finally {
                sql?.close()
            }
        }
    }
}

However starting with Griffon 0.9.2 you're no longer required to surround the action code with execOutsideUI as the ActionManager will do it for you, as long as you invoke the action using the ActionManager facilities. This feature breaks backward compatibility with previous releases so it's possible to disable it altogether by specifying the following flag in Config.groovy

griffon.disable.threading.injection=true

This feature can be partially enabled/disabled too. You can specify with absolute precision which actions should have this feature enabled or disabled, by adding the following settings to griffon-app/conf/Config.groovy

controller {
    threading {
        sample {
            SampleController {
                action1 = false
                action2 = true
            }
            FooController = false
        }
        bar = false
    }
}

The ActionManager will evaluate these settings as follows:

  • the action identified by sample.SampleController.action1 will not have automatic threading injected into its code, while sample.SampleController.action2 (and any other found in the same controller) will have it.
  • all actions belonging to sample.FooController will not have automatic threading injected.
  • all actions belonging to all controllers in the bar package will not have threading injected.

8.1.2 The Action Manager

Controller actions may automatically be wrapped and exposed as toolkit specific actions in a group's builder; this greatly simplifies how actions can be configured based on i18n concerns.

At the heart of this feature lies the GriffonControllerActionManager. This component is responsible for instantiating, configuring and keeping references to all actions per controller. It will automatically harvest all action candidates from a Controller once it has been instantiated. Each action has all of its properties configured following this strategy:

  • match <controller.class.name>.action.<action.name>.<key>
  • match application.action.<action.name>.<key>

<action.name> should be properly capitalized. In other words, you can configure action properties specifically per Controller or application wide. Available keys are

KeyDefault value
nameGriffonNameUtils.getNaturalName() applied to the action's name - 'Action' suffix (if it exists)
acceleratorundefined
short_descriptionundefined
long_descriptionundefined
mnemonicundefined
small_iconundefined; should point to an URL in the classpath
large_iconundefined; should point to an URL in the classpath
enabledundefined; boolean value
selectedundefined; boolean value

Values must be placed in resources files following the internationalization configuration guidelines.

Example

The following Controller defines four actions, the first two as closure properties while the others as methods. Two actions have an 'Action' suffix in their names.

package sample
import java.awt.event.ActionEvent
class SampleController {
    def newAction = { … }
    def open = { … }
    void close(ActionEvent evt) { … }
    void deleteAction(ActionEvent evt) { … }
}

The actions new and delete use the 'Action' suffix in order to avoid compilation errors given that they make use of reserved keywords. It's all the same to the GriffonControllerActionManager as it will generate the following variables in the group's builder: newAction, openAction, closeAction and deleteAction. ActionManager expects the following keys to be available in the application's i18n resources (i.e. griffon-app/i18n/messages.properties)

sample.SampleController.action.New.name = New
sample.SampleController.action.Open.name = Open
sample.SampleController.action.Close.name = Close
sample.SampleController.action.Delete.name = Delete
# additional keys per action elided

In the case that you'd like the close action to be customized for all controllers, say using the Spanish language, then you'll have to provide a file named griffon-app/i18n/messages_es.properties with the following keys

application.action.Close.name = Cerrar

Make sure to remove any controller specific keys when reaching for application wide configuration.

8.1.3 Action Interceptors

Action Interceptors open a new set of possibilities by allowing developers and plugin authors to define code that should be executed before and after any controller action is executed by the framework. For example, you may want to protect the execution of a particular action given specific permissions; the shiro uses annotations that are handled by an Action Interceptor, like this

import griffon.plugins.shiro.annotation.*

@RequiresAuthentication class PrinterController { @RequiresPermission('printer:print') def print = { … }

@RequiresRoles('administrator') def configure = { … } }

The scaffolding plugin on the other hand modifies the arguments sent to the action. Take the following snippet for example

import griffon.plugins.shiro.annotation.*
import org.apache.shiro.authc.UsernamePasswordToken
import griffon.plugins.shiro.SubjectHolder
import javax.swing.JOptionPane

class StrutsController { @RequiresGuest def login = { LoginCommandObject cmd -> try { SubjectHolder.subject.login(new UsernamePasswordToken(cmd.username, cmd.password)) } catch(Exception e) { JOptionPane.showMessageDialog( app.windowManager.findWindow('mainWindow'), 'Invalid username and/or password', 'Security Failure', JOptionPane.ERROR_MESSAGE) } }

@RequiresAuthentication def logout = { SubjectHolder.subject.logout() } }

Note that the login action requires an instance of LoginCommandObject. The scaffolding plugin is aware of this fact; it will create an instance of said class, wire up an scaffolded view in a dialog and present it to the user. The LoginCommandObject instance will be set as the action's arguments if it validates successfully, otherwise action execution is aborted.

Implementing an Action Interceptor

Action interceptors must implement the griffon.core.controller.GriffonControllerActionInterceptor interface. There's a handy base class (org.codehaus.griffon.runtime.core.controller.AbstractGriffonControllerActionInterceptor) that provides sensible defaults. Say you'd want to know how much time it took for an action to be executed, also if an exception occurred during its execution. This interceptor could be implemented as follows

class TracerGriffonControllerActionInterceptor extends AbstractGriffonControllerActionInterceptor {
    private final Map<String, Long> TIMES = [:]

Object[] before(GriffonController controller, String actionName, Object[] args) { TIMES[qualifyActionName(controller, actionName)] = System.nanoTime() return super.before(controller, actionName, args) }

void after(ActionExecutionStatus status, GriffonController controller, String actionName, Object[] args) { String qualifiedActionName = qualifyActionName(controller, actionName) long time = TIMES[qualifiedActionName] - System.nanoTime() println("Action ${qualifiedActionName} took ${time} ns [${status}]") } }

The griffon.core.controller.GriffonControllerActionInterceptor interface defines a handful of methods that are invoked by the GriffonControllerActionManager at very specific timings during the lifetime and execution of controller actions.

  • void configure(GriffonController controller, String actionName, Method method)
  • void configure(GriffonController controller, String actionName, Field closure)

The configure() method is called during the configuration phase, when the Action Manager creates the actions. There are two variants of this method, one that accepts a Groovy closure, the other that takes a Java method as argument. These methods are called once in the lifetime of an action.

  • Object before(GriffonController controller, String actionName, Object args)

The before() method is invoked every time an action is about to be executed. This method is responsible for adjusting the arguments (if needed) or aborting the action execution altogether. Any exception thrown by an interceptor in this method will halt action execution however only griffon.core.controller.AbortActionExecution is interpreted as a graceful abort.

  • boolean exception(Exception exception, GriffonController controller, String actionName, Object args)

The exception() method is invoked only when an exception occurred during the action's execution. Implementors must return true if the exception was handled successfully. The exception will be rethrown by the Action Manager if no interceptor handled the exception. This happens as the last step of the action interception procedure.

  • void after(ActionExecutionStatus status, GriffonController controller, String actionName, Object args)

The after() method is called after an action has been executed. Any exceptions occurred during the action's execution should have been handled by exception(). The status argument specifies if the action was successfully executed (OK), if it was aborted by an interceptor (ABORTERD) or if an exception occurred during its execution (EXCEPTION).

Action interceptors are instantiated using the application's internal instantiation mechanism, thus allowing interceptors to participate in Dependency Injection driven by api:artifactEvents events.

Configuration

Action Interceptors are delivered to applications by addons. Addons now have an actionInterceptors property that define the interceptors they provide. For example, say an addon provides the Tracer interceptor we just defined in the previous section. That particular interceptor can be registered as follows

class TracerGriffonAddon {
    Map actionInterceptors = [
        tracer: [
            interceptor: 'com.acme.plugins.tracer.TracerGriffonControllerActionInterceptor'
        ]
    ]
}

Addons may register as many interceptors as needed. Interceptors may also depend on other interceptor, for example

class TracerGriffonAddon {
    Map actionInterceptors = [
        tracer: [
            transactional: 'com.acme.plugins.tracer.TransactionalGriffonControllerActionInterceptor'
        ]
        tracer: [
            dependsOn: ['transactional']
            interceptor: 'com.acme.plugins.tracer.TracerGriffonControllerActionInterceptor'
        ]
    ]
}

Now the tracer interceptor depends on transactional, which means transactional will be called before tracer. Interceptors may define dependencies on interceptors provided by other addons, just specify the names in their respective dependsOn: property.

It's also possible to globally override the order of execution of interceptors, or define and order when interceptors are orthogonal. Take for example the security interceptor provided by the shiro plugin and the scaffolding interceptor provided by scaffolding. These interceptors know nothing about each other however security should be called before scaffolding. This can be accomplished by adding the following snippet to Config.groovy

griffon.controller.action.interceptor.order = ['security', 'scaffolding']

8.2 Services

Services are responsible for the application logic that does not belong to a single controller. They are meant to be treated as singletons, injected to MVC members by following a naming convention. Services are optional artifacts, and as such there is no default folder created for them when a new application is created.

Services must be located inside the griffon-app/services directory with a Service suffix. The create-service command performs this job for you; also adding a unit test for the given service.

Let's say you need to create a Math service, the command to invoke would be

griffon create-service math

This results in the following files being created

  • griffon-app/services/MathService.groovy - the service class.
  • test/unit/MathServiceTests.groovy - service unit test.

A trivial implementation of an addition operation performed by the MathService would look like the following snippet

class MathService {
    def addition(a, b) { a + b }
}

Using this service from a Controller is a straight forward task. As mentioned earlier services will be injected by name, which means you only need to define a property whose name matches the uncapitalized service name, for example

class MyController {
    def mathService

def action = { model.result = mathService.addition model.a, model.b } }

The type of the service class is optional as basic injection is done by name alone.

Service injection is trivial, it does not provide a full blown DI, in other words further service dependencies will not be resolved. You will need a richer DI solution in order to achieve this, fortunately there are Spring and Weld plugins that do this and more.

Given that services are inherently treated as singletons they are also automatically registered as application event listeners. Be aware that services will be instantiated lazily which means that some events might not reach a particular service if it has not been instantiated by the framework by the time of event publication. It's also discouraged to use the @Singleton annotation on a Service class as it will cause trouble with the automatic singleton management Griffon has in place.

Lastly, all services instances will become available through an instance of type griffon.core.ServiceManager. This helper class exposes available services via a Map. You can query all currently available services in the following manner

app.serviceManager.services.each { name, instance ->
   // do something cool with services
}

You can also query for a particular service instance in the following way

def fooService = app.serviceManager.findService('foo')

It's worth mentioning that the previous method will instantiate the service if it wasn't available up to that point.

All services are instantiated lazily by default. You can change this behavior by adding a configuration flag to Config.groovy

griffon.services.eager.instantiation = true

8.2.1 Service Lifecycle

Services participate in a lifecycle as they are automatically managed by the application. The GriffonService interface defines a pair of methods that every service may override.

public interface GriffonService extends GriffonArtifact {
    void serviceInit();
    void serviceDestroy();
}

The first one is called immediately after the service has been instantiated by the ServiceManager. This is the right place to put initialization code. The app instance should be already set on the service instance, giving you direct access to the application's configuration and i18n facilities.

The second method is also called by ServiceManager when the application is shutting down. Be aware that this method will be called before MVC groups are destroyed.

8.2.2 Service Configuration DSL

Services may have some of its properties defined in external configuration files, for example in Config.groovy, using a simple DSL. Take for example the following service class

class NetworkService {
    String host
    int port
    private Server server

void connect() { if (!server) { server = new Server(host, port) } } }

This service declares 2 public properties (host and port) but does not define any values for them. Inside Config.groovy we find the following definitions

services {
    network {
        host = 'http://acme.com'
        port = 1234
    }
}

The rules are simple to understand given this example.

  • The entry point is the top services section.
  • Each child node identifies the target service by name. Notice that the Service suffix is omitted.
  • Each property within a service block will be set on the service instance.