Last updated by admin 2 years ago
This page is deprecated

Compile Time Injection

You might be better served by using the ExpandoMetaClass

Grails contains an architecture to implement compile time injection of properties, methods etc. This is built on functionality provided by the GroovyClassLoader and is important when used in conjunction with Java libraries because they are not aware of the Groovy runtime environment and hence dynamic property injection does not work.

The core classes for compile time injection are found in the org.codehaus.groovy.grails.injection package. As of this writing only injection of properties into domain classes is supported (sorry if this is out-of-date when you read it). To do this create a class that implements the org.codehaus.groovy.grails.injection.GrailsDomainClassInjector and add it as a bean definition to the applicationContext.xml file.

The org.codehaus.groovy.grails.injection.GrailsInjectionOperation class will automatically look up implementors of the GrailsDomainClassInjector interface and execute them at which point you have the oppurtunity to add new properties before compilation occurs. Remember to check if they already exist otherwise you will get weird ClassFormatError errors. An example of injecting a property called {{id}} can be seen below:

import org.codehaus.groovy.grails.injection.*;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.classgen.GeneratorContext;
import org.codehaus.groovy.control.SourceUnit;

public class ExampleDomainClassInjector implements GrailsDomainClassInjector {

public void performInjection(SourceUnit source, GeneratorContext context, ClassNode classNode) { if(classNode.getGetterMethod("getId") != null) { classNode.addProperty("id", ClassNode.ACC_PUBLIC, ClassHelper.long_TYPE,null,null,null); } }

}






If you want to add methods, this process can become tricky. The groovy AST (Abstract Syntax Tree) MethodNode is a dom-like tree of command blocks, return blocks, etc. and can be tiresome to construct (unless someone has created an editor, if so please say!). I got round this by using the SourceUnit to parse and compile a String of groovy code into the MethodNode for me.

import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.classgen.GeneratorContext;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.grails.injection.GrailsASTUtils;
import org.codehaus.groovy.grails.injection.GrailsDomainClassInjector;

public class RandomInjector implements GrailsDomainClassInjector {

private static final Log LOG = LogFactory.getLog(GrailsDomainClassInjector.class);

public void performInjection(SourceUnit source, GeneratorContext context, ClassNode classNode) { injectRandom(classNode); }

private void injectRandom(ClassNode classNode) { final boolean hasRandom = GrailsASTUtils.implementsZeroArgMethod(classNode, "random"); if (!hasRandom) { String source = "public static " + classNode.getNameWithoutPackage() + " random(){" + "def ran = new Random()n " + ".get(ran.nextInt(" + ".count())+1)" + "}"; SourceUnit su = SourceUnit.create("mine", source); su.parse(); su.completePhase(); su.convert(); List l = su.getAST().getMethods(); if (l.size() > 0) { MethodNode mn = (MethodNode) l.get(0); if (LOG.isDebugEnabled()) { LOG.debug("[RandomInjector] Adding method ["+mn.getName()+"()] to class ["+ classNode.getName() + "] : "); } classNode.addMethod(mn); } } }

}






Some other discussion on the ast can be read on http://blackdragsview.blogspot.com/2006/11/chitchat-with-groovy-compiler.html