Permalink
Browse files

closes #66: support for node/rel.<property> style getting/setting pro…

…perties and JSON support
  • Loading branch information...
1 parent fb85b9c commit db84db71dcc53104f9b966c76fa01e743aa4a64d @sarmbruster committed Sep 23, 2012
@@ -0,0 +1,31 @@
+Inspired by a [Github issue ticket|https://github.com/SpringSource/grails-data-mapping/issues/66] the Neo4j Grails contains some
+enhancements to the Neo4j core API. These enhancements are implemented using Groovy's ExpandoMetaClass.
+
+h4. setting properties on nodes/relationships
+
+Assigning an arbitrary property onto a Neo4j node or relationship can be simply done by using Groovy's property
+mechanism:
+{code}
+def node = graphDatabaseService.createNode()
+node.myProperty = myValue
+{code}
+
+The same words for getting properties:
+
+{code}
+def node = ...
+def value = node.myProperty
+{code}
+
+{note}
+There is a important convention: when the property ends with "Date" then a date type is assumed. The node property then
+helds respective millis.
+{note}
+
+h4. JSON marshalling
+Neo4j nodes and relationships can be easily marshalled to JSON, e.g. in a controller using:
+
+{code}
+def node = graphDatabaseService.getNodeById(myid)
+render node as JSON
+{code}
@@ -8,4 +8,5 @@ gettingStarted:
combiningNeo4jAndHibernate: Combining Neo4j And Hibernate
advancedConfiguration: Advanced Configuration
mapping: Mapping domain classes to Neo4j node space
+neo4jEnhancements: Enhancements to Neo4j core API
sampleApp: Sample Application
@@ -1,6 +1,15 @@
-import org.grails.datastore.gorm.neo4j.plugin.support.*
import org.codehaus.groovy.grails.validation.ConstrainedProperty
import org.grails.datastore.gorm.neo4j.constraints.UniqueConstraint
+import org.grails.datastore.gorm.neo4j.plugin.support.Neo4jMethodsConfigurer
+import org.grails.datastore.gorm.neo4j.plugin.support.Neo4jOnChangeHandler
+import org.grails.datastore.gorm.neo4j.plugin.support.Neo4jSpringConfigurer
+import org.neo4j.kernel.impl.core.NodeProxy
+import org.neo4j.kernel.impl.core.RelationshipProxy
+import grails.converters.JSON
+import org.codehaus.groovy.grails.commons.metaclass.MetaClassEnhancer
+import org.codehaus.groovy.grails.plugins.converters.api.ConvertersApi
+import org.codehaus.groovy.grails.commons.GrailsMetaClassUtils
+import org.springframework.context.ApplicationContext
class Neo4jGrailsPlugin {
@@ -16,7 +25,7 @@ class Neo4jGrailsPlugin {
def grailsVersion = "1.2 > *"
//def observe = ['services']
//def loadAfter = ['domainClass', 'hibernate', 'services', 'cloudFoundry']
- def loadAfter = ['domainClass', 'hibernate', 'services', 'cloudFoundry']
+ def loadAfter = ['domainClass', 'hibernate', 'services', 'cloudFoundry', 'converters']
def observe = ['services', 'domainClass']
def author = "Stefan Armbruster"
@@ -38,12 +47,79 @@ class Neo4jGrailsPlugin {
def doWithDynamicMethods = { ctx ->
def datastore = ctx.neo4jDatastore
def transactionManager = ctx.neo4jTransactionManager
- def methodsConfigurer = new Neo4jMethodsConfigurer(datastore, transactionManager)
+ def methodsConfigurer = new Neo4jMethodsConfigurer(datastore, transactionManager)
methodsConfigurer.hasExistingDatastore = manager.hasGrailsPlugin("hibernate")
def foe = application?.config?.grails?.gorm?.failOnError
methodsConfigurer.failOnError = foe instanceof Boolean ? foe : false
-
+
methodsConfigurer.configure()
+
+ setupGetOrSet()
+
+// NodeProxy.metaClass.propertyMissing = getOrSet
+// RelationshipProxy.metaClass.propertyMissing = getOrSet
+ //RestNode.metaClass.propertyMissing = getOrSet
+ //Relationship.metaClass.propertyMissing = getOrSet
+
+ setupJsonMarshallers(ctx)
+ }
+
+ private void setupJsonMarshallers(ApplicationContext ctx) {
+ MetaClassEnhancer enhancer = new MetaClassEnhancer()
+ enhancer.addApi(new ConvertersApi(applicationContext: ctx))
+
+ // Override GDK asType for some common Interfaces and Classes
+ enhancer.enhanceAll([NodeProxy, RelationshipProxy].collect {
+ GrailsMetaClassUtils.getExpandoMetaClass(it)
+ })
+
+
+ JSON.registerObjectMarshaller(NodeProxy, 1000) { n ->
+ def m = [:]
+ m.id = n.id
+ n.propertyKeys.each { k ->
+ m[(k)] = n."$k"
+ }
+ m.relationships = n.relationships.collect {it}
+ m
+ }
+
+ JSON.registerObjectMarshaller(RelationshipProxy.class) { r ->
+ def m = [:]
+ m.id = r.id
+ m.type = r.type.name()
+ m.startNode = r.startNode.id
+ m.endNode = r.endNode.id
+ r.propertyKeys.each { k ->
+ m[(k)] = r."$k"
+ }
+ m
+ }
+ }
+
+ private void setupGetOrSet() {
+ def getOrSet = { name, value = null ->
+ if (value) {
+ delegate.setProperty(name, value instanceof Date ? value.time : value)
+ } else {
+ def val = delegate.getProperty(name, null)
+ (val instanceof Long && name.endsWith('Date')) ? new Date(val) : val
+ }
+ }
+
+ def classLoader = Thread.currentThread().contextClassLoader
+ def classes = [NodeProxy, RelationshipProxy]
+ ['org.neo4j.rest.graphdb.entity.RestNode', 'org.neo4j.rest.graphdb.entity.RestRelationship'].each {
+ try {
+ classes << classLoader.loadClass(it)
+ } catch (ClassNotFoundException e) {
+ //pass
+ }
+ }
+
+ classes.each {
+ it.metaClass.propertyMissing = getOrSet
+ }
}
def doWithApplicationContext = { applicationContext ->
@@ -0,0 +1,75 @@
+package org.grails.datastore.gorm.neo4j
+
+import grails.plugin.spock.IntegrationSpec
+import org.neo4j.graphdb.GraphDatabaseService
+import grails.converters.JSON
+import grails.web.JSONBuilder
+import org.codehaus.groovy.grails.web.json.JSONElement
+import neo4j.DummyDomain
+import spock.lang.Ignore
+import java.text.SimpleDateFormat
+import spock.lang.IgnoreRest
+import org.apache.commons.lang.CharRange
+import org.neo4j.graphdb.DynamicRelationshipType
+
+class GetOrSetSpec extends IntegrationSpec {
+
+ GraphDatabaseService graphDatabaseService
+
+ def "test getOrSet method on nodes"() {
+ setup:
+ def node = graphDatabaseService.createNode()
+
+ when: 'setting a property'
+ node."$propertyName" = propertyValue
+
+ then: 'retrieving the property'
+ node."$propertyName" == propertyValue
+
+ where:
+ propertyName | propertyValue
+ 'name' | 'abc'
+ 'createdDate' | new Date()
+ 'count' | 5
+ 'price' | 2.12f
+ }
+
+ def "marshalling test for nodes"() {
+ when:
+ def n = graphDatabaseService.createNode()
+ n.setProperty('myproperty', 'myvalue')
+ def json = marshalAsJSON(n)
+
+
+ then:
+ json.id == n.id
+ json.myproperty == 'myvalue'
+ json.relationships == []
+
+ }
+
+ def "marshalling test for relationship"() {
+ when:
+ def startNode = graphDatabaseService.createNode()
+ startNode.setProperty('myproperty', 'startnode')
+ def endNode = graphDatabaseService.createNode()
+ endNode.setProperty('myproperty', 'endnode')
+ def rel = startNode.createRelationshipTo(endNode, DynamicRelationshipType.withName('RELTYPE'))
+ rel.setProperty('myproperty', 'rel')
+ def json = marshalAsJSON(rel)
+
+ then:
+ json.id == rel.id
+ json.myproperty == 'rel'
+ json.startNode == startNode.id
+ json.endNode == endNode.id
+ json.type == 'RELTYPE'
+
+ }
+
+ private JSONElement marshalAsJSON(object) {
+ def sw = new StringWriter()
+ (object as JSON).render(sw)
+ JSON.parse(sw.toString())
+ }
+}

0 comments on commit db84db7

Please sign in to comment.