When we last saw our heroes, the first brave Grails' objects were fumbling about in the Java world, lost without their powers. The next step in our intrepid Objects' epic journey, however, will be to discover how to use and control their GORM powers while in Javaland.
Let's define a simple Grails domain model consisting of a Book Object. As the Book Object is compiled by the Grails class loader we can't define references to the class in our Java classes. We could however define an IBook interface and have the Grails Book implement it. In fact, as they take their first baby steps in the Java world, we will have our Gorm objects directly implement a simple interface so that we can reference them e.g.
class Book implements IBook{
String title
}
public interface IBook{
public String getTitle();
public void setTitle(String title);
}
There is, however, a major problem with this approach. There are many methods available on the Book object that won't be there at compile time, this may cause the Grails compiler to barf. A better approach is to have a Dynamic proxy implement the interface and call our Groovy Object. This way we can call any of the dynamic Gorm methods we wish. And we will do this, but for simplicity - not yet.
Getting started down the dynamic road
The simplest place to start is probably with the static methods available on GORM Classes. First let's define an interface that includes all of the common base finder methods..
public interface IStatic<DomainType,IDType> {
public Integer count();
public Integer countBy(String criteria,Object... args);
public grails.orm.HibernateCriteriaBuilder createCriteria();
public Boolean exists(IDType id);
public<E> E executeQuery(String query,Object... args);
public<E> E executeQuery(Closure query); //-- we could also embed support for a SQL DSL..
public DomainType find(String query,Object... args);
public DomainType find(DomainType example);
public DomainType find(Closure query);
public List<DomainType> findAll(String query,Object... args);
public List<DomainType> findAll(Closure query);
public DomainType findBy(String query,Object... args);
public List<DomainType> findAllBy(String query,Object... args);
public DomainType findWhere(Map params);
public List<DomainType> findAllWhere(Map params);
public DomainType get(IDType params);
public List<DomainType> getAll(IDType... ids);
public List<DomainType> list();
public List<DomainType> list(Map params);
public List<DomainType> listOrderBy(String query,Map params);
public<E> E withCriteria(Closure query);
public void withTransaction(Closure transaction);
}
We can use this interface as a base, by extending it to include the specific dynamic finder methods we want to use. N.B. Although we will not have to provide any implementation for these methods, we do need to let statically typed Java know which methods we'd like to be able to use.
Calling static finder methods
Next let's create a generic dynamic invocation handler that can call any GORM dynamic finder method for us. All it needs is a single instance of the GORM class we wish to process.
public class Static<DomainType,IDType> implements InvocationHandler {
GroovyObject gormModel;
public Static(GroovyObject model){
this.gormModel = model;
}
/* (non-Javadoc)
* @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
*/
public Object invoke(Object target, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
Object result = gormModel.getMetaClass().
invokeStaticMethod(null,method.getName(),
args);
return result;
}
Creating the GORM-Java proxier
It might be nice to have a standard way to create instances of our GORM invocation handler - so let's define a factory to create them. This will come in handy later when we need to perform so jiggery-pokery to proxy the models returned themselves.
public class StaticFactory {
public IStatic create(GroovyObject key) {
return (IStatic) java.lang.reflect.Proxy.newProxyInstance(
getClass().getClassLoader(),
new Class[]{IStatic.class},
new Static(key));
}
}
Trying it out
Remember our DAO from last week? If instead of putting full blown access here we put instead code to create an instance of our GORM Objects (our DAO lives in the Grails world) - we can use this to get a handle on our GORM models.
public class Access implements IAccess{
def access = [
book : {new Book()}
]
public Object access(String key){
def m = access[key]
return m.call()
}
public Object access(String key,Object [] args){
return access[key].call(args)
}
}
We can create our Access instance as last week, via reflection. Let's assume we've done that. The code below shows how to create a Static instance capable of executing the GORM dynamic finders. Instead of calling those methods directly on the class we simply get a handle on the appropriate IStatic instance and call the method on that.
public List listAllBooksFromStatic(IAccess gorm){
StaticFactory factory = new StaticFactory();
GroovyObject bookModel = gorm.access("book");
IStatic<IBook,Long> Book = factory.create(bookModel);
List<IBook> books = Book.list();
for(IBook book : books){
System.out.println("Next book " + book.getTitle());
}
return books;
}
But what about the really dynamic methods?
Let's try and find all Books by title, first let's create an interface that extends IStatic
public interface IStaticBook<DomainType,IDType> extends IStatic<DomainType,IDType> {
public IBook findByTitle(String title);
}
Then plug that interface in the factory instead of IStatic. Later on we'll plug in an Interface resolver into the factory rather than hard coding the interface to use.
public IStatic create(GroovyObject key) {
return (IStatic) java.lang.reflect.Proxy.newProxyInstance(
getClass().getClassLoader(),
new Class[]{IStaticBook.class},
new Static(key));
}
And let's modify our access code and find by title
public void findByTitle(IAccess gorm){
StaticFactory factory = new StaticFactory();
GroovyObject bookModel = gorm.access("book");
IStaticBook<IBook,Long> Book = factory.create(bookModel);
IBook book = Book.findByTitle("The Shining");
}
More to do
This code is fairly raw example code. You should ofcourse tidy up the creation of IStatic objects - in your application only one instance per type need actually exist. They are pretty good candidates for injection via DI container. But more work remains - we have yet to add support for the dynamic methods provided on the GORM objects themselves....
To be continued..
Resources
Downloads
Note the source is stored in jars due to upload file extension restrictions.