Showing posts with label plugin. Show all posts
Showing posts with label plugin. Show all posts

2011-11-17

The Life Cycle of Plugins & Addons in the Enterprise

I'm probably one of the few people in the world that has had the opportunity to work with both Grails and Spring Roo on extended projects. In both environments I've discovered a strategy around code reuse that is a bit different from what I normally observe in the Enterprises I consult with.

Both Spring Roo and Grails feature a reusable component system. They are similar enough you can use similar strategies in the enterprise with either Spring Roo or Grails. If you are in an enterprise that uses both Grails and Roo (these companies do in fact exist), I recommend that you develop common functionality in Spring POJO then graft a Grails plugin and/or a Spring Roo addon over the top.

In Grails, plugins are powerful sub-applications. A plugin can provide a command line script to the Grails CLI, a library, a library dependency, to a full set of Model View and Controllers or anywhere in between.

In Spring Roo, we term these reusable components addons because they do not necessarily contribute runtime functionality to the final system. Spring Roo's philosophy of having zero runtime footprint means that you don't have a Roo runtime to hang your hat on. What you do have is a very powerful Command Line Interface (CLI) that you can leverage along with a templating system inside your Roo addon to allow your end-user-developer to generate their own components. Instead of shipping function as you do in Grails, in Roo you provide a mechanism for guiding the developer through generating function.

Whether you are shipping function wholesale, as you do in Grails, or you are providing functional templates as you do in Roo, in an abstract sense I've discovered the same pattern applies to both Roo and Grails. You start with a pilot project for the addon or the plugin and you lift it out of the pilot project allowing it to graduate to a shared enterprise-wide repository.



As time goes by, you work on the addon or plugin as a separate project. Each project that uses the addon/plugin will keep track of its own versions of these addons meaning it is important to keep those old revisions available just in case. You will also want to tag each released revision in the event you need to back port fixes into old releases should a critical project get stuck on an old version of Roo or Grails for some reason.

The advantages to the enterprise would be a single place to revision control the client components of a Single Sign On system, or a single place to revision control and distribute shared client functions, look and feel components, or other commonly reused system components.

2009-11-10

Simple Grails Plugin Repository Mirror Script

We needed a local offline repository for Grails plugins. I thought this would be a good chance to contribute a grails application to the community. It turns out there already is one called plugrepo that you can just download and use.

The plugrepo grails application is a very nice looking and if I'd known about it I wouldn't have spent the effort building my own.

What started this is a command line Groovy script I had hooked to a cron job on our Ubuntu Linux Apache server to create a mirror. I'll go ahead and post this here in case someone would rather have a flat Apache served archive... otherwise take a look at plugrepo.

To use the archive setup your grails app with this additional configuration file:

// grails-app/conf/BuildConfig.groovy
grails.plugin.repos.discovery.myRepository="http://myhost.test.com/grails/"


The following groovy script is meant to be run on the Ubuntu command line and
populate the /var/www/grails directory as if it were a grails plugin repository.

#!/usr/bin/env groovy
import groovy.xml.MarkupBuilder
/**
* Shawn Hartsock
*
* This is a quick and dirty Groovy CLI script for creating Grails plugin
* repository mirrors.
*/

def uri = "http://myhostname.test.com/grails"
def destinationRoot = "/var/www/grails"
def version = "grails-1.1.1"
def separator = System.getProperty("file.separator")
def latestOnly = true

if(!args) {
println """

this script is designed to take plugins-list.xml files (as arguments) from
a ~${separator}grails${separator}1.1.1 (or other version) folder and download each
plugin to the $root working directory.

It will then output a modified a $destinationRoot${separator}.plugin-meta${separator}plugins-list.xml
file modified to reflect the new locations of the files.

"""
System.exit(1);
}

try {
def destination = destinationRoot + separator + version
new File(destination).mkdir()
def writer = new StringWriter()
def xml = new MarkupBuilder(writer)
List files = []
args.each { arg ->
if(arg == "-a") {
latestOnly = false
}
if(new File(arg).exists()) {
files.add(arg)
}
}
xml.plugins(revision:2) {
files.each { file ->
process(file, destination, separator, uri + "/" + version, xml, latestOnly)
}
}
new File(destinationRoot + separator + ".plugin-meta").mkdir()
def outFile = destinationRoot + separator + ".plugin-meta" + separator + "plugins-list.xml"
def out = new File( outFile )
if(out.exists()) {
if(out.delete()) {
out = new File( outFile )
}
}

out << writer.toString()
} catch(Exception ex) {
System.err.println(ex.message);
System.exit(0);
}

void process(String fileName, String destination, String separator, String uri, xml, latestOnly) throws Exception {
def text = new File(fileName).text // may throw file access exceptions
def plugins
try {
plugins = new XmlParser().parseText(text)
assert plugins?.plugin.size() > 0
assert plugins.plugin[0].release != null
assert plugins.plugin[0].release.file != null
} catch(java.lang.AssertionError ae) {
throw new Exception("File $fileName does not contain plugin definitions: " + ae.message)
} catch(Exception ex) {
throw new Exception("File $fileName could not be parsed as plugin XML: " + ex.message)
}
println "${plugins.plugin.size()} plugins found in $fileName "
println "\t"
plugins.plugin.each { p ->
xml.plugin( name:p.'@name', 'latest-release':p.'@latest-release') {
p.release.each { r ->
def get = true
if(latestOnly) get = (r.'@version' == p.'@latest-release')?true:false
if(get) {
def f = download(r.file.text(), destination, separator)
if( f ) {
def url = uri + "/" + f
println "\tregistering:\n\t " + url
release(type:r.'@type',tag:r.'@tag',version:r.'@version') {
title(r.title.text())
author(r.author.text())
authorEmail(r.authorEmail.text())
description(r.description.text())
documentation(r.documentation.text())
file(url)
}
}
}
}
}
}
}

def download(String urlString, String dest, String separator) {
// note: this is a web URL so you tokenize on "/" not file.separator
def fileName = urlString.tokenize("/")[-1] // last name on URL
def destFileName = dest + separator + fileName
if( new File(destFileName).exists() ) {
println "\t $destFileName already present in the repository"
return fileName
}
print "\tdownloading $fileName to $dest directory\t"
def file = new FileOutputStream( destFileName )
def out = new BufferedOutputStream(file)
out << new URL(urlString).openStream()
out.close()
println "."
return fileName
}

2008-05-01

Grails Audit Logging Plugin is a "real plugin"

I'm very happy to be able to post that the Audit Logging Plugin is now available via the "official" Grails plugin install commands. My plugin is no where near as cool as the Searchable Plugin but I'm very pleased to offer it up. I hope it fills a need judging from the relatively large spike in traffic I got from other blogs and sites like refactr I'd say there's quite a few people who might want this plugin.

You know who you are. Now you can send me bug reports!

2008-04-24

Audit Logging Plugin version 0.3: who did what when

When I posted my Audit Logging plug-in I had no idea how to get at the user name of the person using the session. I posted a question on the Grails Dev forum. And the answer supplied by pftravis lead me this...

static String actorKey = 'userPrincipal.name'
static String sessionAttribute = null

def getActor() {
def attr = org.springframework.web.context.request.RequestContextHolder.getRequestAttributes()
def actor = null
if(sessionAttribute) {
actor = attr?.session?.getAttribute(sessionAttribute)
}
else {
actor = resolve(attr,actorKey)
}
return actor
}

... if there is no configuration the default will read the 'userPrincipal.name' from the context. In my set up of the listener I read in the Config.groovy from the grails project and if there is a configuration like this...

auditLog {
actorKey = "session.user.name"
}

...then I'll use a clever little groovy trick to resolve the session, user, and name out of the attributes. If instead the configuration looks like...

auditLog {
actorKey = "userPrincipal.id"
}

... the same trick will read the id of the authenticated user. But, if you are using CAS then you can do this...

auditLog {
username = "${edu.yale.its.tp.cas.client.filter.CASFilter.CAS_FILTER_USER}"
}

... and you get the CAS authenticated user name.

So long story short all you have to know is if your security system uses userPrincipal and if it does whether "userPrincipal.name" or "userPrincipal.id" is right. If you have a home brew security system all you have to know is what part of the authenticated user you want to identify the user by... and specify "session.user.name" or "session.login" depending on how your security system works. And for systems like CAS you can use an attribute key of any kind.

Nice and generic. Check out the latest Grails Audit Logging Plugin. As always any feedback or criticism will be welcome... and my goal in all of this is to contribute and learn.

2008-04-21

Learning more about Hibernate: Grails Audit Logging v0.2

Very important changes in version 0.2, the mapping method I had written did this:
def nameMap = [:]
for(int ii=0; ii def obj = newState[ii]
entity.properties.each({ key,val ->
if(val.is(obj)) {
nameMap[ii] = key
}
})
}

... but I found that I could do this instead ...
def persister = event.getPersister()
def nameMap = persister.getPropertyNames()


This fixes a bug in the onChange event handler that meant you couldn't see the old value on properties that had been set to null. Hit this in one of my test cases Friday afternoon.

Other tests show that it's impossible to fire onUpdate and not onChange so I've backed out the onUpdate event handler. In my working version I've also allowed each of the other events to catch either old or new values as a LinkedHashMap object.

Other annoyances were that when the version changed this was logged as an event by when the Audit Logger was set to verbose mode. (The version I posed had 'detailed = false' which disabled verbose logging.) I've made 'version' a special property that is not logged or reported as a change.

Some of the changes have the tidy side-effect of making things faster.

2008-04-18

Inside Hibernate Events and Audit Logging with Grails Plugins

Previously, I wrote about how I was able to take components posted on hibernate.org and reference the configuration files of other plug-ins to craft a plug-in for Grails that used the Hibernate Events model to create an Audit Logging system for my Grails projects. I've posted the Grails Audit Logging Plugin that I'm using in my own mission critical projects. You can download and unzip the plug-in (or just install it into a Grails project) to read every delicious line of source code if you want to.

Before yesterday, I had no experience with Hibernate event listeners and no experience with Grails plug-ins yet in a single day I was able to create what might be a rather sophisticated plug-in for Grails using internet available resources. I'd love to know if anyone else uses this project or is inspired by it to do some other work based on it. This is my detailed write-up of the experience.

When I wanted audit logging in Grails, I wanted something that looked like other Grails features. I wanted to create a class that looked like:

class Person {
static auditable = true

Long id
Long version

String firstName
String middleName
String lastName

String email

def onChange = { oldMap,newMap ->
println "Person was changed ..."

oldMap.each({ key, oldVal ->

def newVal = newMap[key]
if(oldVal != newVal) {
println " * $key from $oldVal to $newVal "
}

})
}


}

... and would automatically get Audit Logging and onChange event handlers that could examine the old and new values and act accordingly. (Audit Logging makes our DBA happy. And, we need to have different behaviors based on exactly how a value changed.) It was surprisingly easy to extend Grails to add these behaviors and I can't believe I was able to learn the plug-in architecture, learn Hibernate Events, and create a usable plug-in in only a day. If I can do it you can too.

In this post I'd like to dissect what I did to create an Audit Logging plug-in for Grails. It should shed some light on a few things. Hopefully it will inspire developers who are more talented than I am to do some really great work. There's really not that much to it and you can easily do this for yourself.

Firstly, creating Grails plug-ins is absolutely a joy. Just like creating any other project in grails you start with the command line. To create a plug-in project...

$ grails create-plugin audit-logging

... and the result is a Grails project. All the conventions you would be familiar with in creating a Grails application are there. It's the same set of folders with an additional file... AuditLoggingGrailsPlugin.groovy that appears by magic in the root of the project. The file name is a simple variation of the name you gave the plug-in. This file is one of the things that distinguishes this project as a plug-in project. We'll come back to this file later. But remember this is the file that tells Grails what to do with your plug-in and how to install it into the Spring run-time.

In my project I wanted to create a Groovy class that could be wired into the Hibernate Events Model. I had played with a Hibernate Interceptor but the Hibernate Event model does what I need so well there's really no need for the interceptor. Either way, it really pays to carefully select what events you want to listen to. Certain events will have data that certain other events don't.

After some trial and error I found that for the purpose of an audit log the events that we care about are just before an entity is deleted, just after it is inserted, and just after it has been updated.

  • You want to be invoked before the entity is deleted so you can optionally log all the values that the entity used to have before it was destroyed.

  • You want to be invoked after the entity is inserted so you can reference any auto generated id numbers or version numbers and log them.

  • You want to be invoked after an update because the PostUpdateEvent object will have the old state and the current state (or new state) of the entity in it and you can log any changes.


So let's first create a Listener class that implements these listeners:

class AuditLogListener implements PreDeleteEventListener,
PostInsertEventListener, PostUpdateEventListener, Initializable {


Each of those interfaces will demand their own methods... specifically...

public boolean onPreDelete(final PreDeleteEvent event) {

...

public void onPostInsert(final PostInsertEvent event) {

... and ...

public void onPostUpdate(final PostUpdateEvent event) {

we want to go "static typed" here since the classes calling us are Java classes and we want to be very clear which method it is that implements the interface.

While I've only used three event listeners here, it's true of all the pre-event listeners in hibernate that they return true if and only if the alter something in the event. So to be extra cautious here we've declared our parameters final. We won't be mucking about with event objects before or after the fact. The final keyword makes this explicit.

Next, we'll want to record log entries from the listener class. To do this my plug-in introduces an additional domain class. If the root folder of our project is audit-logging then the domain class is at audit-logging/grails-app/domain/AuditLogEvent.groovy and looks like this:

class AuditLogEvent {
Long id
Long version
Date dateCreated
Date lastUpdated

String actor
String className
String persistedObjectId

String eventName
String propertyName
String oldValue
String newValue

static constraints = {
actor(nullable:true)
className(nullable:true)
persistedObjectId(nullable:true)
eventName(nullable:true)
propertyName(nullable:true)
oldValue(nullable:true)
newValue(nullable:true)
}

static mapping = {
table 'audit_log'
}
}

... this class isn't special. It's just another domain class like any other GORM domain class. In fact if I wanted to be evil I could label the AuditLogEvent class as an "auditable" class for my plug-in later. That would mean I would get trapped in an infinite recursion of audit logging. The first logged insert into the database would trigger an audit log insert which would trigger an audit log insert which would trigger an audit log insert... and so on...

I elected to map this domain class to the table 'audit_log' and that's just a personal taste issue. I feel that one row in the audit log is a single event but the whole list of events is a log. I hope it doesn't confuse anyone. It does read better from SQL for our DBA.

Grails will allow us to call save on a new object of class AuditLogEvent from inside the listener so we don't have to mess around with pulling out session factories from our event objects. This is a really nice feature of working with GORM in Grails.

To do really simple audit logging from any of our events all we need to do in the Grails environment is:

def entity = event.getEntity()

def className = entity.getClass().getName()
def eventName = 'INSERT'
def persistedObjectId = getEntityId(event)

new AuditLogEvent(
className:className,
eventName:eventName,
persistedObjectId:persistedObjectId.toString()
).save()

In a straight Spring environment we would have a great deal more code to write. Take a look at what is essentially the same event handler in straight Java and Hibernate and compare that to the Grails listener in my project, it's much more work to handle the event in straight Java and Hibernate. Groovy and Grails make the job much easier.

One of the things that was much easier for example was to have the onChange handler receive the "old" and "new" states as a Map objects. Firstly, the map objects are at least more mnemonic to work with than arrays of Objects secondly... creating the maps was much simpler in Groovy. Let me explain...

The onPostUpdateEvent event provides us with two arrays of objects. First is the OldState array and second is the State array. I could not find an API that could tell me what parameter is in what ordinal position in these Object arrays. That meant that I could see what things changed from and to... but not which things... when I reference the entity it is the entity as it exists in its new state. But, there's no corresponding old state. So I wrote a block of Groovy to create a mapping of ordinal positions to property key names. It was surprisingly easy going take a look...

def nameMap = [:]
for(int ii=0; ii<newState.length; ii++) {
def obj = newState[ii]
entity.properties.each({ key,val ->
if(val.is(obj)) {
nameMap[ii] = key
}
})
}

... at the end of that I have a mapping of numbers to keys. I do this by using the Groovy is method. This is the Groovy identity comparison. Remember, since equality in Groovy means value equality (an intuitive meaning) you need a different operator to compare identity. Now I can build my newMap and my oldMap like so...

for(int ii=0; ii<newState.length; ii++) {
oldMap[nameMap[ii]] = oldState[ii]
newMap[nameMap[ii]] = newState[ii]
}

... and the maps are ready to be used as parameters to my logger method so it can record what column changed and what the new and old values are. And as a nice side effect I can now invoke the onChange handler with old and new maps representing the old and new parameters respectively.

I could go line by line through the event listener but I don't think I'll do that in this post. Instead I'll post the listener and let the interested pick it apart. Remember you can see the source code in the plug-in download too...

/**
* @author shawn hartsock
*
* Grails Hibernate Interceptor for logging
* saves, updates, deletes and acting on
* individual properties changes... and
* delegating calls back to the Domain Class
*
* This class is based on the post at
* http://www.hibernate.org/318.html
*
* 2008-04-17 created initial version 0.1
*/

import org.hibernate.EntityMode;
import org.hibernate.StatelessSession;
import org.hibernate.HibernateException;
import org.hibernate.cfg.Configuration;
import org.hibernate.event.Initializable;
import org.hibernate.event.PreDeleteEvent;
import org.hibernate.event.PreDeleteEventListener;
import org.hibernate.event.PostInsertEvent;
import org.hibernate.event.PostInsertEventListener;
import org.hibernate.event.PostUpdateEvent;
import org.hibernate.event.PostUpdateEventListener;

public class AuditLogListener implements PreDeleteEventListener,
PostInsertEventListener, PostUpdateEventListener, Initializable {
static detailed = false // TODO back off into configuration file

// only returns auditable entities
def isAuditableEntity(event) {
if(event && event.getEntity()) {
def entity = event.getEntity()
if(entity.metaClass.hasProperty(entity,'auditable') && entity.'auditable') {
return true
}
}
return false
}

/**
* we allow user's to specify
* static auditable = [handlersOnly:true]
* if they don't want us to log events for them and instead have their own plan.
*/
boolean callHandlersOnly(entity) {
if(entity && entity.metaClass.hasProperty(entity,'auditable') && entity.'auditable') {
if(entity.auditable.getClass() == java.util.LinkedHashMap.class &&
entity.auditable.containsKey('handlersOnly')) {
return (entity.auditable['handlersOnly'])?true:false
}
return false
}
return false
}

def getEntityId(event) {
if(event && event.entity) {
def entity = event.getEntity()
if(entity.metaClass.hasProperty(entity,'id') && entity.'id') {
return entity.id.toString()
}
else {
// trying complex entity resolution
return event.getPersister().hasIdentifierProperty() ? event.getPersister().getIdentifier(event.getEntity(), event.getPersister().guessEntityMode(event.getEntity())) : null;
}
}
return null
}

/**
* We must use the preDelete event if we want to capture
* what the old object was like.
*/
public boolean onPreDelete(final PreDeleteEvent event) {
try {
if(isAuditableEntity(event) && ! callHandlersOnly(event.getEntity())) {
def entity = event.getEntity()
def actor = "" // Need to figure out how to get the actor id somehow...
def entityName = entity.getClass().getName()
def entityId = getEntityId(event)
logChanges(null,event.getEntity().properties,null,entityId,'DELETE',entityName)
}
executeSimpleHandler(event,'onDelete')
} catch (HibernateException e) {
println "Audit Plugin unable to process DELETE event"
e.printStackTrace()
}
return false
}

/**
* I'm using the post insert event here instead of the pre-insert
* event so that I can log the id of the new entity after it
* is saved. That does mean the the insert event handlers
* can't work the way we want... but really it's the onChange
* event handler that does the magic for us.
*/
public void onPostInsert(final PostInsertEvent event) {
try {
if(isAuditableEntity(event) && ! callHandlersOnly(event.getEntity())) {
def entity = event.getEntity()
def actor = "" // Need to figure out how to get the actor id somehow...
def entityName = entity.getClass().getName()
def entityId = getEntityId(event)
logChanges(event.getEntity().properties,null,null,entityId,'INSERT',entityName)
}
executeSimpleHandler(event,'onSave')
} catch (HibernateException e) {
println "Audit Plugin unable to process INSERT event"
e.printStackTrace()
}
return;
}

/**
* Now we get fancy. Here we want to log changes...
* specifically we want to know what property changed,
* when it changed. And what the differences were.
*
* This works better from the onPreUpdate event handler
* but for some reason it won't execute right for me.
* Instead I'm doing a rather complex mapping to build
* a pair of state HashMaps that represent the old and
* new states of the object.
*
* The old and new states are passed to the object's
* 'onChange' event handler. So that a user can work
* with both sets of values.
*
* Needs complex testing BTW.
*/
public void onPostUpdate(final PostUpdateEvent event) {
def entity = event.getEntity()
def entityName = entity.getClass().getName()
def entityId = event.getId()

// object arrays representing the old and new state
def oldState = event.getOldState()
def newState = event.getState()

// The event API only gives us an array of
// objects representing the new and old states
// so we need to use the identity comparison to
// create a name mapping for each state's
// ordinal positions.
def nameMap = [:]
for(int ii=0; ii<newState.length; ii++) {
def obj = newState[ii]
entity.properties.each({ key,val ->
if(val.is(obj)) {
nameMap[ii] = key
}
})
}
def oldMap = [:]
def newMap = [:]
for(int ii=0; ii<newState.length; ii++) {
oldMap[nameMap[ii]] = oldState[ii]
newMap[nameMap[ii]] = newState[ii]
}

// allow user's to over-ride whether you do auditing for them.
if(! callHandlersOnly(event.getEntity())) {
logChanges(newMap,oldMap,event,entityId,'UPDATE',entityName)
}

executeSimpleHandler(event,'onUpdate')
if(hasChanged(oldState,newState)) {
executeComplexHandler(event,'onChange',oldMap,newMap)
}
}

private boolean hasChanged(oldState,newState) {
for(int ii = 0; ii if(oldState[ii] != newState[ii]) {
return true
}
}
return false
}

public void initialize(final Configuration config) {
// TODO Auto-generated method stub
return
}

static excludedProps = [
'id','version',
'log','class','errors',
'auditable','constraints',
'mapping','searchable'
]
def logChanges(newMap,oldMap,parentObject,persistedObjectId,eventName,className) {
if(newMap && oldMap) {
newMap.each({key,val->
if(!excludedProps.contains(key)) {
if(val != oldMap[key]) {
def audit = new AuditLogEvent(
actor:'', // TODO figure out how to log the actor later
className:className,
eventName:eventName,
persistedObjectId:persistedObjectId.toString(),
propertyName:key,
oldValue:oldMap[key].toString(),
newValue:newMap[key].toString()
)
if(!audit.save()) {
println audit.errors
}
}
}
})
}
else if(newMap && detailed) {
newMap.each({key,val->
if(!excludedProps.contains(key)) {
def audit = new AuditLogEvent(
actor:'', // TODO figure out how to log the actor later
className:className,
eventName:eventName,
persistedObjectId:persistedObjectId.toString(),
propertyName:key,
oldValue:null,
newValue:val.toString(),
)
if(!audit.save()) {
println audit.errors
}
}
})
}
else if(oldMap && detailed) {
oldMap.each({key,val->
if(!excludedProps.contains(key)) {
def audit = new AuditLogEvent(
actor:'', // TODO figure out how to log the actor later
className:className,
eventName:eventName,
persistedObjectId:persistedObjectId.toString(),
propertyName:key,
oldValue:val.toString(),
newValue:null
)
if(!audit.save()) {
println audit.errors
}
}
})
}
else {
// I think this is dead code now...
def audit = new AuditLogEvent(
actor:'', // TODO figure out how to log the actor later
className:className,
eventName:eventName,
persistedObjectId:persistedObjectId.toString()
)
if(!audit.save()) {
// TODO don't use println...
println audit.errors
}
}
return
}

def executeSimpleHandler(event,handler) {
def entity = event.getEntity()
if(isAuditableEntity(event) && entity.metaClass.hasProperty(entity,handler)) {
try {
entity."${handler}"(getEntityId(event))
} catch (groovy.lang.MissingMethodException mme) {
// TODO don't use println here...
println "${handler} has to take no parameters!"
}
}
}

def executeComplexHandler(event,handler,oldState,newState) {
def entity = event.getEntity()
if(isAuditableEntity(event) && entity.metaClass.hasProperty(entity,handler)) {
try {
entity."${handler}"(oldState,newState)
} catch (groovy.lang.MissingMethodException mme) {
try {
entity."${handler}"()
} catch (groovy.lang.MissingMethodException mme2) {
// TODO don't use println here...
println "${handler} has to take two or no parameters!"
}
}
}
}
}

Now once you've got your listener in just the shape you need it in. Take a look at the plug-in file AuditLoggingGrailsPlugin.groovy again.

There are several closures that while not documented extensively are rather self explanatory. In our example plug-in project we will are hooking into the GORM session factory. That means we want to work in the doWithApplicationContext closure.

This closure takes a parameter which by default is set up as the applicationContext if you take a look at the source code for The Hibernate Events Plugin you can see in the file HibernateEventsGrailsPlugin.groovy how Keving Burke has used the application context to get the sessionFactory and then registered the listeners with the session factory. I've only slightly adapted this here...

def doWithApplicationContext = { applicationContext ->
def listeners = applicationContext.sessionFactory.eventListeners
def listener = new AuditLogListener()

['preDelete','postInsert', 'postUpdate',].each({
addEventTypeListener(listeners, listener, it)
})

}

This closure is very interesting, it is pulling the sessionFactory out of the applicationContext and getting the array of eventListeners. Then we create an instance of our new event listener... finally we register the event listener with the session factory for each type of event it can accept.

The next method is very Groovy we are creating a string name for the particular type of event listener and then using Groovy's Meta features to extract the array of listeners of that type. The rest of the method is about manipulating the object array and registering the new listener.
 
def addEventTypeListener(listeners, listener, type) {
def typeProperty = "${type}EventListeners"
def typeListeners = listeners."${typeProperty}"

def expandedTypeListeners = new Object[typeListeners.length + 1]
System.arraycopy(typeListeners, 0, expandedTypeListeners, 0, typeListeners.length)
expandedTypeListeners[-1] = listener

listeners."${typeProperty}" = expandedTypeListeners
}

.. this is a very clever twist that injects a set of new listeners into the already created sessionFactory and does so using Groovy specific programming tricks. That call to listeners."${typeProperty}" is dynamically getting at "preDeleteEventListeners" and "postUpdateEventListeners" each in turn. That's very groovy. The method's result is a modified and registered listener array inside a running sessionFactory. Very cool.

Now that we have a listener and code to register the listener we need to run the packager to get the plug-in ready to be installed in another Grails project. Inside the project folder just run...

$ grails package-plugin

... in my example project this command produces the zip file grails-audit-logging-0.1.zip. Now to install your new plug-in in any Grails project just specify the path and use the grails plugin-install command. For example from another project you would install the audit-logging plug-in with this command...

$ grails install-plugin ../audit-logging/grails-audit-logging-0.1.zip

Now when you run your grails application you should see that the plug-in is installed and you can start using the plug-in's domain class conventions.

NOTE: I've posted the grails-audit-logging-0.2.zip plugin here and I've written it up here if you are just interested in audit logging.

You've seen how easy it is create a grails plug-in that can do very sophisticated things using Java, Hibernate, and Spring API's. This type of project shows the real strength of Groovy and Grails by allowing you to leverage existing Java libraries and code to create new kinds of features rapidly and easily that would have been more work in some other frameworks.

I hope this inspires you to create your own plug-ins that do amazing things and add to the power of the Grails framework. Happy coding!

2008-04-03

Groovy Grails and Searchable: The wait is OVER.

I honestly thought I'd be talking about how great Searchable is going to be for at least another six months... and then this happened: Searchable Plugin: Just use it! . It literally just works. WOW! I'm so happy right now I think I need to dance!

I downloaded the new searchable plugin and installed it into my latest Grails project. Suddenly I can start using search on all my properly configured domain classes. I immediately added a search bar to my application even though it's not in the requirements.

This is a fantastic win for Grails. I am so shocked we have a version that works so well so soon. Honestly, start using Grails now just so you can use this plugin. Go! Do it now! Go!

2008-02-29

Grails app.version & plugin utopia

I was catching up on my reading recently when I came across
this post. The technique Darryl is using to show application version numbers to his users is:
v${grailsApplication.metadata.'app.version'}

Which makes perfect sense to me. In fact, I don't understand why it's not in the Grails defaults to show the version number. If you add that line to your grails-app/views/layouts/main.gsp or add the version number to the default title every page would get a version stamp. I can't see how it would be obtrusive either since you could remove it or change where it was placed yourself later.

The other news that I think is interesting on aboutGroovy.com is the Selenium plugin for Grails. This is indicative of what could be a major selling point for Grails. It can easily make use of all kinds of existing Java tools as plugins. And do it today.

But, it's not only that you get to use these tools, Grails provides an interesting distribution mechanism for the tools as well.

You install grails plugins from the command line and you list out available plugins the same way for example:

$ grails list-plugins

Welcome to Grails 1.0 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: /usr/local/grails

Base Directory: /home/shawn/workspace/myproject
Environment set to development
Running script /usr/local/grails/scripts/ListPlugins.groovy
[mkdir] Created dir: /home/shawn/.grails/1.0/plugins
Plugins list cache does not exist or is broken, recreating ...
Plugins list cache has expired. Updating, please wait... ...

Plug-ins available in the Grails repository are listed below:
-------------------------------------------------------------

acegi -- No description available
aop -- No description available
autorest -- No description available
axis2 <0.1> -- Add Web Service support for Grails services using Apaxhe Axis2.
cache -- No description available
cas-client <0.2> -- This plugin provides client integration for JA-SIG CAS
code-coverage <0.7> -- Generates Code Coverage reports
converters <0.3> -- Provides JSON and XML Conversion for common Objects (Domain Classes, Lists, Maps, POJO)
dbmapper <0.1.7> -- Database relation to domain object generator
dbmigrate <0.1.5> -- Database migration tasks
djangotemplates -- No description available
dojo <0.4.3> -- Provides integration with the Dojo toolkit http://dojotoolkit.org, an Ajax framework.
dwr <0.1> -- This plugin adds DWR capabilies to the services in a Grails application.
eastwood-chart <0.1> -- This plugin wraps JFreeChart's Eastwood servlet for Grails app
echo2 <0.1> -- Echo2 capabilities to Grails applications
ext-ui -- No description available
fck-editor <0.3> -- FCKeditor
feeds <1.2> -- Render RSS/Atom feeds with a simple builder
flex <0.1> -- Provides integration between Grails and Flex
google-chart <0.4.3> -- This plugin adds Google Chart API features to Grails applications.
gwt <0.2.4> -- The Google Web Toolkit for Grails.
i18n-templates <1.0.1> -- I18n Templates
ivy <0.1> -- The Ivy Dependency Manager for Grails.
jasper <0.5> --
jcaptcha <0.2> -- Grails JCaptcha Plugin
jcr -- No description available
jms <0.3> -- This plugin adds MDB functionality to services.
jsecurity <0.1.1> -- Security support via the JSecurity framework.
jsf -- No description available
laszlo <0.6> -- OpenLaszlo plugin for Grails
ldap <0.1> -- Adds easy to use LDAP connectivity
liquibase <1.5.2.0> -- LiquiBase Database Refactoring for Grails
modalbox <0.1> -- This plugin adds the ModalBox to your Grails applications.
mondrian <0.1> -- This plugin installs Pentaho Mondrian into your Grails application.
ofchart <0.5> --
portlets -- No description available
quartz <0.2> -- This plugin adds Quartz job scheduling features to Grails application.
radeox <0.1> -- A plugin for the Radeox Wiki-render engine to allow easy markup and cross-linking.
remoting <0.3> -- Adds easy-to-use server-side and client-side RPC support.
richui <0.2> -- Provides a set of AJAX components
scaffold-tags <0.7> -- Adds tags to support fully-customizable & dynamic scaffolding
searchable <0.4-SNAPSHOT> -- Adds rich search functionality to Grails domain models.
staticresources -- No description available
webtest <0.3> -- A plug-in that provides functional testing for Grails using Canoo Web Test
wicket <0.3> -- Provides integration between Grails and the Wicket framework
xfire <0.7.4> -- Add Web Service support for Grails services using XFire.
xmlrpc <0.1> -- This plugin adds XML RPC functionality to GRAILS
yui <2.5.0> -- Yahoo! User Interface Library (YUI)
yui-widgets <0.1> -- Yahoo! User Interface Library (YUI) Widgets

To find more info about plugin type 'grails plugin-info [NAME]'

To install type 'grails install-plugin [NAME] [VERSION]'

For further info visit http://grails.org/Plugins


This is interesting not because this is new for Java programs, but, because the Grails plugin repository is now a Java Software distribution system. I think we'll see this eventually morph into something like a Gentoo or CPAN type distribution system where the real "all-stars" of development are known for having useful or interesting plugins.

The problem is. Even now this list is getting big. One of the attractions to Grails for new developers... like it or not... is that there are fewer choices. It's kind of funny but too many choices (as Scott Davis has pointed out in his "Paradox of Choice" talk) leads to people making no choice at all.

So already there are getting to be too many choices in Grails plugins. Now, admittedly, if you stuck to 1.0 and above releases you actually have only 4 plugins to choose from. But, very soon many of those "beta" plugins will be 1.0 and either way you still see them... still deal with the choice. This is an interesting problem that is going to be coming very rapidly for Grails. How do you deal with all that adoption and all that interest.

I think it will be very vital how the Grails team handles the next 18 months. They will have to find a way to deal with all the plugin contributions they get and how to limit choice intelligently.

Will they go the CPAN route? I'd like that since CPAN was (and technically still is) the best feature of Perl. Just as Ruby Gems is also a vital feature of Ruby. But, Gems lacks some of the features that made CPAN so popular. CPAN has code author based search and other web search features that allow you to read through PerlDoc (the Perl version of JavaDoc) for each module. The author recognition was not vital to Perl or CPAN but I think it helped authors to make contributions.

Will they choose an elitist mentality and have "levels" of plugins which are gold, silver, bronze? Sort of a Gentoo approach which was the best feature of Gentoo. Or perhaps it's too soon to think about yet. After all there aren't too many plugins yet...

At some point we'll need to see some categorization from the command line of plugins. We'll need search features for plugins. And, we'll need a mechanism for at least three levels of plugins... "production", "test", and "development" because we will need to allow for beta code to be distributed... and for developers to work on advanced features that rely on other plugins that aren't even released yet.

We'll need to do some level of OSGI in Grails... at least something that handles plugin dependencies and manages them. At some point the Grails ecosystem could grow large enough that you could have plugins that require other plugins. That could get very interesting.

2007-11-17

Grails Searchable Plugin... wow.

Words fail me Searchable plugin for Grails... wow. I'm speechless... I'm... wow. I just don't know what to say... wow.

Is anybody using this yet? I'm going to have to plug this into my very next project... wow.

2007-11-07

Grails XFire Plugin

Had a little trouble with the XFire Plugin for Grails. I found this thread of posts on
nabble which clears things up. I attached the thread to the appropriate bug. Other than that I noticed that java.util.Date Is turned into a string. To fix this I created DTO (Data Transport Object) with XMLGregorianCalendar instead. This works find for DTO but could cause a minor headache if I have to expose domain classes directly through a SOAP service. Other than that the XFire plugin handles everything I need it to so far.

XFire is apparently on the verge of getting replaced with CXF which is an Apache incubator project. I was able to work through an CXF service myself using the JSR-181 annotations (that's the Java Web Services meta data specification). Configuring CXF inside a Spring container was the usual Spring XML-er-ific mess that you run into when doing Spring work without a tool kit.

Integrating the CXF Spring config with Grails worked up until I hit a class incompatability between Groovy 1.1 and CXF somewhere down in the XML parsers. I caught just enough to see that it was a class version problem around the DATETIME type where Groovy had one version and CXF had another. That was enough for me to decide to stick with the XFire plugin. I don't want to be in the business of building my own versions of anything that I didn't write.

I figure if the XFire plugin is that simple to set up then it will probably be just that simple to "port" my services to CXF later. Grails does a pretty good job of letting you switch between DWR, Prototype, and YUI when you are working in Ajax... so why not have multiple SOAP plugins that swap out just as easily?