This is good stuff: "Make a Builder" (I think I'm about to get busy). I also think that this is why Groovy is one of the best languages to work in right now. What I love about that post is that it includes the Java code for how to build a builder and how that then affects your code.
Extending any platform built on Groovy is probably just this simple. The practical examples are absolutely price-less.
2008-04-29
2008-04-28
Changing the face of UI
I have talked previously about how I felt about many of the UI building environments out there today. My UI experience comes from a nearly decade old C/C++ experience in CaveGL, OpenGL, and GLUT... so I can't speak much to Java UI. Fact is I've never had to build a full Java UI for a commerical project before. I've only had toy Java UI projects.
Recently, when an opportunity to do real Java UI work landed in my lap, I jumped on it a little too eagerly. In my time working with Java UI I've found that unlike my days with OpenGL and other open source GUI rendering tool kits the ones for Java are comparatively opaque.
Part of what worked well in those "primitive" environments was how easy it was to stumble upon or discover the API and design a system with trial-and-error. My feeling is you can't do this in Eclipse because the system has too many moving parts to just tinker with and discover. I feel strongly that software on any level should encourage play and discovery and be tolerant of screw-ups by the user.
I've also mentioned that UI should perhaps be a descriptive exercise. That is to say that the look of UI should be described and computed. The function should be meticulously programmed but the look and feel should be easily changed and readily played with.
Today I found this project, Java Builder. The idea behind Java Builder is exactly what I've been trying to express in words. Java builder provides a YAML based DSL to describe Java UI. The YAML is turned into straight Java.
This is brilliant work that is moving in the right direction.
Recently, when an opportunity to do real Java UI work landed in my lap, I jumped on it a little too eagerly. In my time working with Java UI I've found that unlike my days with OpenGL and other open source GUI rendering tool kits the ones for Java are comparatively opaque.
Part of what worked well in those "primitive" environments was how easy it was to stumble upon or discover the API and design a system with trial-and-error. My feeling is you can't do this in Eclipse because the system has too many moving parts to just tinker with and discover. I feel strongly that software on any level should encourage play and discovery and be tolerant of screw-ups by the user.
I've also mentioned that UI should perhaps be a descriptive exercise. That is to say that the look of UI should be described and computed. The function should be meticulously programmed but the look and feel should be easily changed and readily played with.
Today I found this project, Java Builder. The idea behind Java Builder is exactly what I've been trying to express in words. Java builder provides a YAML based DSL to describe Java UI. The YAML is turned into straight Java.
This is brilliant work that is moving in the right direction.
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...
... 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...
...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...
... the same trick will read the id of the authenticated user. But, if you are using CAS then you can do this...
... 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.
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:
... but I found that I could do this instead ...
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.
def nameMap = [:]
for(int ii=0; iidef 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:
... 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...
... 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.
So let's first create a Listener class that implements these listeners:
Each of those interfaces will demand their own methods... specifically...
...
... and ...
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:
... 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:
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...
... 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...
... 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...
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...
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.
.. 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...
... 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...
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!
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; iiif(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-16
Audit Logging in Grails
EDIT: Audit Logging is now available via the grails command line tool
Here's the story of how I ended up writing an AuditLogging plugin for Grails. I've posted the Grails Audit Logging plugin here. And I'll do a more formal write-up later. A demo project is here if you want to see it in action.
So, my story.
I have a project that I needs audit logging for certain objects in my domain. As usual (in the last six months) I started the project using Grails because of it's powerful and full featured GORM tool. GORM in case you didn't already know is built on top of Hibernate. So that means deep down we have the powerful hibernate events model to hook into.
A couple of years ago on the LAMP stack I would have written triggers in my database to handle the changes to certain tables. This would have the distinct disadvantage of not having any knowledge of the actual domain model that the table was supporting.
Today I can hook into Hibernate and get the same results keeping my domain class and triggers nice and close to each other.
Honestly, I would have probably went with the in database triggers and felt bad about it. Thank goodness I stumbled on this blog entry by Kevin Burke I would have had a much less interesting and satisfying project. Instead Kevin's influence caused me to create the AuditLogging plugin for Grails.
Kevin had the need to hook into the Hibernate Events model, and had already written the Grails Hibernate Events Plugin. I was still a few weeks off from needing this plugin but I was glad to see it existed. Frankly, I'd be utterly lost without Kevin's plugin to show me how to work with Grails and Hibernate Events.
In true groovy fashion Kevin's work leans heavily on closures. He's added ten new conventions to GORM domain classes for use in the events plugin. I won't list them all there but the ones I was interested in were the afterInsert, afterUpdate, and afterDelete events and their matching closures.
When fired the event handler will call each closure from the matching org.hibernate.event.*Listener class. Kevin's project doesn't need to see the old values and compare them to the new values. So he doesn't pass these back down to the handlers defined in the class. Unfortunately for me. That's not going to work. I need audit logging and I need to be able to do custom things for some domain classes when certain values change. Pretty tall order actually.
I spent some time reading up on events in Chapter 12 of the Hibernate 3 documentation. And taking a look at the Hibernate documentation I can see that any of the event objects nicely expose the Hibernate internals. In specific using the event object I can snag the EntityPersister that fired the event. That's interesting since I need to be able to see specifically what changed and throw one of 35 different event objects (that's a specific detail of my current project) out into GORM when it happens.
In particular I want to have a very groovy plugin that can be turned on and off for a given domain class using a convention like searchable does... I want it to be able to specify an "onChange" handler that can see old values and new values and do different things depending on what changed and how it changed... yeah... a pretty tall order.
That's why I'm so surprised at how easy this was to write. I was lucky to have the work of Rob Monie and even Kevin's Hibernate Events plugin to reference. The result is a plugin that you can use pretty simply.
To install my plugin just:
Then in your favorite domain class do this:
Now when the class is acted on a new AuditLogEvent record will be inserted. The record will record when the event happened and what changed. If it's an insert or a delete then it will only record that the event happened.
What if you don't want every detail logged but only wanted that onChange handler? Then you can specify
Now only the handlers you specify will be called. My plugin handles onSave, onUpdate, onDelete, and two types of onChange. The first type of onChange takes two parameters. The second kind takes none. In practice the no parameter onChange is identical to the onUpdate.
I'm putting this plugin to use in my project now. Hopefully someone else will find this useful. This work is still very rough but I will be torture testing the plugin in my own project which will have thousands of events per second. Any input is appreciated.
EDIT see also Grails Audit Logging Plugin
$ grails install-plugin audit-logging
Here's the story of how I ended up writing an AuditLogging plugin for Grails. I've posted the Grails Audit Logging plugin here. And I'll do a more formal write-up later. A demo project is here if you want to see it in action.
So, my story.
I have a project that I needs audit logging for certain objects in my domain. As usual (in the last six months) I started the project using Grails because of it's powerful and full featured GORM tool. GORM in case you didn't already know is built on top of Hibernate. So that means deep down we have the powerful hibernate events model to hook into.
A couple of years ago on the LAMP stack I would have written triggers in my database to handle the changes to certain tables. This would have the distinct disadvantage of not having any knowledge of the actual domain model that the table was supporting.
Today I can hook into Hibernate and get the same results keeping my domain class and triggers nice and close to each other.
Honestly, I would have probably went with the in database triggers and felt bad about it. Thank goodness I stumbled on this blog entry by Kevin Burke I would have had a much less interesting and satisfying project. Instead Kevin's influence caused me to create the AuditLogging plugin for Grails.
Kevin had the need to hook into the Hibernate Events model, and had already written the Grails Hibernate Events Plugin. I was still a few weeks off from needing this plugin but I was glad to see it existed. Frankly, I'd be utterly lost without Kevin's plugin to show me how to work with Grails and Hibernate Events.
In true groovy fashion Kevin's work leans heavily on closures. He's added ten new conventions to GORM domain classes for use in the events plugin. I won't list them all there but the ones I was interested in were the afterInsert, afterUpdate, and afterDelete events and their matching closures.
When fired the event handler will call each closure from the matching org.hibernate.event.*Listener class. Kevin's project doesn't need to see the old values and compare them to the new values. So he doesn't pass these back down to the handlers defined in the class. Unfortunately for me. That's not going to work. I need audit logging and I need to be able to do custom things for some domain classes when certain values change. Pretty tall order actually.
I spent some time reading up on events in Chapter 12 of the Hibernate 3 documentation. And taking a look at the Hibernate documentation I can see that any of the event objects nicely expose the Hibernate internals. In specific using the event object I can snag the EntityPersister that fired the event. That's interesting since I need to be able to see specifically what changed and throw one of 35 different event objects (that's a specific detail of my current project) out into GORM when it happens.
In particular I want to have a very groovy plugin that can be turned on and off for a given domain class using a convention like searchable does... I want it to be able to specify an "onChange" handler that can see old values and new values and do different things depending on what changed and how it changed... yeah... a pretty tall order.
That's why I'm so surprised at how easy this was to write. I was lucky to have the work of Rob Monie and even Kevin's Hibernate Events plugin to reference. The result is a plugin that you can use pretty simply.
To install my plugin just:
$ grails install-plugin http://shawn.hartsock.googlepages.com/grails-audit-logging-0.1.zip
Then in your favorite domain class do this:
class MyClass {
static auditable = true
Long id
Long version
String someValue
def onChange = { newMap, oldMap ->
oldMap.each({ key, oldVal ->
if(oldVal != newMap[key]) {
println " * $key changed from $oldVal to " + newMap[key]
}
})
}
}
Now when the class is acted on a new AuditLogEvent record will be inserted. The record will record when the event happened and what changed. If it's an insert or a delete then it will only record that the event happened.
What if you don't want every detail logged but only wanted that onChange handler? Then you can specify
static auditable = [handlersOnly:true]
Now only the handlers you specify will be called. My plugin handles onSave, onUpdate, onDelete, and two types of onChange. The first type of onChange takes two parameters. The second kind takes none. In practice the no parameter onChange is identical to the onUpdate.
I'm putting this plugin to use in my project now. Hopefully someone else will find this useful. This work is still very rough but I will be torture testing the plugin in my own project which will have thousands of events per second. Any input is appreciated.
EDIT see also Grails Audit Logging Plugin
2008-04-14
The PLEAC
The PLEAC Project over at SourceForge is a project that is taking all the cookbook examples in "The Perl Cookbook" and re-implementing them in various high-level languages.
The project will eventually act as sort of a Rosetta Stone for programming languages. Based on the PLEAC examples developers can see into the structure of another programming language quickly without first having to become schooled in that language.
If Rosetta Stone is too grand an idea for you... then think of the PLEAC as a pocket phrase-book for programmers traveling for the weekend in a foreign land... programmers who just want to order a beer and find the restroom.
As of this writing Groovy is the only other programming language that has 100% of the Perl Cookbook examples implemented in it. (Obviously, the Perl examples already existed.) I don't know if this is an indicator of the popularity of Groovy or some other indicator of how well non-Java programmers can pick up Groovy. It's very odd that the Ruby and PHP examples are not complete yet.
I would encourage folks no matter what programming language they use to please contribute to this project as it will be a valuable resource for anyone in any programming language. Just like the Rosetta Stone, it will only help to foster the exchange of ideas between the programming communities. Or, at least help you order a beer.
The project will eventually act as sort of a Rosetta Stone for programming languages. Based on the PLEAC examples developers can see into the structure of another programming language quickly without first having to become schooled in that language.
If Rosetta Stone is too grand an idea for you... then think of the PLEAC as a pocket phrase-book for programmers traveling for the weekend in a foreign land... programmers who just want to order a beer and find the restroom.
As of this writing Groovy is the only other programming language that has 100% of the Perl Cookbook examples implemented in it. (Obviously, the Perl examples already existed.) I don't know if this is an indicator of the popularity of Groovy or some other indicator of how well non-Java programmers can pick up Groovy. It's very odd that the Ruby and PHP examples are not complete yet.
I would encourage folks no matter what programming language they use to please contribute to this project as it will be a valuable resource for anyone in any programming language. Just like the Rosetta Stone, it will only help to foster the exchange of ideas between the programming communities. Or, at least help you order a beer.
2008-04-09
A nice bit of textbook groovy
Groovy is just so groovy. I've been working in Groovy since August of 2007 but I still take such pleasure from little things like this... take some tokens
now break them apart
now make the array a list...
now get groovy with the list...
... and check it against test case list ...
... and that just beats the socks off of stuffing the words in a hash then pulling out the keys. Putting it back together I have a single method now:
this method will pull apart a string with tagTokens and return a unique list of tags. Inspired by Graeme Rocher's book "The Definitive Guide to Grails" Listing 9-8. Literally a text book example of why Groovy is so groovy.
Back to our one-liner. I developed the return statement to the method parseTags using a groovy shell script. First, I put groovy in my path by editing my .profile or from the command line...
Once you have that you can run the script from the command line. The whole script reads... (file name: test.groovy)
Notice the #! it works because groovy is in our path. Now from the command line I run...
... if I chmod the test.groovy file executable it will run as a shell script. And that's just groovy. I can play with code now changing bits and re-running it over and over finding just the line I need. That little shell script is a test. In a Linux system it's the lightest test environment imaginable.
And this is a big deal because now I can script Java. I can call Java code from OS commands as easily as I call a shell script. It could mean I could write Java-esque scripting admin files to fire-up Java services like GlassFish or JBoss. That could be very groovy.
These little tweaks can make a huge difference.
def tokens = "abc,def foo+bar bar foo"
now break them apart
def parts = tokens.split("[+,\\s]")
now make the array a list...
def list = parts.toList()
now get groovy with the list...
def words = list.unique()
... and check it against test case list ...
assert words == ["abc", "def", "foo", "bar"]
... and that just beats the socks off of stuffing the words in a hash then pulling out the keys. Putting it back together I have a single method now:
static parseTags(String tagTokens) {
return tagTokens.split("[+,\\s]").toList().unique()
}
this method will pull apart a string with tagTokens and return a unique list of tags. Inspired by Graeme Rocher's book "The Definitive Guide to Grails" Listing 9-8. Literally a text book example of why Groovy is so groovy.
Back to our one-liner. I developed the return statement to the method parseTags using a groovy shell script. First, I put groovy in my path by editing my .profile or from the command line...
$ export PATH=$PATH:/path/to/groovy/bin
Once you have that you can run the script from the command line. The whole script reads... (file name: test.groovy)
#!/usr/bin/env groovy
assert "abc,def foo+bar bar foo".split("[+,\\s]").toList().unique() == ["abc", "def", "foo", "bar"]
println "If this prints then it worked." // otherwise the script will bomb out on the previous line
Notice the #! it works because groovy is in our path. Now from the command line I run...
$ groovy test.groovy
... if I chmod the test.groovy file executable it will run as a shell script. And that's just groovy. I can play with code now changing bits and re-running it over and over finding just the line I need. That little shell script is a test. In a Linux system it's the lightest test environment imaginable.
And this is a big deal because now I can script Java. I can call Java code from OS commands as easily as I call a shell script. It could mean I could write Java-esque scripting admin files to fire-up Java services like GlassFish or JBoss. That could be very groovy.
These little tweaks can make a huge difference.
Labels:
groovy
2008-04-03
A Groovy GeoCoder.us Client for Grails
I have a need for a Grails GeoCoder for a project I'm working on. I'm going to feed the GeoCodes to a RichUI front end using Google Maps. And, I'll have access to addresses in the format...
... so I've written a simple front-controller service to take in an address as a string, wash it into the format used by geocoder.us and pass it to geocoder.us then take the result and build a marker for RichUI.
In the grails-app/services directory:
You'll notice I used groovy.net.xmlrpc.* that's not in Grails by default. You'll have to download groovy-xmlrpc-0.3.jar from http://dist.codehaus.org/groovy/jars/ and put the file in your Grails' project's lib directory. If you are using an IDE you'll have to put that new jar file in the build path for your particular IDE so you can test the service.
If you have a class:
... then when you want to add a geocode location complete with a marker to your Grails RichUI project... all you have to do is ... from the controller:
... and in the show.gsp put at the top of the file:
... and somewhere inside the file:
NOTE: I've noticed Internet Explorer can have a hard time with the googlemaps tag in certain cases unless you wrap it in a div block.
And that's it. I highly recommend that you cache query results from geocoder.us to avoid putting too much load on their free service. If you plan on using this on a heavily used site of any kind I highly advise that you buy an account from GeoCoder it's a really fine service and deserves your support.
RichUI is a really nice tool kit to use and deserves some of your time too. Once you have this down you'll be geoCoding and google mapping so much you may have a hard time focusing on other features! Have fun!
EDIT: Check out these Google GeoCoder based posts by Ken Kousen or Justin Spradlin for how to use the Google GeoCoding services instead.
1600 Pennsylvania Ave
Washington, DC 20502
... so I've written a simple front-controller service to take in an address as a string, wash it into the format used by geocoder.us and pass it to geocoder.us then take the result and build a marker for RichUI.
In the grails-app/services directory:
import groovy.net.xmlrpc.*
// Shawn Hartsock sez: don't just use the free service, buy a geocoder.us account!
class GeoCoderService {
boolean transactional = true
static final serviceUrl = "http://geocoder.us/service/xmlrpc"
def geoCode(String address) {
def query = queryClean(address)
def proxy = new XMLRPCServerProxy(serviceUrl)
def result = proxy.geocode(query)
return result
}
def marker(String address) {
def res = geoCode(address)
if(res) {
def marker = [:]
marker['description'] = address.replaceAll("\n","<br/>")
marker['latitude'] = res['lat']
marker['longitude'] = res['long']
return marker
}
return null
}
def queryClean(String string) {
def query = string.replaceAll("\n",",")
query = query.replaceAll(",,",",")
return query
}
}
You'll notice I used groovy.net.xmlrpc.* that's not in Grails by default. You'll have to download groovy-xmlrpc-0.3.jar from http://dist.codehaus.org/groovy/jars/ and put the file in your Grails' project's lib directory. If you are using an IDE you'll have to put that new jar file in the build path for your particular IDE so you can test the service.
If you have a class:
class Address {
Long id
Long version
String address1
String address2
String city
String state
String postalCode
String postalRoutingCode
/** please forgive the multi-line string wrapping */
public String toString() {
return """${address1}${(address2)?"\n$address2":''}
${city}, ${state} ${postalCode}${(postalRoutingCode)?'-':''}${postalRoutingCode}"""
}
}
... then when you want to add a geocode location complete with a marker to your Grails RichUI project... all you have to do is ... from the controller:
class AddressController {
// reference the service...
GeoCoderService geoCoderService
// and later on ...
def show = {
/* code skipped */
return [
address: address,
markers: [geoCoderService.marker(address.toString())]
]
}
/* more code ... skipped for clarity */
}
... and in the show.gsp put at the top of the file:
NOTE: you'll have to register for a Google Maps key... see: RichUI-GoogleMaps
<resource:googlemaps key="myGoogleMapsKey" />
... and somewhere inside the file:
<g:if test="${markers}">
<div class="mapPane">
<richui:googlemaps markers="${markers}" zoomLevel="7" />
</div>
</g:if>
NOTE: I've noticed Internet Explorer can have a hard time with the googlemaps tag in certain cases unless you wrap it in a div block.
And that's it. I highly recommend that you cache query results from geocoder.us to avoid putting too much load on their free service. If you plan on using this on a heavily used site of any kind I highly advise that you buy an account from GeoCoder it's a really fine service and deserves your support.
RichUI is a really nice tool kit to use and deserves some of your time too. Once you have this down you'll be geoCoding and google mapping so much you may have a hard time focusing on other features! Have fun!
EDIT: Check out these Google GeoCoder based posts by Ken Kousen or Justin Spradlin for how to use the Google GeoCoding services instead.
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!
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!
Labels:
grails,
groovy,
plugin,
search,
searchable
2008-04-01
Dominant Groovy
Steven Devijver has an interesting post over at java.dzone.com titled Groovy will replace the Java language as dominant language
While I'd love to agree with Steven, I'm afraid I don't. Now... I think Groovy has the potential to be very very popular on the JVM and I think it is a wonderful way to work. Python didn't replace C. From Steven's own article:
No matter what programming language popularity chart you like to stare at and hit reload on in the middle of the night the top two languages today are Java and C not C++ but good old C. That means that even when some programmers could choose from Python, Perl, PHP, Ruby, Haskel, or a whole bevy of languages the still choose C. So I think we'll see the same on the JVM. Many programmers will have the chance to adopt other tools other than Java but the majority will probably choose to stay with Java.
This is a bit of a warning to Sun. C hasn't had new features in a while. Maybe Java shouldn't get new features so fast either? Creating new languages to support new features seems like a really good move to me.
I really like Groovy a lot and I sincerely hope Groovy makes it into the top 10 programming languages in the world in the next few years but I don't think Groovy will supplant Java in the near term. I can see a time when nearly all Java testing is done in Groovy and much web development. But, even when Grails gets "serious" they use Java.
I also don't buy the analogy of "Java is assembly to Groovy" argument. I think Groovy is to Java as Python is to C. This is a big deal on the JVM since scripting on the JVM has been very hard to do. I remember a long time ago there was talk of porting Perl to the JVM and that never really materialized. There is serious work today on Ruby for the JVM and that work seems to have legs.
The idea for multi-language VM is a very good idea. I also think it's counter productive to speculate on who will trump who for popularity. So far I've only seen this cause problems for people and create unnecessary friction. Soon there will be a very big swelling of Groovy support in niches where performance is trumped by developer productivity but where performance still holds sway over productivity and code output Java still reigns.
While I'd love to agree with Steven, I'm afraid I don't. Now... I think Groovy has the potential to be very very popular on the JVM and I think it is a wonderful way to work. Python didn't replace C. From Steven's own article:
Groovy is the dream child of James Strachan, extravagant open-source developer and visionary. While waiting for a delayed flight he was playing with Python and found it such a cool language that he decided the JVM needed to have a language like this too.
No matter what programming language popularity chart you like to stare at and hit reload on in the middle of the night the top two languages today are Java and C not C++ but good old C. That means that even when some programmers could choose from Python, Perl, PHP, Ruby, Haskel, or a whole bevy of languages the still choose C. So I think we'll see the same on the JVM. Many programmers will have the chance to adopt other tools other than Java but the majority will probably choose to stay with Java.
This is a bit of a warning to Sun. C hasn't had new features in a while. Maybe Java shouldn't get new features so fast either? Creating new languages to support new features seems like a really good move to me.
I really like Groovy a lot and I sincerely hope Groovy makes it into the top 10 programming languages in the world in the next few years but I don't think Groovy will supplant Java in the near term. I can see a time when nearly all Java testing is done in Groovy and much web development. But, even when Grails gets "serious" they use Java.
I also don't buy the analogy of "Java is assembly to Groovy" argument. I think Groovy is to Java as Python is to C. This is a big deal on the JVM since scripting on the JVM has been very hard to do. I remember a long time ago there was talk of porting Perl to the JVM and that never really materialized. There is serious work today on Ruby for the JVM and that work seems to have legs.
The idea for multi-language VM is a very good idea. I also think it's counter productive to speculate on who will trump who for popularity. So far I've only seen this cause problems for people and create unnecessary friction. Soon there will be a very big swelling of Groovy support in niches where performance is trumped by developer productivity but where performance still holds sway over productivity and code output Java still reigns.
Subscribe to:
Posts (Atom)