Skip to content

Instantly share code, notes, and snippets.

@robpatrick
Created November 3, 2013 12:47
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robpatrick/7289943 to your computer and use it in GitHub Desktop.
Save robpatrick/7289943 to your computer and use it in GitHub Desktop.
The Grails ORM technology - GORM, has the ability to auto-timestamp GORM objects with the date and time they were created and last updated, details can be found here: http://grails.org/doc/latest/guide/GORM.html#eventsAutoTimestamping Our developers didn't like to have to litter our GORM code with these attributes so I created an AST transformat…
package com.alkalinezoo.transform
import java.lang.annotation.Target
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import org.codehaus.groovy.transform.GroovyASTTransformationClass
/**
* Simple annotation that when used on a class adds two new fields of type {@code java.util.Date}
* the fields are:
* <p>
* {@code Date dateCreated}
* <p>
* {@code Date lastUpdated}
* <p>
* This annotation is primarily designed to be used with the GORMs auto-timestamp functionality, but can
* be used on any groovy class that requires these fields.
*
* @author robpatrick
*/
@Target([ElementType.TYPE])
@Retention(RetentionPolicy.SOURCE)
@GroovyASTTransformationClass(['com.alkalinezoo.transform.CreatedAndLastUpdatedASTTransformation'])
@interface CreatedAndLastUpdated {
}
package com.alkalinezoo.transform
import org.codehaus.groovy.transform.GroovyASTTransformation
import org.codehaus.groovy.control.CompilePhase
import org.codehaus.groovy.transform.ASTTransformation
import org.codehaus.groovy.ast.ASTNode
import org.codehaus.groovy.control.SourceUnit
import com.alkalinezoo.transform.CreatedAndLastUpdated
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.FieldNode
import org.codehaus.groovy.ast.PropertyNode
import org.codehaus.groovy.ast.AnnotationNode
/**
* AST Transformation that add the {@code dateCreated} and {@code lastUpdated}
* properties to the class annotated with the {@Link CreatedAndLastUpdated}
* annotation.
*
* @author robpatrick
*/
@GroovyASTTransformation( phase = CompilePhase.CANONICALIZATION )
class CreatedAndLastUpdatedASTTransformation implements ASTTransformation {
private static final String DATE_CREATED = 'dateCreated'
private static final String LAST_UPDATED = 'lastUpdated'
/**
* The main method to implement from ASTTransformation that is called by the compiler.
* It looks for the annotated node and adds the two new properties it.
*
* @param astNodes The astNodes from the annotated class.
* @param sourceUnit Provides an anchor for a single source unit (usually a script file)
* as it passes through the compiler system. Not used.
*/
@SuppressWarnings( 'UnusedMethodParameter' )
void visit( ASTNode[] astNodes, SourceUnit sourceUnit ) {
if ( safeToAddProperties( astNodes, CreatedAndLastUpdated ) ) {
ClassNode classNode = (ClassNode)astNodes[1]
if ( !classNode.getDeclaredField( DATE_CREATED ) ) {
addProperty( classNode, DATE_CREATED, Date )
}
if ( !classNode.getDeclaredField( LAST_UPDATED ) ) {
addProperty( classNode, LAST_UPDATED, Date )
}
}
}
/**
* This method adds a new property to the class. Groovy automatically handles adding the getters and setters so you
* don't have to create special methods for those.
*
* @param classNode The node to which the properties will be added.
* @param propertyName name of the property to add to the node.
* @param propertyType the type of the property to add to the class.
*/
private void addProperty( ClassNode classNode, String propertyName, Class propertyType ) {
FieldNode field = new FieldNode( propertyName, PropertyNode.ACC_PUBLIC, new ClassNode( propertyType ), new ClassNode( classNode.class ), null )
classNode.addProperty( new PropertyNode( field, PropertyNode.ACC_PUBLIC, null, null ) )
}
/**
* Defensive code against the compiler changing in future versions and not passing what we are expecting. More details can be
* found here: {@link http://joesgroovyblog.blogspot.co.uk/2011/09/ast-transformations-transformation.html}.
*
* @param astNodes The astNodes from the annotated class.
* @param annotationType The class of the annotation for which the transformation should have been called.
* @return true if it is sage to
*/
private Boolean safeToAddProperties( ASTNode[] astNodes, Class annotationType ) {
if ( !astNodes || !astNodes[0] || !astNodes[1] || !( astNodes[0] instanceof AnnotationNode ) || astNodes[0].classNode?.name != annotationType.name ) {
return false
}
true
}
}
package com.alkalinezoo.example
import com.alkalinezoo.transform.CreatedAndLastUpdated
/**
* Example of how the {@link CreatedAndLastUpdated} annotation is used in a Grails GORM class.
*
* @author robpatrick
*/
@CreatedAndLastUpdated
class CreatedAndLastUpdatedExample {
String name
String description
static constraints = {
description maxSize: 1000
}
}
@ilopmar
Copy link

ilopmar commented Jan 3, 2014

Thank you for sharing it. Could you please post the tests?

Regards, Iván.

@sbglasius
Copy link

This should be a plugin instead of just a gist. Great idea!

@robpatrick
Copy link
Author

@sbglasius I think it is already. Since writing this I have been told about this plugin: http://grails.org/plugin/timestamped I've not used it myself, but it might be worth looking at.

@donalmurtagh
Copy link

If you're using a sufficiently recent version of Groovy a trait is a simpler solution, eg. .

trait Timestamped {
    Date dateCreated
    Date lastUpdated
}

class MyDomainClass implements Timestamped {
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment