Archive

Posts Tagged ‘Grails Plugin’

The basics of Grails Plugin development

April 11, 2012 1 comment

1. Grails Plugin Development



The grails plugin project is a means to modularise and deliver extra capabilities into a standard grails project

the basic development section of the user guide is starting point currently section 12 in the grials 2.01 build documentation

Grails Plugin development pages

however there are things that are not well explained to novices, so this post outlines what you have to do to get some of the basics right.

This post assumes your using STS as your IDE. Under STS just create a file>new>grails plugin project. let this wizard do its thing. it builds a project for you in your eclipse workspace, and sets view in eclipse to be “grails”. The generated project looks mostly like an ordinary grails project, and can be run as one, with some special elements.

key to this is that in the plugin project top level you will find an GrailsPlugin.groovy. This is the file that provides you all the hooks to get into the grails platform when doing fancy stuff in your plugin.

the plugin descriptor file comes in three sections.
section 1: is basic version details and dependency references
section 2: is basic title/user details etc
section 3: grails build/runtime closures allowing you to hook into the grails application runtime for any project that uses your plugin.

This section looks like

...
def doWithWebDescriptor = { xml ->
// TODO Implement additions to web.xml (optional), this event occurs before
}

def doWithSpring = {
// TODO Implement runtime spring config (optional)
}

def doWithDynamicMethods = { ctx ->
// TODO Implement registering dynamic methods to classes (optional)
}

def doWithApplicationContext = { applicationContext ->
// TODO Implement post initialization spring config (optional)
}

def onChange = { event ->
// TODO Implement code that is executed when any artefact that this plugin is
// watching is modified and reloaded. The event contains: event.source,
// event.application, event.manager, event.ctx, and event.plugin.
}

def onConfigChange = { event ->
// TODO Implement code that is executed when the project configuration changes.
// The event is the same as for 'onChange'.
}

def onShutdown = { event ->
// TODO Implement code that is executed when the application shuts down (optional)
}

1.1 Local development testing



There is documentation on how to publish your plugin – but whilst your are in development mode its best to just include your plugin from your local environment. This is very readily done, by creating an ordinary Grails project, and within the /conf/buildConfig file add the line, which gets your project to auto include your draft plugin development project

grails.plugin.location.yourPlugin = "C:/Users/802518659/Documents/grails-workspace/yourPlugin"


1.2 Finding out about application artefacts



Within a plugin project the developer normally only knows the classes and elements that they provide in the plugin. Once the installed plugin is installed in another project it is surrounded by other classes and elements provided by other plugins and the project itself, so how do you get to effect stuff you havn’t written yourself, read on.

As a plugin developer you are provided with an implicit property called ‘application’ which is an injected from spring DI as an instance of GrailsApplication within the doWith closures. It provides you access to grails artefacts in the runtime environment.

The Grails documentation talks about artefact descriptors. Each artifact has an associated ArtefacDescriptor that provides additional information about it

the application property has a number of properties and methods

property/method description
artefactClasses returns list of artefact descriptors of the type artefact. It should start in lower case as this is a property
getArtefactClass (String artefactName) returns the artefact descriptor of type Artefact for the named artefact instance, e.g getControllerClass(“MyController”). The artefactName must be fully qualfied class name, including package if there is one
isArtefactClass (Class ArtefactClass) returns true if the class is of type Artefact

the ArtefactDescriptor is an interface with the following properties

property type description example
clazz Class the class name of the artefact org.example.MyController
metaClass MetaClass metaClass reference to the classes MetaClass n/a
name String name of the artefact minus any suffix or package My
shortname String name of the artefact minus any package MyController
propertyName String short name of the artefact in property form myController

what this allows you to do is write code like

import org.codehaus.groovy.grails.commons.GrailsClassUtils as GCU

...
doWithDyanamicMethods..{
    application.controllerClasses.each {cClass -> //do something with}
    def x = GCU.getStaticPropertyValue (cClass.clazz, 'x')

    //add dynamic method to meta class
    cClass.metaClass.myDnamicMethod = {name ->

this example uses the grails provided utility function that is useful for accessing grails class data. Note the lower case artefact name application.controllerClasses.each, this is your artefact starting in lower case as its a property (added dynamically to the ‘application’ for you)

1.2.1 Class reloading



Grails wont automatically re attach dynamic methods on a class reload – so you must do this in your plugin to be friendly.

You do this by adding code in the onChange closure and reapply any dynamic methods to the artefact metaclass that you expect to re-appear after a reload.

grails maintains a list of watched resources. What you do is register a interest in the resources. The first means to do this is by observing a plugin which is a itself observing the artefacts you are interested in. This is done like so if your interested in Controller artefacts

def observe = ["controllers"]

This will fire the onChange closure, when a Controller class is reloaded

def onChange = {event -> //do with event ...}

The following hilights which core plugins in grails watch which resources

  1. ‘controllers’ plugin watches Controllers and tag libraries
  2. ‘services’ plugin watches Services
  3. ‘filters’ plugin watches the Filter Classes
  4. ‘urlMappings’ plugin watches the URL mappings
  5. ‘hibernate’ plugin watches the Hibernate Configuration files
  6. ‘i18n’ watches the i18n bundles e.g. messages.properties for example

you can also specify the watchedResources property which will also fire the onChange closure like this

def watchedResources = "file:./grails-app/**/*<Artefact>.groovy"</code>

or

def watchedResources = ["file:./grails-app/**/*<Artefact>.groovy"]

which uses the ant filepath format. ** wildcard matches any number of nested directories

the onChange closure gets passed an event object which has the following properties

  • source : the file or artefact that has changed. If its a class thats changed source will be the java.lang.Class instance; otherwise it will be a spring resource org.springframework.core.io.Resource.

    Whats not clear in the documentation is that you’ll only get the java.lang.Class in the event if its within the /grails-app subtree. If your directory is outside of that its passed into the event as a spring file resource

  • application : bit redundant as closure also has the implicit application property defined
  • ctx : Spring application context from which you can access any beans you may need
  • manager : the plugin manager
  • plugin : the plugin descriptor instance

source is the most useful.

1.3 Adding your own artefacts



Your plugin can add new artefacts to a project by adding them to the grails-app file tree. Note: The plugin is loaded from where it is installed and is NOT copied into the main application tree of the hosting project.

because of some legacy issues your ArtefactHandler and wrapper classes must be built as JAVA class files in your src directory. trying to do this is Groovy causes multiple complilation issues, and i havnt the knowledge to know where to start on that.

it took a long time to sort tihs so i drew up a class diagram of the key elements as follows

UML class model

in grails terms an artefact is a groovy file that meets certain conventions. Your artefact describes these conventions for Grails.

Grails defines a GrailsClass (interface) whose instances represent a single artefact service class along with an associated handler. This is what is collectively called the Artefact API

To support a new artifact type you have to tell the Grails core about the artifact type first. You can achieve this by implementing your own artifact class (public interface), and dfeault implementation class and an ArtifactHandler, which for most of the common cases you’d do by extending from AbstractInjectableGrailsClass and ArtefactHandlerAdapter respectively.

this is the link to the definition for ArtefacthandlerAdapter

so lets take an example – lets say we want to create artefacts of type “Task” (equivalent to to Controller, Domain, etc already defined for you by Grails). This is the pattern suffix type for our artefacts.

1.3.1 Plugin descriptor changes



The first thing to do is to register your new artefact handler with grails in the plugin descriptor

// register the 'Task' artefact handler
    def artefacts = [<package-path>TaskArtefactHandler]

    // watch for any changes in these directories
    // you decide where you expect your resources to be stored 
    // here grails-app/tasks - 
    // note that this should be a subtree within grails-app
    //to get the class in your onChange event
    def watchedResources = [
        "file:./grails-app/tasks/**/*Task.groovy",
        "file:../../plugins/*/tasks/**/*Task.groovy"
    ]

    // Grails calls this when one of the files matching 'watchedResources' changes.
    // We have to actually swap in the new class when the source changes
    // There isn't anything special here, it's just boilerplate.
    def onChange = { event ->
//check if its not a class and handle it - otherwise check for artefact type
if (event.source instanceof org.springframework.core.io.FileSystemResource)
		{
			org.springframework.core.io.FileSystemResource fr = event.source
			println "do something with file resource " + fr.filename
	
		} else if (application.isArtefactOfType(TaskArtefactHandler.TYPE, event.source)) {
            def oldClass = application.getTaskClass(event.source.name)
            application.addArtefact(TaskArtefactHandler.TYPE, event.source)

            // Reload subclasses
            application.someHandlerClasses.each {
                if (it?.clazz != event.source && oldClass.clazz.isAssignableFrom(it?.clazz)) {
                    def newClass = application.classLoader.reloadClass(it.clazz.name)
                    application.addArtefact(TaskArtefactHandler.TYPE, newClass)
                }
            }
        }

this has some boilerplate in the onChange closure. the closure tests to see if the event is a file (outside of the grails-app/ tree) or an artefact grails class instance type and if so registers the grails class with the implicit ‘application’. Grails now knows about your class it was just informed about in the event.

1.3.2 Provide the new artefact handler



We also need an provide our artefact handler implementation (this must be in java for legacy reasons (?)).

This is best done by extending from the grails provided ArtefactHandlerAdapter Class like so. create this in your plugins /src tree


import org.codehaus.groovy.grails.commons.ArtefactHandlerAdapter;

public class TaskArtefactHandler extends ArtefactHandlerAdapter {

    // the name for these artefacts in the application
    static public final String TYPE = "Task";

    // the suffix of all someHandler classes (i.e. how they are identified as someHandlers)
    static public final String SUFFIX = "Task";

    // pass interface type and default impl to the supertype
    public TaskArtefactHandler() {
        super(TYPE, GrailsTaskClass.class, DefaultGrailsTaskClass.class, SUFFIX);
    }
}

and then

1.3.3 Provide artefact grails type (interface) and default implementation



Next we define the public interface for our new artefact by extending from the system provided InjectableGrailsClass like so.

import org.codehaus.groovy.grails.commons.InjectableGrailsClass;

public interface GrailsTaskClass extends InjectableGrailsClass {
//any public methods you'd expect on your Task classes
}

and finally provide a default implementation for this GrailsClass interface (in java?)

import org.codehaus.groovy.grails.commons.AbstractInjectableGrailsClass;
import org.codehaus.groovy.grails.commons.GrailsClassUtils;

public class DefaultGrailsTaskClass extends AbstractInjectableGrailsClass implements GrailsTaskClass {

    public DefaultGrailsTaskClass(Class clazz) {
        super(clazz, TaskArtefactHandler.SUFFIX);
    }

//any methods you define 

}

by extending from AbstractInjectableGrailsClass your new artefact is spring enabled for you.

so what this has done is

  1. Create a plugin that registers an artefact handler for Task handlers
  2. Register for changes to source in grails-app/tasks subtree in the app and plugins
  3. Provide an onChange handler that reloads Task handler classes when they change
  4. Provide an ArtefactHandler implementation that specifies what a Task handler class is
  5. Provide an InjectableGrailsClass interface for Task classes classes
  6. Provide an implementation of that interface that provides the default implementation of the public interface

1.3.4 Update /scripts and provide templates for creating your Tasks



Underneath the src/ directory provide a sample template for your task. Create a directory src.templates.artifacts. Note the spelling difference – its not the same as artefacts we have just been dealing with.

The create your template groovy with the same name as the suffix for your Grails artefacts like so – this template uses ant style susbtitution params

@artifact.package@import com.softwood.*// your imports

class @artifact.name@ {

	static constraints = {}//define constraints etc here 

}

having done that you can add a public script to your GANT scripts folder call this – CreateTask.groovy like so

CreateTask.groovy


includeTargets << grailsScript("_GrailsInit")
includeTargets << grailsScript("_GrailsCreateArtifacts")

target('default': "Creates a new task") {
    depends(checkVersion, parseArguments)

    def type = "Task"
    promptForName(type: type)

	println "create task script argsMap > " + argsMap
    for (name in argsMap["params"]) {
        name = purgeRedundantArtifactSuffix(name, type)
		println "create task script purged name > " + name
		//goes looking in src/templates/artifacts/<artefact>.groovy for template 
        createArtifact(name: name, suffix: type, type: type, path: "grails-app/tasks")
        createUnitTest(name: name, suffix: type, superClass: "TaskUnitTestCase")
    }

}

this reuses the grails provided system scripts to create instances in the grails-app/tests subtree and generate your sample class as Task.grooy from the template you provide.

because your listening for onChange events these will be automatically loaded and away you go

Advertisements
Categories: Grails Tags: ,
%d bloggers like this: