Grails Neo4j plugin 0.2 released

Today an important update of the Grails Neo4j plugin has been released. Neo4j is a graph database, it’s main concepts are described in brevity in a previous post.  The plugin provides a convenient way to use Neo4j as a persistence layer for Grails domain classes.

The key features / changes of this release are:

  • domain classes managed by Neo4j can now co-existing with traditional domain classes (aka mapped by Hibernate)
  • Upgrade to Neo4j 1.0
  • usage of Grails dependency resolution instead of embedding the jars in /lib directory
  • added a seperate controller to inspect the Neo4j node space
  • major refactoring using AST transformation, just like in the couchdb plugin
  • support for the Neo4j indexer
  • support for non-declared properties
  • support for traversers

In more detail:

Co-existence with other domain implementations

In the initial 0.1 release there was no possibility to have hibernate-based domain classes and neo4j-bases domain classes within the same application. By massively learning from the implementation of the CouchDB plugin, the neo4j plugin shares now the same principles:

  • each domain class annotated with “@Neo4jEntity” is managed by Neo4j
  • the @Neo4jEntity  annotation triggers a Groovy AST transformation at compile time. This transformation e.g. injects a reference to the Neo4j node instance in each domain class.
  • the plugin class delegates most of its work to a singleton-style plugin support class
  • the plugin support class uses Groovy MOP to inject (most of) the GORM methods into the domain classes

Upgrade to Neo4j 1.0

About two weeks ago, the Neo4j team released their 1.0 version. In order to keep pace, the 0.2 version of the plugin upgrades to this version.

Using dependency resolution

In its recent versions Grails provides a powerful dependency resolution DSL. Using this feature, the Neo4j plugin does not contain the neo4j jars itself any longer. Instead they are automatically downloaded upon plugin installation from the Neo4j’s maven repository.

Controller for visualizing the node space

The plugin contains a Neo4jController that provides an easy way to inspect and walk through the nodespace. All the properties and relationships are visible and navigable, see the screenshot. Screenshot Neo4jController

Aside from navigating through the nodespace, the Neo4jController also provides an action for viewing some statistics: counting nodes an relationship by type, and an inspector for viewing the Grails domain class structure. One word of warning: do not expose this controller to the public! Instead use e.g. the acegi plugin and protect it from public access.

Support for Neo4j Indexer

Since there is no easy way to ask the nodespace questions like “Give me all cars with more than 250 hp?”, Neo4j provides an addon that indexes property values. The plugin supports this in two ways:

  • Defining index properties in a domain class by annotating the properties to be indexed with @Neo4jIndexed
  • Using the indexer when the GORM methods ‘findBy<prop>’ or ‘findAllBy<prop>’ are called on an indexed property

The following code example will make that clearer:

import grails.plugins.neo4j.*
class Car {
   String licenseTag // normal, aka non-indexed property
   String color  // indexed property

Support for non-declared properties

Since Neo4j is a schema-less database, there is no limitation on what properties are allowed for a certain node. A Grails domain model is much more strict on this. To fill this gap, the plugin supports non-declared properties:

def car = new Car(licenseTag:'ABC123', color:'red')
assert 'red' == car.color // declared property
assert null = car.numberOfSeats // N.B: numberOfSeats is _not_ declared
car.numberOfSeats = 5
assert '5' == car.numberOfSeats

Using non-declared properties has one limitation: since there’s no type information available, they are stored as String. That’s why the last assert need ‘5’ in quotes.

Support for traversers

A very powerful method to search and find nodes in the nodespace are traversers. The plugin supports them on two levels:

  • static:  DomainClass.traverse uses the reference node as starting point and
  • instance: <domainClassInstance>.traverse uses the domainClassInstance as starting point.

Both variants come with some overloaded implementations:

traverse(StopEvaluator stopEvaluator, ReturnableEvaluator returnableEvaluator, Object... args)
traverse(Traverser.Order order, StopEvaluator stopEvaluator, ReturnableEvaluator returnableEvaluator, Object... args)
traverse(Closure stopEvaluator, Closure returnableEvaluator, Object... arg)
traverse(Traverser.Order order, Closure stopEvaluator, Closure returnableEvaluator, Object... args)

Note that StopEvaluators and ReturnableEvaluator might be coded in a ‘groovy’ way as closures:

def traverser = car.traverse(
 { true },  // StopEvaluator
 { it.currentNode().getProperty("color",null)=="red"} // ReturnableEvaluator

11 replies on “Grails Neo4j plugin 0.2 released”

Hi again Stefan: is the correct syntax is @Neo4jIndex or @@Neo4jIndexed ?

Thanks for this excellent post! One question:

how do I get an association to acquire data properties?

A simple example: suppose I have two grails domain classes, say Community and Person, and Community hasMany [members: Person]. Now, suppose I want to mark the membership by some qualifier, say active/inactive, so that the edge in neo4j knows it. How am I going to do that?

An obvious solution is to promote Membership to be a domain class, and being active a property there. Is there any other way?

I assume this is a limitation in GORM: a GORM relationship might not have properties attached to it. Of course you might put away with the standard GORM methods and use the native Neo4j API.

Was trying to repeat the example above with grails 1.3.7 and the latest neo4j plugin with
void testSaveWithADynamicProperty() {
def unit = new Trackable();”Project-A”;;
assert null == unit.iAmADynamicProperty

assert “XXX” == unit.iAmADynamicProperty;

But it produces a

junit.framework.AssertionFailedError: java.lang.AssertionError: instance must be persisted to use non-declared properties. Expression: delegate.node
am i missing something?

please ignore this was not running it in the scope of transaction hence the entities were not getting persisted.

Leave a Reply

Your email address will not be published. Required fields are marked *