Building Grails Plugins - Tips And Tricks

26
Building Grails Plugins tips and tricks from the wild

Transcript of Building Grails Plugins - Tips And Tricks

Page 1: Building Grails Plugins - Tips And Tricks

Building Grails Pluginstips and tricks from the wild

Page 2: Building Grails Plugins - Tips And Tricks

About Me• Mike Hugo, Independent Software Developer

• http://piragua.com

• http://WhenWorksForYou.com

• Groovy/Grails since 2007

• Author of several Grails plugins

• code-coverage

• hibernate-stats

• test-template

• greenmail

• build-info

• ????

Page 3: Building Grails Plugins - Tips And Tricks

• Plugins overview

• Build your own plugin

• testing

• modularization

• configuration

• events

• did I mention testing?

what’s on the menu for tonight?

Page 4: Building Grails Plugins - Tips And Tricks

App Plugin

a plugin is just another grails application, with a few extra files* GrailsPlugin.groovy* Install, Uninstall and Upgrade scripts

Page 5: Building Grails Plugins - Tips And Tricks

Cool hooks into the runtime environment of a running grails app, plus ability to add new ‘artefacts’ and participate in reloading events

Page 6: Building Grails Plugins - Tips And Tricks

Existing Plugins

there are a ton of existing plugins from security to Rich UI to searching to...you name it.

Page 7: Building Grails Plugins - Tips And Tricks

common problem - this is one i’ve solved about 6 times. time for a plugin

Page 8: Building Grails Plugins - Tips And Tricks

• grails create-plugin build-status

• cd build-status

• mkdir test/projects

• cd test/projects

• grails create-app statusapp

gives you a client you can use to test your plugin.

why?

Page 9: Building Grails Plugins - Tips And Tricks

development

• make a change to the plugin

• grails package-plugin

• cd test/projects/statusapp/

• grails install-plugin ../../../grails-build-status-0.1.zip

• grails run-app

• wash, rinse, repeat

Page 10: Building Grails Plugins - Tips And Tricks

In Place Plugins

• In the application just created, modify BuildConfig.groovy and add:

• grails.plugin.location."build-status" = "../../.."

• TADA! You’re now working with an in-place plugin

Page 11: Building Grails Plugins - Tips And Tricks

Add Controller (test)

import grails.test.*

class BuildInfoControllerTests extends ControllerUnitTestCase { void testIndex() {

! ! controller.index()! !! ! assertEquals('index', renderArgs.view)! ! assertEquals(['app.version'],

renderArgs.model.buildInfoProperties) }}

Page 12: Building Grails Plugins - Tips And Tricks

Add Controller

class BuildInfoController {

! static final List infoProperties = ['app.version']

def index = { ! ! render view:'index', model:

[buildInfoProperties:infoProperties]! }}

Page 13: Building Grails Plugins - Tips And Tricks

Add the View<html><head> <title>Build Info</title></head><body><div> <g:each in="${buildInfoProperties}" var="prop"> <g:if test="${g.meta(name:prop)}"> <tr> <td>${prop}</td><td><g:meta name="${prop}"/></td> </tr> </g:if> </g:each>

</div></body></html>

Page 14: Building Grails Plugins - Tips And Tricks

Functional Testing

• in the app, install the functional test plugin

• grails create-functional-test

class BuildInfoPageFunctionalTests extends functionaltestplugin.FunctionalTestCase {

void testSomeWebsiteFeature() { get('/buildInfo') assertStatus 200 assertContentContains 'app.version' assertContentContains 'app.grails.version' }}

Page 15: Building Grails Plugins - Tips And Tricks

Introduce i18n

• i18n allows a form of customization

• create a messages.properties in the plugin i18n directory for default values

• override it in the app to test to make sure it works

Page 16: Building Grails Plugins - Tips And Tricks

Introducing Config• Allow the client application the ability to

add or remove properties to display

void testIndex_overrideDefaults(){ mockConfig """ buildInfo.properties.exclude = ['app.version'] buildInfo.properties.add = ['custom.property'] """

controller.index() assertEquals 'index', renderArgs.view

def expectedProperties = controller.buildInfoProperties - 'app.version' expectedProperties = expectedProperties + 'custom.property'

assertEquals expectedProperties, renderArgs.model.buildInfoProperties }

Page 17: Building Grails Plugins - Tips And Tricks

Modularizing Views• Put contents of views into templates

• Allows client to override the default view

//view<html><head> <title>Build Info</title></head><body><div> <table>! <g:render template="info"

plugin="buildstatus"></table></div></body></html>

// template<g:each in="${buildInfoProperties}" var="prop"> <g:if test="${g.meta(name:prop)}"> <tr> <td>

<g:message code="${prop}"/></td>

! ! ! <td><g:meta name="${prop}"/>

</td> </tr> </g:if></g:each>

Page 18: Building Grails Plugins - Tips And Tricks

Events

• We want to capture the start of WAR file being built and log the Date/Time

• What events are available?

• Search $GRAILS_HOME/scripts for “event”

• What variables are available?

• binding.variables.each {println it}

http://grails.org/doc/latest/guide/4.%20The%20Command%20Line.html#4.3%20Hooking%20into%20Events

Page 19: Building Grails Plugins - Tips And Tricks

test it

• grails.test.AbstractCliTestCase

• Thank you Peter Ledbrook:http://www.cacoethes.co.uk/blog/groovyandgrails/testing-your-grails-scripts

http://grails.org/doc/latest/guide/4.%20The%20Command%20Line.html#4.3%20Hooking%20into%20Events

Page 20: Building Grails Plugins - Tips And Tricks

import grails.test.AbstractCliTestCaseimport java.util.zip.ZipFile

class CreateWarEventTests extends AbstractCliTestCase {

void testCreateWar(){ execute (['war', '-non-interactive'])

assertEquals 0, waitForProcess()

verifyHeader() Properties props = new Properties() props.load(new ZipFile('target/app-0.1.war').

getInputStream('WEB-INF/classes/application.properties'))

assertNotNull props['build.date'] }

}

Page 21: Building Grails Plugins - Tips And Tricks

eventCreateWarStart = {warname, stagingDir -> Ant.propertyfile(file:

"${stagingDir}/WEB-INF/classes/application.properties") { entry(key: 'build.date', value: new Date()) }}

Page 22: Building Grails Plugins - Tips And Tricks

return the favor

• publish your own events to allow clients to hook into plugin workflow

eventCreateWarStart = {warname, stagingDir -> event("BuildInfoAddPropertiesStart", [warname, stagingDir])

Ant.propertyfile(file: "${stagingDir}/WEB-INF/classes/application.properties") {

entry(key: 'build.date', value: new Date()) }

event("BuildInfoAddPropertiesEnd", [warname, stagingDir])}

Page 24: Building Grails Plugins - Tips And Tricks

in-place plugin caveats

• Reloading plugin artifacts doesn’t always work

• Grails 1.1.1

• see next slide

• Grails 1.2.1

• Plugin controllers lose GrailsPlugin annotation and views cannot be resolved after reloadinghttp://jira.codehaus.org/browse/GRAILS-5869

Page 25: Building Grails Plugins - Tips And Tricks

def watchedResources = ["file:${getPluginLocation()}/web-app/**", "file:${getPluginLocation()}/grails-app/controllers/**/*Controller.groovy", "file:${getPluginLocation()}/grails-app/services/**/*Service.groovy", "file:${getPluginLocation()}/grails-app/taglib/**/*TagLib.groovy"]

def onChange = { event -> if (!isBasePlugin()) { if (event.source instanceof FileSystemResource && event.source?.path?.contains('web-app')) { def ant = new AntBuilder() ant.copy(todir: "./web-app/plugins/PLUGIN_NAME_HERE-${event.plugin.version}") { fileset(dir: "${getPluginLocation()}/web-app") } } else if (application.isArtefactOfType(ControllerArtefactHandler.TYPE, event.source)) { manager?.getGrailsPlugin("controllers")?.notifyOfEvent(event) // this injects the tag library namespaces back into the controller after it is reloaded manager?.getGrailsPlugin("groovyPages")?.notifyOfEvent(event) } else if (application.isArtefactOfType(TagLibArtefactHandler.TYPE, event.source)) { manager?.getGrailsPlugin("groovyPages")?.notifyOfEvent(event) } else if (application.isArtefactOfType(ServiceArtefactHandler.TYPE, event.source)) { manager?.getGrailsPlugin("services")?.notifyOfEvent(event) } } // watching is modified and reloaded. The event contains: event.source, // event.application, event.manager, event.ctx, and event.plugin.}

ConfigObject getBuildConfig() { GroovyClassLoader classLoader = new GroovyClassLoader(getClass().getClassLoader()) ConfigObject buildConfig = new ConfigSlurper().parse(classLoader.loadClass('BuildConfig')) return buildConfig}

String getPluginLocation() { return getBuildConfig()?.grails?.plugin?.location?.'PLUGIN_NAME_HERE'}

Page 26: Building Grails Plugins - Tips And Tricks

the real deal

http://plugins.grails.org/grails-build-info/trunk/

source code available in the grails plugin svn repository, or browse on the web at:http://plugins.grails.org/grails-build-info/trunk/