5. Application Overview

5.1 Directory Structure

Here's a more detailed explanation of each directory within the application's structure

5.2 The MVC Pattern

All Griffon applications operate with a basic unit called the MVC group. An MVC group is comprised of 3 member parts: Models, Views and Controllers. However it is possible to add (or even remove) members from an MVC group by carefully choosing a suitable configuration.

MVC groups configuration is setup in Application.groovy located inside griffon-app/conf. This file holds an entry for every MVC group that the application has (not counting those provided by plugins/addons).

Here's how a typical MVC group configuration looks like

mvcGroups {
    // MVC Group for "sample"
    'sample' {
        model      = 'sample.SampleModel'
        view       = 'sample.SampleView'
        controller = 'sample.SampleController'
    }
}

The order of the members is very important, it determines the order in which each member will be initialized. In the previous example both model and view will be initialized before the controller. Do not mistake initialization for instantiation, as initialization relies on calling mvcGroupInit on the group member.

MVC group configurations accept a special key that defines additional configuration for that group, as it can be seen in the following snippet

mvcGroups {
    // MVC Group for "sample"
    'sample' {
        model      = 'sample.SampleModel'
        view       = 'sample.SampleView'
        controller = 'sample.SampleController'
    }

// MVC Group for "foo" 'foo' { model = 'sample.FooModel' view = 'sample.FooView' controller = 'sample.FooController' config { key = 'bar' } } }

Values placed under this key become available to MVC members during the call to mvcGroupInit, as part of the arguments sent to that method. Here's how the FooController can reach the key defined in the configuration

class FooController {
    void mvcGroupInit(Map args) {
        println args.configuration.config.key
    }
}

While being able to set additional values under this key is a certainly an advantage it would probably be better if those values could be mutated or tweaked, probably treating them as variables, effectively making a group configuration work as a template. For that we'll have to discuss the mvcGroupManager first.

5.2.1 MVCGroupManager

This class is responsible for holding the configuration of all MVC groups no matter how they were defined, which can be either in Application.groovy or in an addon descriptor.

During the startup sequence an instance of MVCGroupManager will be created and initialized. Later the application will instruct this instance to create all startup groups as required. MVCGroupManager has a handful set of methods that deal with MVC group configuration alone; however those that deal with group instantiation come in 3 versions, with 2 flavors each (one Groovy, the other Java friendly).

Locating a group configuration is easily done by specifying the name you're interested in finding

def configuration = app.mvcGroupManager.findConfiguration('foo')

Once you have a configuration reference you can instantiate a group with it by calling any of the variants of the create method

def configuration = app.mvcGroupManager.findConfiguration('foo')
def group1 = configuration.create('foo1')
def group2 = configuration.create('foo2', [someKey: 'someValue'])
// the following will make the group's id match its name
def group3 = configuration.create()
def group4 = configuration.create(someKey: 'someValue')

Be careful that creating groups with the same name is usually not a good idea. The default MVCGroupManager will complain when this happens and will automatically spit out an exception. This behavior may be changed by setting a configuration key in Config.groovy

griffon.mvcid.collision = 'warning' // accepted values are 'warning', 'exception' (default)

The manager will log a warning and destroy the previously existing group before instantiating the new one when 'warning' is the preferred strategy

Now, even though you can create group instances based on their configurations the preferred way is to call any of createMVCGroup, buildMVCGroup or withMVCGroup methods. These methods are available to the app property every GriffonArtifact has, which points to the currently running application. Griffon artifacts also inherit these methods as part of their default contract. Finally, any class annotated with the MVCAware AST transformation will also gain access to these methods.

Groups will be available by id regardless of how they were instantiated. You can ask the mvcGroupManager for a particular group at any time, for example

def g1 = app.mvcGroupManager.groups.foo1
def g2 = app.mvcGroupManager.findGroup('foo1')
def g3 = app.mvcGroupManager.foo1
assert g1 == g2
assert g1 == g3

It's also possible to query all models, views, controllers and builders on their own. Say you'd want to inspect all currently instantiated models, this is how it can be done

app.mvcGroupManager.models.each { model ->
    // do something with model
}

5.2.2 MVCGroups and Configuration

Now that you know there are several ways to instantiate MVC groups we can go back to customizing them.

The simples way is to pass in new values as part of the arguments map that mvcGroupInit receives, for example

def group = app.mvcGroupManager.buildMVCGroup('foo', [key: 'foo'])

However is you wish to use the special config key that every MVC group configuration may have then you must instantiate the group in the following way

def configuration = app.mvcGroupManager.cloneMVCConfiguration('foo', [key: 'someValue'])
def group = configuration.create()

Note that you can still send custom arguments to the create() method.

5.2.3 Configuration Options

The following options are available to all MVC groups as long as you use the config key.

Disabling Lifecycle Events

Every MVC group triggers a few events during the span of its lifetime. These events will be sent to the event bus even if no component is interested in handling them. There may be times when you don't want these events to be placed in the event bus in order to speed up group creation/destruction. Use the following configuration to gain this effect:

mvcGroups {
    // MVC Group for "sample"
    'sample' {
        model      = 'sample.SampleModel'
        view       = 'sample.SampleView'
        controller = 'sample.SampleController'
        config {
            events {
                lifecycle = false
            }
        }
    }
}

The following events will be disabled with this setting:

Disabling Instantiation Events

The Griffon runtime will trigger an event for every artifact it manages. As with the previous events this one will be sent to the event bus even if no component handles it. Skipping publication of this event may result in a slight increase of speed during group instantiation. Use the following configuration to gain this effect:

mvcGroups {
    // MVC Group for "sample"
    'sample' {
        model      = 'sample.SampleModel'
        view       = 'sample.SampleView'
        controller = 'sample.SampleController'
        config {
            events {
                instantiation = false
            }
        }
    }
}

The following events will be disabled with this setting:

Disabling Controllers as Application Event Listeners

Controllers are registered as application event handlers by default when a group in instantiated. This makes it very convenient to have them react to events placed in the application's event bus. However you may want to avoid this automatic registration altogether, as it can lead to performance improvements. You can disable this feature with the following configuration:

mvcGroups {
    // MVC Group for "sample"
    'sample' {
        model      = 'sample.SampleModel'
        view       = 'sample.SampleView'
        controller = 'sample.SampleController'
        config {
            events {
                listener = false
            }
        }
    }
}

You can still manually register a controller as an application event handler at any time, with the caveat that it's now your responsibility to unregister it when the time is appropriate, most typically during the group's destroy sequence.

5.3 Application Lifecycle

Every Griffon application goes through the same life cycle phases no matter in which mode they are running, with the exception of applet mode where there is an additional phase due to the intrinsic nature of applets. The application's lifecycle has been inspired by JSR-296, the Swing Application Framework.

Every phase has an associated life cycle script that will be invoked at the appropriate time. These scripts are guaranteed to be invoked inside the UI thread (the Event Dispatch Thread in Swing). The script names match each phase name; you'll find them inside griffon-app/lifecycle.

5.3.1 Initialize

The initialization phase is the first to be called by the application's life cycle. The application instance has just been created and its configuration has been read. No other artifact has been created at this point, which means that event publishing and the ArtifactManager are not yet available to the script's binding.

This phase is typically used to tweak the application for the current platform, including its Look & Feel.

Addons will be initialized during this phase.

The Initialize script will be called right after the configuration has been read but before addons are initialized. You wont have access to addon contributions.

5.3.2 Startup

This phase is responsible for instantiating all MVC groups that have been defined in the application's configuration (Application.groovy) and that also have been marked as startup groups in the same configuration file.

The Startup script will be called after all MVC groups have been initialized.

5.3.3 Ready

This phase will be called right after Startup with the condition that no pending events are available in the UI queue. The application's main frame will be displayed at the end of this phase.

5.3.4 Shutdown

Called when the application is about to close. Any artifact can invoke the shutdown sequence by calling shutdown() on the app instance.

The Shutdown script will be called before any ShutdownHandler or event handler interested in the ShutdownStart event.

5.3.5 Stop

This phase is only available when running on applet mode. It will be called when the applet container invokes destroy() on the applet instance.

5.4 Application Events

Applications have the ability to publish events from time to time to communicate that something of interest has happened at runtime. Events will be triggered by the application during each of its life cycle phases, also when MVC groups are created and destroyed.

All application event handlers are guaranteed to be called in the same thread that originated the event.

Any artifact or class can trigger an application event, by routing it through the reference to the current running application instance. All artifacts posses an instance variable that points to that reference. All other classes can use ApplicationHolder to gain access to the current application's instance.

Publishing an event can be done synchronously on the current thread or asynchronously relative to the UI thread. For example, the following snippet will trigger an event that will be handled in the same thread, which could be the UI thread itself

app.event('MyEventName', ['arg0', 'arg1'])

Whereas the following snippet guarantees that all event handlers that are interested in an event of type MyEventName will be called outside of the UI thread

app.eventOutside('MyEventName', ['arg0', 'arg1'])

Finally, if you'd want event notification to be handed in a thread that is not the current one (regardless if the current one is the UI thread or not) then use the following method

app.eventAsync('MyEventName', ['arg0', 'arg1'])

There may be times when event publishing must be stopped for a while. If that's the case then you can instruct the application to stop delivering events by invoking the following code

app.eventPublishingEnabled = false

Any events sent through the application's event bus will be discarded after that call; there's no way to get them back or replay them. When it's time to enable the event bus again simply call

app.eventPublishingEnabled = true

5.4.1 Life Cycle Events

The following events will be triggered by the application during each one of its phases

5.4.2 Artifact Events

The following events will be triggered by the application when dealing with artifacts

These events will be triggered when dealing with MVC groups

5.4.3 Miscellaneous Events

These events will be triggered when a specific condition is reached

5.4.4 Custom Events

Any artifact that holds a reference to the current application may trigger events at its leisure by calling the event() or eventAsync methods on the application instance. The following example demonstrates how a Controller triggers a "Done" event after an action has finished

class MyController {
    def action = { evt = null ->
        // do some work
        app.event('Done')
    }
}

There are two versions of the event() method. The first takes just the name of the event to be published; the second accepts an additional argument which should be a List of parameters to be sent to every event handler. Event handlers notified by this method are guaranteed to process the event in the same thread that published it. However, if what you need is to post a new event and return immediately then use the eventAsync variants. If you want the event to be handled outside of the UI thread then use the eventOutsideUI() variants.

5.4.5 Event Handlers

Any artifact or class that abides to the following conventions can be registered as an application listener, those conventions are:

There is a general, per application, script that can provide event handlers. If you want to take advantage of this feature you must define a script named Events.groovy inside griffon-app/conf. Lastly both Controller and Service instances are automatically registered as application event listeners. This is the only way to declare event listeners for Log4jConfigStart and BootstrapStart events.

You can also write a class named Events.java in src/main as an alternative to griffon-app/conf/Events.groovy, but not both!

These are some examples of event handlers:

File: griffon-app/conf/Events.groovy

onBootstrapEnd = { app ->
  println """Application configuration has finished loading.
MVC Groups will be initialized now."""
}

File: src/main/Events.java

import griffon.util.ApplicationHolder;

public class Events { public void onUncaughtExceptionThrown(Exception e) { ApplicationHolder.getApplication().shutdown(); } }

File: griffon-app/controller/MyController.groovy

class MyController {
  def onShutdownStart = { app ->
    println "${app.config.application.title} is shutting down"
  }
}

File: griffon-app/controller/MyController.groovy

class MyController {
  void mvcGroupInit(Map args) {
    app.addApplicationListener([
      Foo: {-> println 'got foo!' }
    ])
  }

def fooAction = { evt = null -> // do something app.event('Foo') } }

File: griffon-app/controller/MyController.groovy

class MyController {
  void mvcGroupInit(Map args) {
    app.addApplicationListener('Foo'){-> println 'got foo!' }
  }

def fooAction = { evt = null -> // do something app.event('Foo') } }

File: griffon-app/controller/MyController.java

import java.util.Map;
import griffon.util.RunnableWithArgs;
import org.codehaus.griffon.runtime.core.AbstractGriffonController;

public class MyController extends AbstractGriffonController { public void mvcGroupInit(Map<String, Object> params) { getApp().addApplicationListener("Foo", new RunnableWithArgs() { public void run(Object[] args) { System.out.println("got foo!"); } }); }

public void fooAction() { // do something getApp().event("Foo"); } }

5.4.6 Custom Event Publishers

As the name implies application events are sent system wide. However there is an option to create localized event publishers. Griffon provides a @griffon.transform.EventPublisher AST transformation that you can apply to any class that wishes to be an event publisher.

This AST transformation will inject the following methods to the annotated classes:

Event listeners registered with these classes should follow the same rules as application event handlers (they can be Scripts, classes, maps or closures, and so on).

The following example shows a trivial usage of this feature

@griffon.transform.EventPublisher
class Publisher {
   void doit(String name) {
      publishEvent('arg', [name])
   }

void doit() { publishEvent('empty') } }

class Consumer { String value

void onArg(String arg) { value = 'arg = ' + arg } void onEmpty() { value = 'empty' } }

p = new Publisher() c = new Consumer() p.addEventListener(c) assert !c.value p.doit() assert c.value == 'empty' p.doit('Groovy') assert c.value == 'arg = Groovy'

5.5 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.5.1 Runtime Configuration

The application's runtime configuration is available through the config property of the application instance. This is a ConfigObject whose contents are obtained by merging Application.groovy and Config.groovy. Builder configuration is available through the builderConfig property and reflects the contents of Builder.groovy.

However starting with Griffon 0.9.2 there's an alternative for defining the application's configuration. You can now use properties files instead of Groovy scripts. If both properties files and Groovy scripts are available in the classpath then the settings of the scripts will be overriden by those set in the properties file. Each properties file must match the name of the configuration script. The following table shows the conventions

Script FileProperties File
Application.groovyApplication.properties
Config.groovyConfig.properties
Builder.groovyBuilder.properties

An application can change the name of the configuration script but it can not change the name of the configuration properties file.

5.5.2 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

5.5.3 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.5.4 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.5.5 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

There are no default ShutdownHandlers registered with an application.

5.5.6 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.5.7 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.

5.5.8 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

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.5.9 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.5.10 Locating Resources

Resources can be loaded form the classpath using the standard mechanism provided by the Java runtime, that is, ask a ClassLoader instance to load a resource URL or obtain an InputStream that points to the resource.

But the code can get quite verbose, take for example the following view code that locates a text file and displays it on a text component

scrollPane {
    textArea(columns: 40, rows: 20,
        text: this.class.classLoader.getResource('someTextFile.txt').text)
}

In order to reduce visual clutter, also to provide an abstraction over resource location, both GriffonApplication and GriffonArtifact have a new pair of methods that simply working with resources. Those methods are provided by ResourceHandler:

Thus, the previous example can be rewritten this way

scrollPane {
    textArea(columns: 40, rows: 20,
        text: getResourceAsURL('someTextFile.txt').text)
}

In the future Griffon may switch to a modularized runtime, this abstraction will shield you from any problems when the underlying mechanism changes.

These methods can be attached to any non-artifact class at compile time if you apply the @griffon.transform.ResourcesAware AST transformation.

5.5.10 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

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.

5.6 Swing specific

The following features are available to Swing based applications only.

5.6.1 WindowManager

The WindowManager class is responsible for keeping track of all the windows managed by the application. It also controls how these windows are displayed (via a pair of methods: show, hide). WindowManager relies on an instance of WindowDisplayHandler to actually show or hide a window. The default implementation simple shows and hide windows directly, however you can change this behavior by setting a different implementation of WindowDisplayHandler on the application instance.

WindowManager DSL

Starting with Griffon 0.9.2 there's a new DSL for configuring show/hide behavior per window. This configuration can be set in griffon-app/conf/Config.groovy, and here is how it looks

swing {
    windowManager {
        myWindowName = [
            show: {window, app -> … },
            hide: {window, app -> … }
        ]
        myOtherWindowName = [
            show: {window, app -> … }
        ]
    }     
}

The name of each entry must match the value of the Window's name: property. Each entry may have the following options

The first two options have priority over the third one. If one is missing then the WindowManager will invoke the default behavior. There is one last option that can be used to override the default behavior provided to all windows

swing {
    windowManager {
        defaultHandler = new MyCustomWindowDisplayHandler()
    }     
}

You can go a bit further by specifying a global show or hide behavior as shown in the following example

swing {
    windowManager {
        defaultShow = {window, app -> … }
        // defaultHide = {window, app -> … }
        someWindowName = [
            hide: {window, app -> … }
        ]
    }
}

Custom WindowDisplayHandlers

The following example shows how you can animate all managed windows using a dropIn effect for show() and a dropOut effect for hide(). This code assumes you have installed the Effects plugin.

In src/main/Dropper.groovy

import java.awt.Window
import griffon.swing.SwingUtils
import griffon.swing.DefaultWindowDisplayHandler
import griffon.core.GriffonApplication
import griffon.effects.Effects

class Dropper extends DefaultWindowDisplayHandler { void show(Window window, GriffonApplication app) { SwingUtils.centerOnScreen(window) app.execOutsideUI { Effects.dropIn(window, wait: true) } }

void hide(Window window, GriffonApplication app) { app.execOutsideUI { Effects.dropOut(window, wait: true) } } }

Notice that the effects are executed outside of the UI thread because we need to wait for them to finish before continuing, otherwise we'll hog the UI thread.

The second step to get this example to work is to inform the application it should use Dropper to display/hide windows. This a task that can be easily achieved by adding an application event handler, for example in griffon-app/conf/Events.groovy

// No windows have been created before this step
onBootstrapEnd = { app ->
    app.windowDisplayHandler = new Dropper()
}

Custom WindowDisplayHandler implementations set in this manner will be called for all managed windows. You'll loose the ability of using the WindowManager DSL.

Alternatively, you could specify an instance of Dropper as the default handler by changing the WindowManager's configuration to

swing {
    windowManager {
        defaultHandler = new Dropper()
    }
}

The WindowDisplayHandler interface also defines show/hide methods that can manage JInternalFrame instances.

Starting Window

Previous to Griffon 0.9.2 the first window to be displayed during the Ready phase was determined by a simple algorithm: picking the first available window from the managed windows list. With 0.9.2 however, it's now possible to configure this behavior by means of the WindowManager DSL. Simply specify a value for swing.windowManager.startingWindow, like this

swing {
    windowManager {
        startingWindow = 'primary'
    }
}

This configuration flag accepts two types of values:

If no match is found then the default behavior will be executed.

5.7 Artifact API

The Artifact API provides introspection capabilities on the conventions set on each artifact type. The following sections explain further what you can do with this API.

5.7.1 Evaluating Conventions

Every Griffon application exposes all information about its artifacts and addons via a pair of helper classes

ArtifactManager

The ArtifactManager class provides methods to evaluate the conventions within the project and internally stores references to all classes within a GriffonApplication using subclasses of GriffonClass class.

A GriffonClass represents a physical Griffon resources such as a controller or a service. For example to get all GriffonClass instances you can call:

app.artifactManager.allClasses.each { println it.name }

There are a few "magic" properties that the ArtifactManager instance possesses that allow you to narrow the type of artifact you are interested in. For example if you only need to deal with controllers you can do:

app.artifactManager.controllerClasses.each { println it.name }

Dynamic method conventions are as follows:

The GriffonClass interface itself provides a number of useful methods that allow you to further evaluate and work with the conventions. These include:

For a full reference refer to the javadoc API.

AddonManager

The AddonManager class is responsible for holding references to all addons (which are of type griffon.core.GriffonAddon), as well as providing metainformation on each addon via an addon descriptor. The latter can be used to know at runtime the name and version of a particular addon, useful for building a dynamic About dialog for example.

All addons have the same behavior which is explained in detail in section 12.6 Addons.

5.7.2 Adding Dynamic Methods at Runtime

For Griffon managed classes like controllers, models and so forth you can add methods, constructors etc. using the ExpandoMetaClass mechanism by accessing each controller's MetaClass:

class ExampleAddon {
    def addonPostInit(app) {
        app.artifactManager.controllerClasses.each { controllerClass ->
             controllerClass.metaClass.myNewMethod = {-> println "hello world" }
        }
    }
}

In this case we use the app.artifactManager object to get a reference to all of the controller classes' MetaClass instances and then add a new method called myNewMethod to each controller. Alternatively, if you know before hand the class you wish add a method to you can simple reference that classes metaClass property:

class ExampleAddon {
    def addonPostInit(app) {
        String.metaClass.swapCase = {->
            def sb = new StringBuffer()
            delegate.each {
                sb << (Character.isUpperCase(it as char) ? 
                       Character.toLowerCase(it as char) : 
                       Character.toUpperCase(it as char))
            }
            sb.toString()
        }

assert "UpAndDown" == "uPaNDdOWN".swapCase() } }

In this example we add a new method swapCase to java.lang.String directly by accessing its metaClass.

5.7.3 Artifact Types

All Griffon artifacts share common behavior. This behavior is captured by an interface named griffon.core.GriffonArtifact. Additional interfaces with more explicit behavior exist per each artifact type. The following is a list of the basic types and their corresponding interface

Starting with Griffon 0.9.1 the compiler will make sure that each artifact implements its corresponding interface via AST injection. This feature can be very useful when accessing artifacts from languages other than Groovy (see section 13.1 Dealing with Non-Groovy Artifacts to learn more about this kind of artifacts).

AST injection is always enabled unless you disable it as explained in section 4.7.2 Disable AST Injection.

Additionally to each artifact type you will find a companion GriffonClass that is specialized for each type. These specialized classes can be used to discover metadata about a particular artifact. The following is a list of the companion GriffonClass for each of the basic artifacts found in core

Be aware that additional artifacts provided by plugins (such as Charts and Wizards) may provide their own interface and companion GriffonClass. These too will be available when querying the ArtifactManager.

5.8 Archetypes

While it's true that artifact templates can be provided by plugins it simply was not possible to configure how an application is created. Application Archetypes fill this gap by providing a hook into the application creation process. Archetypes can do the following:

So, if your company requires all applications to be built following the same template and basic behavior then you can create an archetype that enforces those constraints. Archetypes are simple zip files with an application descriptor and templates. Despite this, Griffon provides a few scripts that let you manage archetypes

Archetypes are installed per Griffon location under $USER_HOME/.griffon/<version>/archetypes. Archetypes are registered with an application's metadata when creating an application. You can either manually modify the value of 'app.archetype' to a known archetype name or specify an -archetype=<archetypeName> flag when creating a new application.

If no valid archetype is found then the default archetype will be used. Following is the default template for an application archetype

import griffon.util.Metadata

includeTargets << griffonScript('CreateMvc')

target(name: 'createApplicationProject', description: 'Creates a new application project', prehook: null, posthook: null) { createProjectWithDefaults() createMVC()

// to install plugins do the following // Metadata md = Metadata.getInstance(new File("${basedir}/application.properties")) // // for a single plugin // installPluginExternal md, pluginName, pluginVersion // ** pluginVersion is optional ** // // for multiple plugins where the latest version is preferred // installPluginsLatest md, [pluginName1, pluginName2] // // for multiple plugins with an specific version // installPlugins md, [pluginName1: pluginVersion1] } setDefaultTarget(createApplicationProject)

5.8.1 A Fancy Example

This section demonstrates how an archetype can be created and put to good use for building applications.

#1 Create the archetype

The first step is to create the archetype project and its descriptor, which can be done by executing the following command

griffon create-archetype fancy
cd fancy

#2 Tweak the archetype descriptor

Locate the archetype descriptor (application.groovy) and open it in your favorite editor, paste the following snippet

import griffon.util.Metadata

includeTargets << griffonScript('CreateMvc')

target(name: 'createApplicationProject', description: 'Creates a new application project', prehook: null, posthook: null) { createProjectWithDefaults()

argsMap.model = 'MainModel' argsMap.view = 'MainView' argsMap.controller = 'MainController' createMVC()

createArtifact( name: mvcFullQualifiedClassName, suffix: 'Actions', type: 'MainActions', path: 'griffon-app/views')

createArtifact( name: mvcFullQualifiedClassName, suffix: 'MenuBar', type: 'MainMenuBar', path: 'griffon-app/views')

createArtifact( name: mvcFullQualifiedClassName, suffix: 'StatusBar', type: 'MainStatusBar', path: 'griffon-app/views')

createArtifact( name: mvcFullQualifiedClassName, suffix: 'Content', type: 'MainContent', path: 'griffon-app/views')

Metadata md = Metadata.getInstance(new File("${basedir}/application.properties")) installPluginExternal md, 'miglayout' }

setDefaultTarget(createApplicationProject)

This archetype overrides the default templates for Model, View and Controller that will be used for the initial MVC group. It also creates 4 additional files inside griffon-app/views. Finally it installs the latest version of the MigLayout plugin.

#3 Create the artifact templates

According to the conventions laid out in the archetype descriptor there must exist a file under templates/artifacts that matches each one of the specified artifact types, in other words we need the following files

MainModel.groovy

@artifact.package@import groovy.beans.Bindable
import griffon.util.GriffonNameUtils

class @artifact.name@ { @Bindable String status

void mvcGroupInit(Map args) { status = "Welcome to ${GriffonNameUtils.capitalize(app.config.application.title)}" } }

MainController.groovy

@artifact.package@class @artifact.name@ {
    def model
    def view

// void mvcGroupInit(Map args) { // // this method is called after model and view are injected // }

// void mvcGroupDestroy() { // // this method is called when the group is destroyed // }

def newAction = { evt = null -> model.status = 'New action' }

def openAction = { evt = null -> model.status = 'Open action' }

def saveAction = { evt = null -> model.status = 'Save action' }

def saveAsAction = { evt = null -> model.status = 'Save As action' }

def aboutAction = { evt = null -> model.status = 'About action' }

def quitAction = { evt = null -> model.status = 'Quit action' }

def cutAction = { evt = null -> model.status = 'Cut action' }

def copyAction = { evt = null -> model.status = 'Copy action' }

def pasteAction = { evt = null -> model.status = 'Paste action' } }

MainView.groovy

@artifact.package@build(@artifact.name.plain@Actions)
application(title: GriffonNameUtils.capitalize(app.config.application.title),
    pack: true,
    locationByPlatform:true,
    iconImage: imageIcon('/griffon-icon-48x48.png').image,
    iconImages: [imageIcon('/griffon-icon-48x48.png').image,
               imageIcon('/griffon-icon-32x32.png').image,
               imageIcon('/griffon-icon-16x16.png').image]) {
   menuBar(build(@artifact.name.plain@MenuBar))
   migLayout(layoutConstraints: 'fill')
   widget(build(@artifact.name.plain@Content), constraints: 'center, grow')
   widget(build(@artifact.name.plain@StatusBar), constraints: 'south, grow')
}

MainActions.groovy

@artifact.package@import groovy.ui.Console

actions { action( id: 'newAction', name: 'New', closure: controller.newAction, mnemonic: 'N', accelerator: shortcut('N'), smallIcon: imageIcon(resource:"icons/page.png", class: Console), shortDescription: 'New' ) action( id: 'openAction', name: 'Open...', closure: controller.openAction, mnemonic: 'O', accelerator: shortcut('O'), smallIcon: imageIcon(resource:"icons/folder_page.png", class: Console), shortDescription: 'Open' ) action( id: 'quitAction', name: 'Quit', closure: controller.quitAction, mnemonic: 'Q', accelerator: shortcut('Q'), ) action( id: 'aboutAction', name: 'About', closure: controller.aboutAction, mnemonic: 'B', accelerator: shortcut('B') )

action( id: 'saveAction', name: 'Save', closure: controller.saveAction, mnemonic: 'S', accelerator: shortcut('S'), smallIcon: imageIcon(resource:"icons/disk.png", class: Console), shortDescription: 'Save' ) action( id: 'saveAsAction', name: 'Save as...', closure: controller.saveAsAction, accelerator: shortcut('shift S') )

action(id: 'cutAction', name: 'Cut', closure: controller.cutAction, mnemonic: 'T', accelerator: shortcut('X'), smallIcon: imageIcon(resource:"icons/cut.png", class: Console), shortDescription: 'Cut' ) action(id: 'copyAction', name: 'Copy', closure: controller.copyAction, mnemonic: 'C', accelerator: shortcut('C'), smallIcon: imageIcon(resource:"icons/page_copy.png", class: Console), shortDescription: 'Copy' ) action(id: 'pasteAction', name: 'Paste', closure: controller.pasteAction, mnemonic: 'P', accelerator: shortcut('V'), smallIcon: imageIcon(resource:"icons/page_paste.png", class: Console), shortDescription: 'Paste' ) }

MainMenuBar.groovy

@artifact.package@import static griffon.util.GriffonApplicationUtils.*

menuBar = menuBar { menu(text: 'File', mnemonic: 'F') { menuItem(newAction) menuItem(openAction) separator() menuItem(saveAction) menuItem(saveAsAction) if( !isMacOSX ) { separator() menuItem(quitAction) } }

menu(text: 'Edit', mnemonic: 'E') { menuItem(cutAction) menuItem(copyAction) menuItem(pasteAction) }

if(!isMacOSX) { glue() menu(text: 'Help', mnemonic: 'H') { menuItem(aboutAction) } } }

MainContent.groovy

@artifact.package@label('Main content')

MainStatusBar.groovy

@artifact.package@label(id: 'status', text: bind { model.status })

#4 Package and install the archetype

This step is easily done with a pair of command invocations

griffon package-archetype
griffon install-archetype target/package/griffon-fancy-0.1.zip

#5 Use the archetype

Now that the archetype has been installed all that is left is put it to good use, like this

griffon create-app sample --archetype=fancy

This command should generate the following files in the application directory

If you inspect the application.properties file you'll notice that the miglayout plugin has been installed too.

Archetypes can be versioned, installed and released in the same way plugins are.

5.9 Platform Specific

The following sections outline specific tweaks and options available for a particular platform.

5.9.1 Tweaks for a Particular Platform

Griffon will automatically apply tweaks to the application depending on the current platform. However you have the option to specify a different set of tweaks. For example, the following configuration in Config.groovy specifies a different handler for macosx:

platform {
    handler = [
        macosx: 'com.acme.MyMacOSXPlatformHandler'
    ]
}

Now you only need to create such handler, like this:

package com.acme

import griffon.core.GriffonApplication import griffon.util.PlatformHandler

class MyMacOSXPlatformHandler implements PlatformHandler { void handle(GriffonApplication app) { System.setProperty('apple.laf.useScreenMenuBar', 'true') … } }

The following platform keys are recognized by the application in order to locate a particular handler: linux, macosx, solaris and windows.

5.9.2 MacOSX

Applications that run in Apple's MacOSX must adhere to an strict set of rules. We recommend you to have a look at Apple's (Human Interface Guidelines).

Griffon makes it easier to integrate with MacOSX by automatically registering a couple of System properties that make the applicaiton behave like a native one

Java applications running on MacOSX also have the option to register handlers for About, Preferences and Quit menu options. The default handlers will trigger an specific application event each. These events can be disabled with a command flag set in griffon-app/conf/Config.groovy. The following table outlines the events, flags and the default behavior when the flags are enabled

EventFired whenFlagDefault behavior
OSXAboutuser activates About menuosx.noaboutDefault about dialog is displayed
OSXPrefsuser activates Preferences menuosx.noprefsNo Preferences menu is available
OSXQuituser activates Quit menuosx.noquitApplication shutdowns immediately