1. Grails: How To Plug In
Burt Beckwith
SpringSource
CONFIDENTIAL
Š 2010 SpringSource, A division of VMware. All rights reserved
2. Agenda
ď§ What Is A Plugin?
ď§ Implementation Details
ď§ Testing
ď§ Source Control and Release Management
ď§ Best Practices and Tips
CONFIDENTIAL 2
3. My Plugins
ď§ Acegi ď§ Flex Scaffold (major rework, not yet released)
ď§ App Info
ď§ SpringMVC
ď§ BlazeDS
ď§ Spring Security Core
ď§ Cloud Foundry
ď§ Spring Security ACL
ď§ Cloud Foundry UI
ď§ Spring Security App Info
ď§ CodeNarc
ď§ Spring Security Kerberos
ď§ Console
ď§ Database Reverse Engineering ď§ Spring Security LDAP
ď§ Database Migrations ď§ Spring Security Open ID
ď§ Datasources ď§ Spring Security CAS
ď§ Dynamic Controller ď§ Spring Security UI
ď§ Dynamic Domain Class (core code) ď§ Twitter
ď§ FamFamFam ď§ UI Performance
ď§ Flex
CONFIDENTIAL 3
5. What Is A Plugin?
â A set of software components that
adds specific abilities to a larger
software application
https://secure.wikimedia.org/wikipedia/en/wiki/Plug-in_(computing)
CONFIDENTIAL 5
6. What Is A Plugin?
ď§ Very similar to a Grails application project
ď§ Plus a *GrailsPlugin.groovy plugin descriptor file
ď§ Use grails createÂplugin to create one
ď§ Often adds artifacts, resources, scripts
ď§ Good for code reuse
ď§ Modular development
CONFIDENTIAL 6
7. What Is A Plugin?
http://blog.springsource.com/2010/06/01/whats-a-plugin-oriented-architecture/
CONFIDENTIAL 7
10. The Background
ď§ Grails is designed to wire
different libraries together and
make them easy to use
ď§ Can be seen as âplatform for
runtime configurationâ
ď§ Without well defined system, de-
coupling those components was
hard
CONFIDENTIAL 10
11. Extension Points
ď§ The Build System
ď§ Spring Application Context
ď§ Dynamic method registration
ď§ Auto Reloading
ď§ Container Config (web.xml)
ď§ Adding new Artefact Types
CONFIDENTIAL 11
12. Types of Plugin
ď§ New functionality
⢠Datasources, UI Performance, etc.
ď§ Wrapper
⢠Spring Security, Searchable, Quartz, etc.
⢠Often have many scripts, e.g. Cloud Foundry, Database
Migration
ď§ UI
ď§ Bug fix
⢠http://grails.org/plugin/error-pages-fix, http://grails.org/plugin/aop-reloading-fix
ď§ Resource-only
⢠famfamfam
CONFIDENTIAL 12
15. What Can A Plugin Do?
ď§ Enhance classes at runtime
⢠Add methods, constructors, properties, etc.
ď§ Perform runtime Spring configuration
ď§ Modify web.xml on the fly
ď§ Add new controllers, tag libraries, etc.
CONFIDENTIAL 15
16. A Basic Plugin
class LoggingGrailsPlugin {
def version = "0.1â
def grailsVersion = "1.3 > *"
def doWithDynamicMethods = {
for (c in application.allClasses) {
c.metaClass.getLog = {->
LogFactory.getLog(c)
}
}
}
}
CONFIDENTIAL 16
19. Application Object
ď§ 'GrailsApplication' object
⢠available with the 'application' variable
⢠holds convention information
ď§ Useful methods like:
⢠get*Classes
⢠Retrieve all GrailsClass instances for particular artifact, e.g.
getControllerClasses()
⢠add*Class(Class clazz)
⢠Adds new GrailsClass for specified class,
e.g. addControllerClass(myClass)
⢠get*Class(String name)
⢠Retrieves GrailsClass for given name,
e.g. getControllerClass(name)
CONFIDENTIAL 19
20. Grails Artifacts
ď§ Some resource that fulfills a convention
⢠controllers, services, etc.
ď§ Represented by the GrailsClass interface
⢠http://grails.org/doc/latest/api/org/codehaus/groovy/grails/
commons/package-summary.html for API of all artifacts
ď§ Add new artifacts via the Artifact API
⢠http://grails.org/Developer+-+Artefact+API
CONFIDENTIAL 20
21. Grails Artifacts
ď§ Design decision: include or generate artifacts?
⢠Include is more automatic, no explicit step
⢠Including requires workflow to override
⢠Generate requires extra step but is more customizable
⢠Generation is static â old files can get out of date
ď§ Overriding domain classes?
⢠Really only an issue with create-drop and update, not with migrations
ď§ Overriding controllers?
⢠âun-mapâ with UrlMappings
"/admin/console/$action?/$id?"(controller: "console")
"/console/$action?/$id?"(controller: "errors", action: "urlMapping")
CONFIDENTIAL 21
22. The GrailsClass Interface
ď§ Implemented by Grails artifacts written in Groovy
⢠getPropertyValue(String name)
⢠newInstance()
⢠getName() â logical name
⢠getFullName() â class name including package
⢠getShortName() â class name without package
⢠getPropertyName() â class name as property name
⢠getClazz() â actual java.lang.Class
CONFIDENTIAL 22
23. Plugin Descriptor
ď§ Metadata
⢠version, grailsVersion range, pluginExcludes, dependsOn, etc.
ď§ Lifecycle Closures
â Modify XML generated for web.xml at
doWithWebDescriptor runtime
doWithSpring â Participate in Spring configuration
â Post ApplicationContext initialization
doWithApplicationContext activities
doWithDynamicMethods â Add methods to MetaClasses
onChange â Participate in reload events
CONFIDENTIAL 23
24. Configuring Spring
class JcrGrailsPlugin {
def version = 0.1 Bean name is method name,
def dependsOn = [core:0.4] first argument is bean class
def doWithSpring = {
jcrRepository(RepositoryFactoryBean) {
configuration = "classpath:repository.xml"
homeDir = "/repo"
}
} Set properties on the bean
}
CONFIDENTIAL 24
25. Advantages Of Spring DSL
ď§ Just Groovy code, so can contain complex logic
dependent on:
⢠environment
⢠project conventions
⢠anything!
ď§ Easy to specify complex bean definitions
⢠thanks to Groovy's neat lists and maps syntax
CONFIDENTIAL 25
26. Overriding Spring Beans
ď§ Use loadAfter to define plugin load order; your beans
override other plugins' and Grails'
ď§ resources.groovy loads last, so applications can
redefine beans there
Import com.mycompany.myapp.MyUserDetailsService
beans = {
userDetailsService(MyUserDetailsService) {
grailsApplication = ref('grailsApplication')
foo = 'bar'
}
}
ď§ BeanPostProcessor/BeanFactoryPostProcessor
CONFIDENTIAL 26
27. Plugging In Dynamic Methods
Taken from Grails core, this
def doWithDynamicMethods = { ctx -> plugin finds all classes ending
application with âCodecâ and dynamically
.allClasses creates âencodeAsFooâ methods!
.findAll { it.name.endsWith("Codec") }
.each {clz ->
Object
.metaClass
."encodeAs${clz.name-'Codec'}" = {
clz.newInstance().encode(delegate)
}
}
The âdelegateâ variable
}
is equivalent to âthisâ in
}
regular methods
CONFIDENTIAL 27
28. Reload Events
ď§ Grails apps must be reloadable during
development
⢠Can mean many things
⢠Plugin's responsibility
ď§ Plugins can define
watchedResources
firing onChange events
when modified
CONFIDENTIAL 28
29. Example Reloading Plugin
class I18nGrailsPlugin { Defines set of files to watch
def version = "0.4.2" using Spring Resource pattern
def watchedResources =
"file:../grails-app/i18n/*.properties"
def onChange = { event ->
def messageSource =
event.ctx.getBean("messageSource")
messageSource?.clearCache()
}
}
When one changes, event
is fired and plugin responds
by clearing message cache
CONFIDENTIAL 29
30. The Event Object
ď§ event.source
⢠Source of the change â either a Spring Resource or a
java.lang.Class if the class was reloaded
ď§ event.ctx
⢠the Spring ApplicationContext
ď§ event.application
⢠the GrailsApplication
ď§ event.manager
⢠the GrailsPluginManager
CONFIDENTIAL 30
35. Troubleshooting Dependency Management
ď§ Run grails dependencyÂreport
⢠See http://burtbeckwith.com/blog/?p=624 for
visualization tips
ď§ Increase logging level
⢠log 'warn' â 'info', 'verbose', or 'debug'
CONFIDENTIAL 35
36. Scripts
ď§ Gant: http://gant.codehaus.org/
ď§ Groovy + Ant - XML
ď§ Can be used in apps but more common in plugins
ď§ Put in plugin's (or app's) scripts directory
ď§ _Install.groovy
⢠runs when you run grails installÂplugin or
when it's transitively installed - don't overwrite!
might be reinstall or plugin upgrade
CONFIDENTIAL 36
37. Scripts
ď§ _Uninstall.groovy
⢠runs when you run grails uninstallÂplugin so
you can do cleanup, but don't delete user-
created/edited stuff
ď§ _Upgrade.groovy
⢠runs when you run grails upgrade (not when
you upgrade a plugin)
CONFIDENTIAL 37
38. Scripts
eventCreateWarStart = { name, stagingDir â
ď§ _Events.groovy âŚ
}
⢠Respond to
eventGenerateWebXmlEnd = {
build events âŚ
}
ď§ Naming convention
⢠Prefix with '_' â don't show in grails help
⢠Suffix with '_' â 'global' script, i.e. doesn't need
to run in a project dir (e.g. createÂapp)
CONFIDENTIAL 38
39. Scripts
ď§ Reuse existing scripts with includeTargets
⢠includeTargets << grailsScript("_GrailsWar")
ď§ Include common code in _*Common.groovy
⢠includeTargets << newÂ
File("$cloudFoundryPluginDir/scripts/Â
_CfCommon.groovy")
CONFIDENTIAL 39
40. Testing Scripts
ď§ Put tests in test/cli
ď§ Extend grails.test.AbstractCliTestCase
ď§ stdout and stderr will be in target/cli-output
ď§ http://www.cacoethes.co.uk/blog/groovyandgrails/t
esting-your-grails-scripts
ď§ Run with the rest with grails testÂapp or alone
with grails testÂapp ÂÂother
CONFIDENTIAL 40
45. Testing
ď§ Write regular unit and integration tests in test/unit
and test/integration
ď§ 'Install' using inline mechanism in
BuildConfig.groovy
⢠grails.plugin.location.'myÂplugin' = '../myÂplugin'
ď§ Test scripts as described earlier
ď§ Create test apps programmatically â all of the
Spring Security plugins do this with bash scripts
CONFIDENTIAL 45
46. Testing
ď§ Use build.xml or Gradle to create single target that
tests and builds plugin
<target name='package' description='Package the plugin'
depends='test, doPackage, post-package-cleanup'/>
ď§ Use inline plugin or install from zip
grails install-plugin /path/to/plugin/grails-myplugin-0.1.zip
CONFIDENTIAL 46
48. Becoming A Plugin Developer
ď§ Signup at Codehaus xircles
⢠http://xircles.codehaus.org/signup &
http://xircles.codehaus.org/projects/grails-plugins/members
ď§ Talk about your idea on the mailing list
⢠http://grails.org/Mailing+lists
⢠Summarize your contribution and ask on the dev list
for your request to be processed - include your xircles
user name in the request
ď§ Build (and test!) your plugin and run:
⢠grails publish-plugin
ď§ Done!
CONFIDENTIAL 48
49. Source Control and Release Management
ď§ Internal server for jars & plugins is easy with Artifactory or
Nexus
repositories {
grailsPlugins()
grailsHome()
mavenRepo "http://yourserver:8081/artifactory/libs-releases-local/"
mavenRepo "http://yourserver:8081/artifactory/plugins-releases-local/"
grailsCentral()
}
ď§ Release with grails publishÂplugin ÂÂrepository=repo_name
grails.project.dependency.distribution = {
remoteRepository(
id: "repo_name",
url: "http://yourserver:8081/artifactory/plugins-snapshots-local/") {
authentication username: "admin", password: "password"
}
}
CONFIDENTIAL 49
50. Source Control and Release Management
ď§ Use âreleaseâ plugin http://grails.org/plugin/release
⢠Will be default in 1.4+, publish-plugin replaces release-plugin
⢠Sends tweet from @grailsplugins and email to plugin forum
http://grails-plugins.847840.n3.nabble.com/
⢠Will check code into SVN for you
⢠disable with ÂÂnoScm or grails.release.scm.enabled = false
CONFIDENTIAL 50
52. Best Practices and Tips
ď§ Delete anything auto-generated that you don't use
⢠grails-app/views/error.gsp
⢠_Uninstall.groovy and other generated scripts
⢠Empty descriptor callbacks
ď§ Exclude files used in development or testing
⢠Use def pluginExcludes = [...]
⢠src/docs
⢠Test artifacts
ď§ Use Ivy + Maven repos in BuildConfig.groovy, not lib dir
CONFIDENTIAL 52
53. Best Practices and Tips
ď§ Write tests
ď§ Run them often
ď§ Run tests before commit and especially before
release (use build.xml)
ď§ Create test apps (ideally programmatically)
ď§ Programmatically build inline plugin location:
def customerName = System.getProperty("customer.name")
grails.plugin.location."$customerName" = "customer-plugins/$customerName"
CONFIDENTIAL 53
54. Best Practices and Tips
ď§ Get to 1.0
⢠Initial version 0.1 is just a suggestion
⢠People trust 1.0+ more
ď§ Start using source control early
⢠Easy to run git init locally and later â github
ď§ Support your plugin!
CONFIDENTIAL 54
55. Best Practices and Tips
ď§ http://grails.org/doc/latest/guide/12.%20Plug-ins.html
ď§ http://grails.org/doc/latest/guide/4.%20The
%20Command%20Line.html#4.3%20Hooking%20into
%20Events
ď§ http://www.grails.org/The+Plug-in+Developers+Guide
ď§ http://blog.springsource.com/2010/05/18/managing-
plugins-with-grails-1-3/
CONFIDENTIAL 55