(Quick Reference)

5.6 Application Features - Reference Documentation

Authors: Andres Almiray

Version: 1.2.0

5.6 Application Features

The GriffonApplication interface defines the base contract for all Griffon applications. However there are some meta enhancements done at runtime to all applications. The following methods become available before the Initialize phase is executed:

5.6.1 Metadata

Access to the application's metadata file (application.properties) is available by querying the griffon.util.Metadata singleton. Here's a snippet of code that shows how to setup a welcome message that displays the application's name and version, along with its Griffon version

import griffon.util.Metadata

def meta = Metadata.current application(title: "Some app", package: true) { gridLayout cols: 1, rows: 2 label "Hello, I'm ${meta['app.name']}-${meta['app.version']}" label "Built with Griffon ${meta['app.griffon.version']}" }

There are also a few helpful methods on this class

  • getApplicationName() - same result as meta['app.name']
  • getApplicationVersion() - same result as meta['app.version']
  • getApplicationToolkit() - same result as meta['app.toolkit']
  • getGriffonVersion() - same result as meta['app.griffon.version']
  • getGriffonStartDir() - returns the value of 'griffon.start.dir' from the System properties
  • getGriffonWorkingDir() - returns a File that points to 'griffon.start.dir' if the value is set and the file is writable, otherwise returns a File pointing to the current location if it is writable; if that fails then attempts to return a File pointing to 'user.dir'; if all fail it will return the location to a temporal file, typically '/tmp/${griffonAppName}'.

5.6.2 Environment

A Griffon application can run in several environments, default ones being DEVELOPMENT, TEST and PRODUCTION. An application can inspect its current running environment by means of the griifon.util.Environment enum.

The following example enhances the previous one by displaying the current running environment

import griffon.util.Metadata
import griffon.util.Environment

def meta = Metadata.current application(title: "Some app", package: true) { gridLayout cols: 1, rows: 3 label "Hello, I'm ${meta['app.name']}-${meta['app.version']}" label "Built with Griffon ${meta['app.griffon.version']}" label "Current environment is ${Environment.current}" }

5.6.3 Running Mode

Applications can run in any of the following modes: STANDALONE, WEBSTART or APPLET. The griffon.util.RunMode enum allows access to the current running mode.

This example extends the previous one by adding information on the current running mode

import griffon.util.Metadata
import griffon.util.Environment
import griffon.util.RunMode

def meta = Metadata.current application(title: "Some app", package: true) { gridLayout cols: 1, rows: 3 label "Hello, I'm ${meta['app.name']}-${meta['app.version']}" label "Built with Griffon ${meta['app.griffon.version']}" label "Current environment is ${Environment.current}" label "Current running mode is ${RunMode.current}" }

5.6.4 Shutdown Handlers

Applications have the option to let particular artifacts abort the shutdown sequence and/or perform a task while the shutdown sequence is in process. Artifacts that desire to be part of the shutdown sequence should implement the griffon.core.ShutdownHandler interface and register themselves with the application instance.

The contract of a ShutdownHandler is very simple

  • boolean canShutdown(GriffonApplication app) - return false to abort the shutdown sequence.
  • void onShutdown(GriffonApplication app) - called if the shutdown sequence was not aborted.

There are no default ShutdownHandlers registered with an application.

5.6.5 Application Phase

All applications have the same life-cycle phases. You can inspect in which phase the application is currently on by calling the getPhase() method on an application instance. Valid values are defined by the ApplicationPhase enum : INITIALIZE, STARTUP, READY, MAIN and SHUTDOWN.

5.6.6 Application Locale

Starting with Griffon 0.9 applications have a bound locale property that is initialized to the default Locale. Components can listen to Locale changes by registering themselves as PropertyChangeListeners on the application instance.

The value of this property can be changed at any time. Doing so will also update the value of the default Locale used in the currently running JVM process.

You may specify a configuration flag in Application.groovy that can be used as the initial value for this property, like this

application {
    title = 'Sample'
    startupGroups = ['foo']

locale = 'es

// Should Griffon exit when no Griffon created frames are showing? autoShutdown = true

// If you want some non-standard application class, apply it here //frameClass = 'javax.swing.JFrame' }

This flag accepts instances of java.util.Locale or java.util.String with the following formats:

  • language
  • language_country
  • language_country_variant

5.6.7 Default Imports

Since Griffon 0.9.1 default imports per artifacts are supported. All Groovy based artifacts will resolve classes from the griffon.core and griffon.util packages automatically, there is no longer a need to define imports on those classes unless you require an static import or define an alias. An example of this feature would be as follows.

class MyController {
    void mvcGroupInit(Map args) {
        println Metadata.current.'app.name'
    }
}

The Metadata class is defined in package griffon.util. There are additional imports per artifact type, here's the list of default definitions

  • Model
    • groovy.beans -> @Bindable, @Vetoable
    • java.beans -> useful for all PropertyChange* classes
  • View (when using Swing)
    • java.awt
    • java.awt.event
    • javax.swing
    • javax.swing.event
    • javax.swing.table
    • javax.swing.text
    • javax.swing.tree

The list of imports per artifacts can be tweaked or changed completely at will. You only need to specify a file named META-INF/griffon-default-imports.properties with the following format

<artifact_type> = <comma_separated_package_list>

These are the contents of the default file

views = javax.swing., javax.swing.event., javax.swing.table., javax.swing.text., javax.swing.tree., java.awt., java.awt.event.
models = groovy.beans., java.beans.

Imports are cumulative, this means you a package can't be removed from the default list provided by Griffon.

5.6.8 Startup Arguments

Command line arguments can be passed to the application and be accessed by calling getStartupArgs() on the application instance. This will return a copy of the args (if any) defined at the command line.

Here's a typical example of this feature in development mode

griffon run-app arg0 arg1 argn

Here's another example demonstrating that the feature can be used once the application has been packaged, in this case as a single jar

griffon dev package jar
java -jar dist/jars/app.jar arg0 arg1 argn

5.6.9 Uncaught Exceptions

There are times when an exception catches you off guard. The JVM provides a mechanism for handling these kind of exceptions: Thread.UncaughtExceptionHandler. You can register an instance that implements this interface with a Thread or ThreadGroup, however it's very likely that exceptions thrown inside the EDT will not be caught by such instance. Furthermore, it might be the case that other components would like to be notified when an uncaught exception is thrown. This is precisely what GriffonExceptionHandler does.

Stack traces will be sanitized by default, in other words, you won't see a long list containing Groovy internals. However you can bypass the filtering process and instruct Griffon to leave the stack traces untouched by specifying the following flag either in the command line with -D switch or in Config.groovy

griffon.full.stacktrace = true

Exceptions will be automatically logged using the error level. Should you choose to disable logging but still have some output when an exception occurs then configure the following flag

griffon.exception.output = true

Any exception caught by GriffonExceptionHandler will trigger a pair of events. The event names match the following convention

  • Uncaught<exception.class.simpleName>
  • UncaughtExceptionThrown

The exception is sent as the sole argument of these events. As an example, assume that a service throws an IllegalArgumentException during the invocation of a service method. This method was called from within a Controller which defines a handler for this exception, like this

class SampleService {
    void work() {
        throw new IllegalArgumentException('boom!')
    }
}

class SampleController { def sampleService

def someAction = { sampleService.work() }

def onUncaughtIllegalArgumentException = { iae -> // process exception } }

As mentioned before, the name of an event handler matches the type of the exception it will handle, polymorphism is not supported. In other words, you wont be able to handle an IllegalArgumentException if you declare a handler for RuntimeException. You can however, rely on the second event triggered by this mechanism. Be aware that every single exception will trigger 2 events each time it is caught. It is best to use a synchronization approach to keep track of the last exception caught, like so

import groovy.transform.Synchronized

class SampleController { private lastCaughtException

@Synchronized void onUncaughtRuntimeException(RuntimeException e) { lastCaughtException = e // handle runtime exception only }

@Synchronized void onUncaughtExceptionThrown(e) { if(lastCaughtException == e) return lastCaughtException = e // handle any other exception types } }

As a final remark, any exceptions that might occur during the handling of the events wont trigger GriffonExceptionHandler again, they will simply be logged and discarded instead.