Skip to content

Commit

Permalink
Extract relationships from JSON + apply into Neo
Browse files Browse the repository at this point in the history
  • Loading branch information
Martin Kleppmann committed May 22, 2009
1 parent b0952f8 commit 6f7be4e
Showing 1 changed file with 100 additions and 10 deletions.
110 changes: 100 additions & 10 deletions src/main/scala/com/example/restapi/Resources.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.example.restapi

import scala.collection.mutable.HashSet
import java.util.logging.Logger

import com.sun.jersey.api.json.{JSONConfiguration, JSONJAXBContext}
import javax.ws.rs.ext.{ContextResolver, Provider}
import javax.xml.bind.JAXBContext
Expand All @@ -20,6 +23,9 @@ class JAXBContextResolver extends ContextResolver[JAXBContext] {
* Provides helpers for converting Neo4j nodes to/from JSON.
*/
object NeoJsonConverter {

val log = Logger.getLogger(this.getClass.getName)

/**
* Serialises a Neo4j node into a JSON object of the following form:
* <pre>{
Expand Down Expand Up @@ -79,22 +85,106 @@ object NeoJsonConverter {
* In either case, the up-to-date node is returned.
*/
def jsonToNeo(json: JSONObject, neo: NeoService, existingNode: Node): Node = {
val specialProps = Array("_id", "_url", "_in", "_out")
val node = if (existingNode == null) neo.createNode else existingNode
// Keep track of any of the node's properties + relationships not present in the JSON
val deleteProp = new scala.collection.mutable.HashSet[String]
if (existingNode != null) for (key <- node.getPropertyKeys) deleteProp += key
// Update node with properties from JSON
for (key <- json.keys.asInstanceOf[java.util.Iterator[String]]) {
if (!(Array("_id", "_in", "_out") contains key)) {
node.setProperty(key, json.get(key))
deleteProp -= key
jsonPropertiesToNeo(json, node, specialProps)

// Keep track of any of the node's relationships not present in the JSON
val deleteRel = new HashSet[Long]
for (rel <- node.getRelationships) deleteRel += rel.getId

// Process special properties
for (key <- specialProps) try {
key match {
case "_id" =>
if (json.getLong(key) != node.getId)
log.warning("Mismatch of IDs: active node is " + node.getId + ", JSON specified " + json.getLong(key))

case "_in" =>
val obj = json.getJSONObject(key)
for (key <- obj.keys.asInstanceOf[java.util.Iterator[String]])
jsonRelationshipToNeo(obj.get(key), neo, node, key, Direction.INCOMING, deleteRel)

case "_out" =>
val obj = json.getJSONObject(key)
for (key <- obj.keys.asInstanceOf[java.util.Iterator[String]])
jsonRelationshipToNeo(obj.get(key), neo, node, key, Direction.OUTGOING, deleteRel)

case _ =>
}
} catch {
case e: JSONException => log.warning("Error decoding " + key + " JSON property: " + e.getMessage)
}
// Delete any unused properties
for (key <- deleteProp) node.removeProperty(key)

// Delete any unused properties and relationships
for (id <- deleteRel) neo.getRelationshipById(id).delete
node
}

/**
* Makes the properties of a Neo node or relationship match those in a given JSON object,
* ignoring JSON object properties with particular names.
*/
private def jsonPropertiesToNeo(json: JSONObject, container: PropertyContainer, ignoreProps: Seq[String]) {
val deleteProp = new HashSet[String]
for (key <- container.getPropertyKeys) deleteProp += key
// Iterate over properties in JSON object
for (key <- json.keys.asInstanceOf[java.util.Iterator[String]]) try {
if (!(ignoreProps contains key)) {
container.setProperty(key, json.get(key))
deleteProp -= key
}
} catch {
case e: JSONException => log.warning("Error decoding " + key + " JSON property: " + e.getMessage)
}
// Delete any properties not occurring in JSON object
for (key <- deleteProp) container.removeProperty(key)
}

/** Tries to convert a single JSON object, representing a relationship, into Neo. */
private def jsonRelationshipToNeo(obj: Object, neo: NeoService, node: Node, name: String,
direction: Direction, unprocessedRels: HashSet[Long]) {
val relType = DynamicRelationshipType.withName(name)

def getOrCreate(otherNodeId: Long): Relationship = {
// Try to find an existing relationship of the same type and direction between the two nodes
val rel = node.getRelationships(relType, direction).find {
rel => (rel.getOtherNode(node).getId == otherNodeId) && (unprocessedRels contains rel.getId)
} getOrElse {
// If no existing relationship was found, create a new one
val other = neo.getNodeById(otherNodeId)
if (direction == Direction.OUTGOING) {
node.createRelationshipTo(other, relType)
} else {
other.createRelationshipTo(node, relType)
}
}
unprocessedRels -= rel.getId // mark this relationship as used
rel
}

// A relationship could be encoded in various different forms:
try {
obj match {
case arr: JSONArray => // Array: process each element in turn
for (n <- 0 until arr.length) jsonRelationshipToNeo(arr.get(n), neo, node, name, direction, unprocessedRels)

case obj: JSONObject => // Object: must have _start or _end property, and maybe other properties
val rel = getOrCreate(obj.getLong(if (direction == Direction.INCOMING) "_start" else "_end"))
jsonPropertiesToNeo(obj, rel, Array("_id", "_url", "_start", "_end", "_type"))

case num: java.lang.Number => // Number: just the ID of the other node
getOrCreate(num.longValue)
}
} catch {
case e: JSONException => log.warning("Error decoding " + direction + " " + name + " relationship from JSON: " +
e.getMessage)
case e: NotFoundException => log.warning("Reference to non-existent node in " + direction + " " + name +
" relationship in JSON: " + e.getMessage)
}
}


/** Implicitly convert a Java iterable to a Scala iterator. */
implicit def java2scala[T](iter: java.lang.Iterable[T]): scala.Iterator[T] =
new scala.collection.jcl.MutableIterator.Wrapper(iter.iterator)
Expand Down

0 comments on commit 6f7be4e

Please sign in to comment.