Download - Grails Plugin Best Practices
![Page 1: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/1.jpg)
© 2010 SpringSource, A division of VMware. All rights reserved
CONFIDENTIALCONFIDENTIAL
Grails Plugin Best Practices
Burt Beckwith
SpringSource
@burtbeckwith
https://burtbeckwith.com/blog/
![Page 2: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/2.jpg)
2CONFIDENTIAL 2CONFIDENTIAL
Also see:
http://gr8conf.eu/Presentations/ Grails-Plugin-Best-Practices
![Page 3: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/3.jpg)
3CONFIDENTIAL 3CONFIDENTIAL
My Plugins
Acegi
App Info
App Info Hibernate
AspectJ (in-progress)
Atomikos
Binary Artifacts
BlazeDS (reworked)
Cache
Cache-Ehcache
Cache-Redis
Cache-Gemfire
Cloud Foundry
Cloud Foundry UI
Cloud Support
CodeNarc
Console (rework, current owner)
Database Reverse Engineering
Database Sessions
Database Migration
Database Migration JAXB
Datasources
Dumbster
Dynamic Controller
Dynamic Domain Class (core code)
EJB (in-progress)
EJB Glassfish (in-progress)
FamFamFam
Flex (reworked, current owner)
Flex Scaffold (major rework, not yet released)
HDIV (in-progress)
![Page 4: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/4.jpg)
4CONFIDENTIAL 4CONFIDENTIAL
My Plugins
Heroku
Hibernate Filter (rework, current owner)
Jbossas
jdbc-pool (rework, current owner)
JMX
LazyLob
Logback
Memcached
P6Spy UI
Ratpack
Remoting (reworked, current owner)
SpringMVC
Spring Security Core
Spring Security ACL
Spring Security App Info
Spring Security CAS
Spring Security Kerberos
Spring Security LDAP
Spring Security OAuth Consumer (in-progress)
Spring Security OAuth Provider (in-progress)
Spring Security Open ID
Spring Security Shiro
Spring Security UI
Standalone
standalone-tomcat-memcached
standalone-tomcat-redis
tcpmon
UI Performance
Webxml (rework, current owner)
![Page 5: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/5.jpg)
5CONFIDENTIAL 5CONFIDENTIAL
What Is A Plugin?
![Page 6: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/6.jpg)
6CONFIDENTIAL 6CONFIDENTIAL
What Is A Plugin?
A set of software components that adds specific abilities to a larger software applicationhttps://secure.wikimedia.org/wikipedia/en/wiki/Plug-in_(computing)
“
![Page 7: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/7.jpg)
7CONFIDENTIAL 7CONFIDENTIAL
What Is A Plugin?
Very similar to a Grails application project
Plus a *GrailsPlugin.groovy plugin descriptor file
Use grails createplugin to create one
Often adds artifacts, resources, scripts
Good for code reuse
Modular development
![Page 8: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/8.jpg)
8CONFIDENTIAL 8CONFIDENTIAL
Application Directory Structure
![Page 9: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/9.jpg)
9CONFIDENTIAL 9CONFIDENTIAL
Plugin Directory Structure
![Page 10: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/10.jpg)
10CONFIDENTIAL 10CONFIDENTIAL
Best Practices and Tips
![Page 11: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/11.jpg)
11CONFIDENTIAL 11CONFIDENTIAL
Best Practices and Tips
Delete anything auto-generated that you don't use
•grails-app/views/error.gsp
• Empty grails-app/i18n/messages.properties
•_Uninstall.groovy and other generated scripts
•web-app files
•grails-app/conf/UrlMappings.groovy
• Empty descriptor callbacks
![Page 12: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/12.jpg)
12CONFIDENTIAL 12CONFIDENTIAL
Best Practices and Tips
Exclude files used in development or testing
• Use def pluginExcludes = [...]
• src/docs
• Test artifacts
Create .gitignore (manually or with
integrate-with --git)
Open every file, decide that you own it or not
![Page 13: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/13.jpg)
13CONFIDENTIAL 13CONFIDENTIAL
Best Practices and Tips
Use dependency management in
BuildConfig.groovy, not lib dir
Change the minimum Grails version to 2.0 or the lowest
version you're comfortable supporting:
•def grailsVersion = '2.0 > *'
![Page 14: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/14.jpg)
14CONFIDENTIAL 14CONFIDENTIAL
Best Practices and Tips
Write tests
Run them often
Run tests before commit and especially before
release (use Gradle or 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"
![Page 15: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/15.jpg)
15CONFIDENTIAL 15CONFIDENTIAL
Best Practices and Tips
Prefer Java to Groovy (or @CompileStatic) for
performance if applicable
Instead of using inline plugins, install into a test
app and use Meld or another diff tool to keep in
sync
Before you release, run package-plugin and open
the ZIP, look at what's there
![Page 16: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/16.jpg)
16CONFIDENTIAL 16CONFIDENTIAL
Best Practices and Tips
Use a sensible naming convention, e.g.
grails.plugin.<pluginname>
Most plugin metadata is not optional:
• Fix grailsVersion
• Update description and documentation
• Set value for license, issueManagement, scm (and
organization, developers if applicable)
![Page 17: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/17.jpg)
17CONFIDENTIAL 17CONFIDENTIAL
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!
![Page 18: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/18.jpg)
18CONFIDENTIAL 18CONFIDENTIAL
Best Practices and Tips
http://grails.org/Creating+Plugins
http://grails.org/The+Plug-in+Developers+Guide
http://blog.springsource.com/2010/05/18/managing-plu
gins-with-grails-1-3/
http://grails.org/doc/latest/guide/plugins.html
http://burtbeckwith.com/blog/?p=1973 (converting
apps plugins)↔
![Page 19: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/19.jpg)
19CONFIDENTIAL 19CONFIDENTIAL
Planning for Customization
![Page 20: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/20.jpg)
20CONFIDENTIAL 20CONFIDENTIAL
Planning for Customization
Plugins are compiled first, then the app; this means that
everything can be overridden
Prefer Spring beans to using new
Prefer dynamic instantiation (to allow class name override) to
using new
Use protected, not private and avoid static
Move logic from plugin descriptor to classes
![Page 21: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/21.jpg)
21CONFIDENTIAL 21CONFIDENTIAL
Extension Points
The Build System
Spring Application Context
Dynamic method registration
Auto Reloading
Container Config (web.xml)
Adding new Artefact Types
![Page 22: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/22.jpg)
22CONFIDENTIAL 22CONFIDENTIAL
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
![Page 23: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/23.jpg)
23CONFIDENTIAL 23CONFIDENTIAL
Plugin Goals
Convention-based approaches
DRY (Don't Repeat Yourself)
Required extension points satisfied
Easy to distribute & install
• Without additional configuration
![Page 24: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/24.jpg)
24CONFIDENTIAL 24CONFIDENTIAL
What Can A Plugin Do?
Enhance classes at runtime
• Add methods, constructors, properties, etc.
Perform runtime Spring configuration
Modify web.xml programmatically
Add new controllers, taglibs, services, etc.
Add new artifact types
Support development-mode file and config reloading
Contribute scripts
![Page 25: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/25.jpg)
25CONFIDENTIAL 25CONFIDENTIAL
A Basic Plugin
class LoggingGrailsPlugin {
def version = "0.1” def grailsVersion = "2.0 > *"
def doWithDynamicMethods = { for (c in application.allClasses) { c.metaClass.getLog = {-> LogFactory.getLog(c) } } }}
![Page 26: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/26.jpg)
26CONFIDENTIAL 26CONFIDENTIAL
Application Object
'GrailsApplication' object
• available with the application variable in the plugin descriptor, and
the grailsApplication Spring bean
• holds convention information
• Convenient access to the config: grailsApplication.config
(since the holders are deprecated)
![Page 27: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/27.jpg)
27CONFIDENTIAL 27CONFIDENTIAL
Application Object
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)
![Page 28: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/28.jpg)
28CONFIDENTIAL 28CONFIDENTIAL
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
![Page 29: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/29.jpg)
29CONFIDENTIAL 29CONFIDENTIAL
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")
![Page 30: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/30.jpg)
30CONFIDENTIAL 30CONFIDENTIAL
Plugin Descriptor
Metadata
• version, grailsVersion range, pluginExcludes, dependsOn, etc.
Lifecycle Closures
doWithWebDescriptor← Modify XML generated for web.xml at runtime
doWithSpring ← Participate in Spring configuration
doWithApplicationContext← Post ApplicationContext initialization activities
doWithDynamicMethods ← Add methods to MetaClasses
onChange ← Participate in reload events
![Page 31: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/31.jpg)
31CONFIDENTIAL 31CONFIDENTIAL
Configuring Spring
class JcrGrailsPlugin { def version = 0.1 def dependsOn = [core:0.4]
def doWithSpring = { jcrRepository(RepositoryFactoryBean) { configuration = "classpath:repository.xml" homeDir = "/repo" } }}
Bean name is method name,first argument is bean class
Set properties on the bean
![Page 32: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/32.jpg)
32CONFIDENTIAL 32CONFIDENTIAL
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 thereimport com.mycompany.myapp.MyUserDetailsServicebeans = { userDetailsService(MyUserDetailsService) { grailsApplication = ref('grailsApplication') foo = 'bar' }}
![Page 33: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/33.jpg)
33CONFIDENTIAL 33CONFIDENTIAL
Overriding Spring Beans
For more advanced configuration, use a
BeanPostProcessor, BeanFactoryPostProcessor, or
a BeanDefinitionRegistryPostProcessor
![Page 34: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/34.jpg)
34CONFIDENTIAL 34CONFIDENTIAL
Plugging In Dynamic Methods
def doWithDynamicMethods = { ctx -> application .allClasses .findAll { it.name.endsWith("Codec") } .each {clz -> Object .metaClass ."encodeAs${clz.name-'Codec'}" = { clz.newInstance().encode(delegate) } } }}
Taken from Grails core, thisplugin finds all classes endingwith “Codec” and dynamicallycreates “encodeAsFoo” methods!
The “delegate” variableis equivalent to “this” inregular methods
![Page 35: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/35.jpg)
35CONFIDENTIAL 35CONFIDENTIAL
Example Reloading Plugin
class I18nGrailsPlugin { def version = "0.4.2" def watchedResources = "file:../grails-app/i18n/*.properties"
def onChange = { event -> def messageSource = event.ctx.getBean("messageSource")
messageSource?.clearCache() }}
Defines set of files to watchusing Spring Resource pattern
When one changes, eventis fired and plugin respondsby clearing message cache
![Page 36: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/36.jpg)
36CONFIDENTIAL 36CONFIDENTIAL
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
![Page 37: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/37.jpg)
37CONFIDENTIAL 37CONFIDENTIAL
Adding Elements to web.xml
def doWithWebDescriptor = { xml →
def contextParam = xml.'context-param' contextParam[contextParam.size() - 1] + { 'filter' { 'filter-name'('springSecurityFilterChain') 'filter-class'(DelegatingFilterProxy.name) } }
def filterMapping = xml.'filter-mapping' filterMapping[filterMapping.size() - 1] + { 'listener' { 'listener-class'(HttpSessionEventPublisher.name) } }}
![Page 38: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/38.jpg)
38CONFIDENTIAL 38CONFIDENTIAL
Dependency Management
Jar files
• Prefer BuildConfig.groovy Maven repos→
• lib directory is ok if unavailable otherwise
Other plugins
• def loadAfter = ['controllers']
• Declare dependencies in BuildConfig.groovy
Grails
• def grailsVersion = '2.0 > *'
![Page 39: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/39.jpg)
39CONFIDENTIAL 39CONFIDENTIAL
BuildConfig.groovy
grails.project.dependency.resolution = { inherits 'global' log 'warn'
repositories { grailsCentral()
flatDir name:'myRepo', dirs:'/path/to/repo'
mavenLocal() mavenCentral()
mavenRepo 'http://download.java.net/maven/2/' mavenRepo 'http://my.cool.repo.net/maven/' }
![Page 40: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/40.jpg)
40CONFIDENTIAL 40CONFIDENTIAL
BuildConfig.groovy
dependencies { runtime 'mysql:mysql-connector-java:5.1.22' compile group: 'commons-httpclient', name: 'commons-httpclient', version: '3.1' build('net.sf.ezmorph:ezmorph:1.0.4', 'net.sf.ehcache:ehcache:1.6.1') { transitive = false } test('com.h2database:h2:1.2.144') { export = false } runtime('org.liquibase:liquibase-core:2.0.1', 'org.hibernate:hibernate-annotations:3.4.0.GA') { excludes 'xml-apis', 'commons-logging' } }
![Page 41: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/41.jpg)
41CONFIDENTIAL 41CONFIDENTIAL
BuildConfig.groovy
plugins { runtime ":hibernate:$grailsVersion", { export = false }
runtime ':jquery:1.8.3' compile ':spring-security-core:1.2.7.3' runtime ':console:1.2'
build ':release:2.2.1', ':rest-client-builder:1.0.3', { export = false } }}
![Page 42: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/42.jpg)
42CONFIDENTIAL 42CONFIDENTIAL
Troubleshooting Dependency Management
Run grails dependencyreport
• See http://burtbeckwith.com/blog/?p=624 for
visualization tips
Increase BuildConfig.groovy logging level
•log 'warn' → 'info', 'verbose', or 'debug'
![Page 43: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/43.jpg)
43CONFIDENTIAL 43CONFIDENTIAL
Scripts
Gant: http://gant.codehaus.org/
Groovy + Ant - XML
Can be used in apps but more common in plugins
Put in the scripts directory
_Install.groovy• runs when you run grails installplugin or when it's transitively installed - don't overwrite! might be reinstall or plugin upgrade
![Page 44: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/44.jpg)
44CONFIDENTIAL 44CONFIDENTIAL
Scripts
_Uninstall.groovy
• runs when you run grails uninstallplugin 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)
![Page 45: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/45.jpg)
45CONFIDENTIAL 45CONFIDENTIAL
Scripts
_Events.groovy
• Respond to
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. createapp)
eventCreateWarStart = { name, stagingDir →…}
eventGenerateWebXmlEnd = {…}
![Page 46: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/46.jpg)
46CONFIDENTIAL 46CONFIDENTIAL
Scripts
Reuse existing scripts with includeTargets
• includeTargets << grailsScript("_GrailsWar")
Include common code in _*Common.groovy•includeTargets << new File( cloudFoundryPluginDir, "scripts/CfCommon.groovy")
![Page 47: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/47.jpg)
47CONFIDENTIAL 47CONFIDENTIAL
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
/testing-your-grails-scripts
Run with the rest with grails testapp or alone
with grails testapp other
![Page 48: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/48.jpg)
48CONFIDENTIAL 48CONFIDENTIAL
Scripts
includeTargets << new File(cloudFoundryPluginDir, "scripts/_CfCommon.groovy")
USAGE = '''grails cf-list-files [path] [--appname] [--instance]'''
target(cfListFiles: 'Display a directory listing') { depends cfInit
// implementation of script}
def someHelperMethod() { … }
setDefaultTarget cfListFiles
Typical Structure
![Page 49: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/49.jpg)
49CONFIDENTIAL 49CONFIDENTIAL
Custom Artifacts
ControllerMixinArtefactHandler extends
ArtefactHandlerAdapter
interface ControllerMixinGrailsClass extends
InjectableGrailsClass
DefaultControllerMixinGrailsClass extends
AbstractInjectableGrailsClass implements
ControllerMixinGrailsClass
![Page 50: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/50.jpg)
50CONFIDENTIAL 50CONFIDENTIAL
Custom Artifacts
class MyGrailsPlugin {
def watchedResources = [ 'file:./grails-app/controllerMixins/**/*ControllerMixin.groovy', 'file:./plugins/*/grails-app/controllerMixins/**/*ControllerMixin.groovy' ]
def artefacts = [ControllerMixinArtefactHandler]
def onChange = { event → ... }}
![Page 51: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/51.jpg)
51CONFIDENTIAL 51CONFIDENTIAL
Testing
![Page 52: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/52.jpg)
52CONFIDENTIAL 52CONFIDENTIAL
Testing
Write regular unit and integration tests in test/unit
and test/integration
'Install' using inline mechanism in
BuildConfig.groovy
• grails.plugin.location.'myplugin' = '../myplugin'
Test scripts as described earlier
Create test apps programmatically – all of the
Spring Security plugins do this with bash scripts
![Page 53: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/53.jpg)
53CONFIDENTIAL 53CONFIDENTIAL
Testing
Use build.xml or Gradle to create single target that
tests and builds plugin
Use inline plugin or install from zip
Zip install is deprecated, so better to use the
maven-install script• Will resolve as a BuildConfig dependency (using mavenLocal())
<target name='package' description='Package the plugin' depends='test, doPackage, post-package-cleanup'/>
grails install-plugin /path/to/plugin/grails-myplugin-0.1.zip
![Page 54: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/54.jpg)
54CONFIDENTIAL 54CONFIDENTIAL
Testing
Use CI, e.g. CloudBees BuildHive:
• https://fbflex.wordpress.com/2012/07/12/using-cl
oudbees-buildhive-to-test-grails-plugins/
• https://buildhive.cloudbees.com/job/burtbeckwith
/job/grails-database-session/
![Page 55: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/55.jpg)
55CONFIDENTIAL 55CONFIDENTIAL
Source Control and Release Management
![Page 56: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/56.jpg)
56CONFIDENTIAL 56CONFIDENTIAL
Becoming A Plugin Developer
Create an account at grails.org
Discuss your idea on the User mailing list
• http://grails.org/Mailing+lists
Submit a request at http://grails.org/plugins/submitPlugin
• Monitor the submission for questions and comments
Build (and test!) your plugin and run:
•grails publish-plugin --stacktrace
Profit!
![Page 57: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/57.jpg)
57CONFIDENTIAL 57CONFIDENTIAL
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()}
![Page 58: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/58.jpg)
58CONFIDENTIAL 58CONFIDENTIAL
Source Control and Release Management
Release with grails publishplugin repository=repo_name
grails.project.dependency.distribution = { remoteRepository( id: "repo_name", url: "http://yourserver:8081/artifactory/plugins-snapshots-local/") { authentication username: "admin", password: "password" }}
![Page 59: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/59.jpg)
59CONFIDENTIAL 59CONFIDENTIAL
Source Control and Release Management
Use “release” plugin http://grails.org/plugin/release
• Sends tweet from @grailsplugins and email to plugin forum
http://grails-plugins.847840.n3.nabble.com/
• Will not check code into SVN for you
plugins { build ':release:2.2.1', ':rest-client-builder:1.0.3', { export = false }}
![Page 60: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/60.jpg)
60CONFIDENTIAL 60CONFIDENTIAL
Want To Learn More?
![Page 62: Grails Plugin Best Practices](https://reader033.vdocument.in/reader033/viewer/2022060108/554f5762b4c905423f8b5690/html5/thumbnails/62.jpg)
62CONFIDENTIAL 62CONFIDENTIAL
Thanks!