diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/template/Graph.java b/src/main/java/org/springframework/datastore/graph/neo4j/template/Graph.java new file mode 100644 index 000000000..24ca22410 --- /dev/null +++ b/src/main/java/org/springframework/datastore/graph/neo4j/template/Graph.java @@ -0,0 +1,26 @@ +package org.springframework.datastore.graph.neo4j.template; + +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Traverser; +import org.springframework.core.convert.converter.Converter; +import org.springframework.datastore.graph.neo4j.template.graph.GraphDescription; +import org.springframework.datastore.graph.neo4j.template.graph.Property; +import org.springframework.datastore.graph.neo4j.template.traversal.Traversal; + +import java.util.List; + +public interface Graph { + Node createNode(Property... params); + + Node getReferenceNode(); + + Node getNodeById(final long id); + + Traverser traverse(final Traversal traversal); + + Traverser traverse(Node startNode, Traversal traversal); + + List traverse(Node startNode, Traversal traversal, Converter converter); + + void load(final GraphDescription graph); +} diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/template/NeoCallback.java b/src/main/java/org/springframework/datastore/graph/neo4j/template/NeoCallback.java new file mode 100644 index 000000000..2e574f937 --- /dev/null +++ b/src/main/java/org/springframework/datastore/graph/neo4j/template/NeoCallback.java @@ -0,0 +1,16 @@ +package org.springframework.datastore.graph.neo4j.template; + +public interface NeoCallback +{ + void neo(final Status status, final Graph graph) throws Exception; + + + public static interface Status + { + void mustRollback(); + + void interimCommit(); + } + + +} diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/template/NeoTemplate.java b/src/main/java/org/springframework/datastore/graph/neo4j/template/NeoTemplate.java new file mode 100644 index 000000000..b6b745a2b --- /dev/null +++ b/src/main/java/org/springframework/datastore/graph/neo4j/template/NeoTemplate.java @@ -0,0 +1,85 @@ +package org.springframework.datastore.graph.neo4j.template; + +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Transaction; +import org.springframework.datastore.graph.neo4j.template.graph.NeoGraph; + +public class NeoTemplate +{ + private final GraphDatabaseService neo; + + public NeoTemplate(final GraphDatabaseService neo) + { + if (neo == null) + throw new IllegalArgumentException("NeoService must not be null"); + this.neo = neo; + } + + public void execute(final NeoCallback callback) + { + if (callback == null) + throw new IllegalArgumentException("Callback must not be null"); + final TransactionStatus status = new TransactionStatus(neo); + try + { + + callback.neo(status, new NeoGraph(neo)); + } catch (Exception e) + { + status.mustRollback(); + throw new RuntimeException("Error executing neo callback " + callback, e); + } finally + { + status.finish(); + } + } + + private static class TransactionStatus implements NeoCallback.Status + { + private boolean rollback; + private final GraphDatabaseService neo; + private Transaction tx; + + private TransactionStatus(final GraphDatabaseService neo) + { + if (neo == null) + throw new IllegalArgumentException("NeoService must not be null"); + this.neo = neo; + begin(); + } + + private void begin() + { + tx = neo.beginTx(); + } + + public void mustRollback() + { + this.rollback = true; + } + + public void interimCommit() + { + finish(); + begin(); + } + + private void finish() + { + try + { + if (rollback) + { + tx.failure(); + } else + { + tx.success(); + } + } + finally + { + tx.finish(); + } + } + } +} diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/template/graph/GraphDescription.java b/src/main/java/org/springframework/datastore/graph/neo4j/template/graph/GraphDescription.java new file mode 100644 index 000000000..c8b4c0788 --- /dev/null +++ b/src/main/java/org/springframework/datastore/graph/neo4j/template/graph/GraphDescription.java @@ -0,0 +1,65 @@ +package org.springframework.datastore.graph.neo4j.template.graph; + +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.RelationshipType; +import org.springframework.datastore.graph.neo4j.template.Graph; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Properties; + +public class GraphDescription +{ + + private Map nodes = new LinkedHashMap(); + + public GraphDescription(final Properties props) + { + if (props == null) + throw new IllegalArgumentException("Properties must not be null"); + new PropertyParser(props).load(this); + } + + + public GraphDescription() + { + } + + public NodeInfo add(final String nodeName, final String attributeName, final Object value) + { + return getNode(nodeName).setProperty(attributeName, value); + } + + private NodeInfo getNode(final String nodeName) + { + final NodeInfo node = nodes.get(nodeName); + if (node != null) return node; + final NodeInfo newNode = new NodeInfo(nodeName); + nodes.put(nodeName, newNode); + return newNode; + } + + public void relate(final String from, final RelationshipType type, final String to) + { + getNode(from).relateTo(type, getNode(to)); + } + + void addToGraph(Graph graph) + { + boolean first = true; + for (NodeInfo nodeInfo : nodes.values()) + { + final Node node = first ? graph.getReferenceNode() : graph.createNode(); + first = false; + nodeInfo.updateProperties(node); + } + + for (NodeInfo nodeInfo : nodes.values()) + { + final Node from = graph.getNodeById(nodeInfo.getId()); + nodeInfo.updateRelations(from, graph); + } + } + +} diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/template/graph/NeoGraph.java b/src/main/java/org/springframework/datastore/graph/neo4j/template/graph/NeoGraph.java new file mode 100644 index 000000000..d85161d69 --- /dev/null +++ b/src/main/java/org/springframework/datastore/graph/neo4j/template/graph/NeoGraph.java @@ -0,0 +1,91 @@ +package org.springframework.datastore.graph.neo4j.template.graph; + +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Traverser; +import org.springframework.core.convert.converter.Converter; +import org.springframework.datastore.graph.neo4j.template.Graph; +import org.springframework.datastore.graph.neo4j.template.traversal.Traversal; + +import java.util.ArrayList; +import java.util.List; + +public class NeoGraph implements Graph +{ + private final GraphDatabaseService neo; + + public NeoGraph(final GraphDatabaseService neo) + { + if (neo == null) + throw new IllegalArgumentException("NeoService must not be null"); + this.neo = neo; + } + + @Override + public Node createNode(final Property... params) + { + final Node node = neo.createNode(); + if (params == null || params.length == 0) return node; + for (final Property property : params) + { + node.setProperty(property.getName(), property.getValue()); + } + return node; + } + + @Override + public Node getReferenceNode() + { + return neo.getReferenceNode(); + } + + @Override + public Node getNodeById(final long id) + { + return neo.getNodeById(id); + } + + @Override + public Traverser traverse(final Traversal traversal) + { + return traverse(neo.getReferenceNode(), traversal); + } + + @Override + public Traverser traverse(final Node startNode, final Traversal traversal) + { + if (startNode == null) + throw new IllegalArgumentException("StartNode must not be null"); + if (traversal == null) + throw new IllegalArgumentException("Traversal must not be null"); + return traversal.from(startNode); + } + + @Override + public List traverse(final Node startNode, final Traversal traversal, final Converter converter) + { + if (converter == null) + throw new IllegalArgumentException("Converter must not be null"); + + return map(converter, traverse(startNode, traversal)); + } + + private List map(final Converter mapper, final Iterable nodes) + { + final List result = new ArrayList(); + for (final Node node : nodes) + { + result.add(mapper.convert(node)); + } + return result; + } + + @Override + public void load(final GraphDescription description) + { + if (description == null) + throw new IllegalArgumentException("GraphDescription must not be null"); + description.addToGraph(this); + } +} + diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/template/graph/NodeInfo.java b/src/main/java/org/springframework/datastore/graph/neo4j/template/graph/NodeInfo.java new file mode 100644 index 000000000..91113e4a9 --- /dev/null +++ b/src/main/java/org/springframework/datastore/graph/neo4j/template/graph/NodeInfo.java @@ -0,0 +1,82 @@ +package org.springframework.datastore.graph.neo4j.template.graph; + +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.RelationshipType; +import org.springframework.datastore.graph.neo4j.template.Graph; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +class NodeInfo +{ + + private final String name; + private final Map props = new LinkedHashMap(); + + private final Set relations = new LinkedHashSet(); + private long id; + + public NodeInfo(final String nodeName) + { + this.name = nodeName; + } + + public NodeInfo setProperty(final String name, final Object value) + { + this.props.put(name, value); + return this; + } + + public void relateTo(final RelationshipType type, final NodeInfo node) + { + this.relations.add(new RelationShipNodeInfo(type, node)); + } + + public void setId(final long id) + { + this.id = id; + } + + public long getId() + { + return id; + } + + void updateProperties(final Node node) + { + node.setProperty("name", name); + for (Map.Entry prop : props.entrySet()) + { + node.setProperty(prop.getKey(), prop.getValue()); + } + setId(node.getId()); + } + + void updateRelations(final Node from, final Graph nodeService) + { + for (RelationShipNodeInfo relation : relations) + { + final Node to = nodeService.getNodeById(relation.getNodeInfo().getId()); + from.createRelationshipTo(to, relation.getRelationshipType()); + } + } + + public boolean equals(final Object o) + { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final NodeInfo nodeInfo = (NodeInfo) o; + + return name.equals(nodeInfo.name); + + } + + public int hashCode() + { + return name.hashCode(); + } +} diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/template/graph/Property.java b/src/main/java/org/springframework/datastore/graph/neo4j/template/graph/Property.java new file mode 100644 index 000000000..941a8b4ea --- /dev/null +++ b/src/main/java/org/springframework/datastore/graph/neo4j/template/graph/Property.java @@ -0,0 +1,32 @@ +package org.springframework.datastore.graph.neo4j.template.graph; + +public class Property +{ + private final String name; + private final Object value; + + private Property(final String name, final Object value) + { + if (name == null) + throw new IllegalArgumentException("Name must not be null"); + if (value == null) + throw new IllegalArgumentException("Value must not be null"); + this.name = name; + this.value = value; + } + + public static Property _(String name, Object value) + { + return new Property(name, value); + } + + public Object getValue() + { + return value; + } + + public String getName() + { + return name; + } +} diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/template/graph/PropertyParser.java b/src/main/java/org/springframework/datastore/graph/neo4j/template/graph/PropertyParser.java new file mode 100644 index 000000000..62e457c5e --- /dev/null +++ b/src/main/java/org/springframework/datastore/graph/neo4j/template/graph/PropertyParser.java @@ -0,0 +1,53 @@ +package org.springframework.datastore.graph.neo4j.template.graph; + +import org.neo4j.graphdb.DynamicRelationshipType; +import org.springframework.datastore.graph.neo4j.template.util.Converter; + +import static java.lang.String.format; +import java.util.Map; +import java.util.Properties; +import java.util.SortedMap; +import java.util.TreeMap; + +public class PropertyParser +{ + private final Properties props; + private final Converter converter = new Converter(); + + public PropertyParser(final Properties props) + { + this.props = props; + } + + public void load(final GraphDescription graph) + { + final SortedMap sortedSet = new TreeMap(props); + for (Map.Entry entry : sortedSet.entrySet()) + { + if (entry.getKey() == null || entry.getValue() == null) + throw new IllegalArgumentException(format("%s=%s is partially null%nproperties %s", + entry.getKey(), entry.getValue(), props)); + + final String key = entry.getKey().toString(); + final String value = entry.getValue().toString(); + final String[] prop = key.split("\\."); + if (prop.length == 2) + { + final String[] values = value.split(":"); + if (values.length != 2) + graph.add(prop[0], prop[1], value); + else + { + graph.add(prop[0], prop[1], converter.convert(values[0], values[1])); + } + } else + { + final String[] relationships = key.split("->"); + if (relationships.length == 2) + { + graph.relate(relationships[0], DynamicRelationshipType.withName(relationships[1]), value); + } + } + } + } +} diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/template/graph/RelationShipNodeInfo.java b/src/main/java/org/springframework/datastore/graph/neo4j/template/graph/RelationShipNodeInfo.java new file mode 100644 index 000000000..7b385dd31 --- /dev/null +++ b/src/main/java/org/springframework/datastore/graph/neo4j/template/graph/RelationShipNodeInfo.java @@ -0,0 +1,41 @@ +package org.springframework.datastore.graph.neo4j.template.graph; + +import org.neo4j.graphdb.RelationshipType; + +class RelationShipNodeInfo +{ + private final RelationshipType relationshipType; + private final NodeInfo nodeInfo; + + RelationShipNodeInfo(final RelationshipType relationshipType, final NodeInfo nodeInfo) + { + this.relationshipType = relationshipType; + this.nodeInfo = nodeInfo; + } + + public boolean equals(final Object o) + { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final RelationShipNodeInfo that = (RelationShipNodeInfo) o; + + return nodeInfo.equals(that.nodeInfo) && relationshipType.name().equals(that.relationshipType.name()); + + } + + public int hashCode() + { + return relationshipType.name().hashCode() * 31 + nodeInfo.hashCode(); + } + + public NodeInfo getNodeInfo() + { + return nodeInfo; + } + + public RelationshipType getRelationshipType() + { + return relationshipType; + } +} diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/template/traversal/RelationShipTypeDirection.java b/src/main/java/org/springframework/datastore/graph/neo4j/template/traversal/RelationShipTypeDirection.java new file mode 100644 index 000000000..0ea9171fc --- /dev/null +++ b/src/main/java/org/springframework/datastore/graph/neo4j/template/traversal/RelationShipTypeDirection.java @@ -0,0 +1,53 @@ +package org.springframework.datastore.graph.neo4j.template.traversal; + +import org.neo4j.graphdb.Direction; +import org.neo4j.graphdb.RelationshipType; + +import java.util.Arrays; +import java.util.List; + +class RelationShipTypeDirection +{ + private final RelationshipType relationshipType; + private final Direction direction; + + private RelationShipTypeDirection(final RelationshipType relationshipType, final Direction direction) + { + if (relationshipType == null) + throw new IllegalArgumentException("RelationShipType must not be null"); + if (direction == null) + throw new IllegalArgumentException("Direction must not be null"); + this.relationshipType = relationshipType; + this.direction = direction; + } + + public static RelationShipTypeDirection incoming(RelationshipType relationshipType) + { + return new RelationShipTypeDirection(relationshipType, Direction.INCOMING); + } + + public static RelationShipTypeDirection outgoing(RelationshipType relationshipType) + { + return new RelationShipTypeDirection(relationshipType, Direction.OUTGOING); + } + + public static RelationShipTypeDirection twoway(RelationshipType relationshipType) + { + return new RelationShipTypeDirection(relationshipType, Direction.BOTH); + } + + public RelationshipType getRelationshipType() + { + return relationshipType; + } + + public Direction getDirection() + { + return direction; + } + + public List asList() + { + return Arrays.asList(relationshipType, direction); + } +} diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/template/traversal/Traversal.java b/src/main/java/org/springframework/datastore/graph/neo4j/template/traversal/Traversal.java new file mode 100644 index 000000000..a94e4311f --- /dev/null +++ b/src/main/java/org/springframework/datastore/graph/neo4j/template/traversal/Traversal.java @@ -0,0 +1,146 @@ +package org.springframework.datastore.graph.neo4j.template.traversal; + +import org.neo4j.graphdb.*; +import org.springframework.datastore.graph.neo4j.template.util.NodeEvaluator; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; + +public class Traversal +{ + private final Collection relationShipTypeDirections = new HashSet(); + + private Traverser.Order order = Traverser.Order.BREADTH_FIRST; + private StopEvaluator stopEvaluator = StopEvaluator.END_OF_GRAPH; + private ReturnableEvaluator returnableEvaluator = ReturnableEvaluator.ALL_BUT_START_NODE; + + private Traversal() + { + } + + Traverser.Order getOrder() + { + return order; + } + + StopEvaluator getStopEvaluator() + { + return stopEvaluator; + } + + ReturnableEvaluator getReturnableEvaluator() + { + return returnableEvaluator; + } + + public static Traversal walk() + { + return new Traversal(); + } + + public Traversal order(Traverser.Order order) + { + if (order == null) + throw new IllegalArgumentException("Order must not be null"); + this.order = order; + return this; + } + + public Traversal accept(ReturnableEvaluator evaluator) + { + if (evaluator == null) + throw new IllegalArgumentException("Evaluator must not be null"); + this.returnableEvaluator = evaluator; + return this; + } + + public Traversal accept(final NodeEvaluator evaluator) + { + if (evaluator == null) + throw new IllegalArgumentException("Evaluator must not be null"); + this.returnableEvaluator = new ReturnableEvaluator() + { + public boolean isReturnableNode(final TraversalPosition traversalPosition) + { + return evaluator.accept(traversalPosition.currentNode()); + } + }; + return this; + } + + public Traversal stopOn(StopEvaluator stop) + { + if (stop == null) + throw new IllegalArgumentException("StopEvaluator must not be null"); + this.stopEvaluator = stop; + return this; + } + + public Traversal breadthFirst() + { + return order(Traverser.Order.BREADTH_FIRST); + } + + public Traversal depthFirst() + { + return order(Traverser.Order.DEPTH_FIRST); + } + + public Traversal first() + { + return stopOn(StopEvaluator.DEPTH_ONE); + } + + public Traversal all() + { + return stopOn(StopEvaluator.END_OF_GRAPH); + } + + public Traversal incoming(final RelationshipType relationShipType) + { + if (relationShipType == null) + throw new IllegalArgumentException("RelationShipType must not be null"); + relationShipTypeDirections.add(RelationShipTypeDirection.incoming(relationShipType)); + return this; + } + + public Traversal outgoing(final RelationshipType relationShipType) + { + if (relationShipType == null) + throw new IllegalArgumentException("RelationShipType must not be null"); + relationShipTypeDirections.add(RelationShipTypeDirection.outgoing(relationShipType)); + return this; + } + + public Traversal twoway(final RelationshipType relationShipType) + { + if (relationShipType == null) + throw new IllegalArgumentException("RelationShipType must not be null"); + relationShipTypeDirections.add(RelationShipTypeDirection.twoway(relationShipType)); + return this; + } + + public Traversal both(final RelationshipType relationShipType) + { + return twoway(relationShipType); + } + + public Traverser from(final Node node) + { + if (node == null) + throw new IllegalArgumentException("Node must not be null"); + return node.traverse(getOrder(), getStopEvaluator(), getReturnableEvaluator(), + getRelationshipTypeDirectionPairs()); + } + + private Object[] getRelationshipTypeDirectionPairs() + { + Collection result = new ArrayList(relationShipTypeDirections.size()); + for (RelationShipTypeDirection relationShipTypeDirection : relationShipTypeDirections) + { + result.addAll(relationShipTypeDirection.asList()); + } + return result.toArray(); + } +} diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/template/util/Converter.java b/src/main/java/org/springframework/datastore/graph/neo4j/template/util/Converter.java new file mode 100644 index 000000000..755981196 --- /dev/null +++ b/src/main/java/org/springframework/datastore/graph/neo4j/template/util/Converter.java @@ -0,0 +1,31 @@ +package org.springframework.datastore.graph.neo4j.template.util; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +public class Converter +{ + private final Map valueOfs = new HashMap(); + + public Object convert(final String typeName, final String value) + { + if (value == null) return null; + if (typeName == null) + throw new IllegalArgumentException("TypeName must not be null"); + try + { + Method valueOf = valueOfs.get(typeName); + if (valueOf == null) + { + final Class type = Class.forName(typeName.contains(".") ? typeName : "java.lang." + typeName); + valueOf = type.getMethod("valueOf", String.class); + valueOfs.put(typeName, valueOf); + } + return valueOf.invoke(null, value); + } catch (Exception e) + { + throw new RuntimeException(String.format("Error converting value %s from String to type %s", value, typeName), e); + } + } +} diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/template/util/NodeEvaluator.java b/src/main/java/org/springframework/datastore/graph/neo4j/template/util/NodeEvaluator.java new file mode 100644 index 000000000..1aea3f6a9 --- /dev/null +++ b/src/main/java/org/springframework/datastore/graph/neo4j/template/util/NodeEvaluator.java @@ -0,0 +1,7 @@ +package org.springframework.datastore.graph.neo4j.template.util; + +import org.neo4j.graphdb.Node; + +public interface NodeEvaluator { + boolean accept(final Node node); +} diff --git a/src/test/java/org/springframework/datastore/graph/neo4j/template/NeoApiTest.java b/src/test/java/org/springframework/datastore/graph/neo4j/template/NeoApiTest.java new file mode 100644 index 000000000..c0a5ef6c9 --- /dev/null +++ b/src/test/java/org/springframework/datastore/graph/neo4j/template/NeoApiTest.java @@ -0,0 +1,39 @@ +package org.springframework.datastore.graph.neo4j.template; + +import org.junit.After; +import org.junit.Before; +import org.neo4j.graphdb.*; +import org.neo4j.kernel.EmbeddedGraphDatabase; +import org.springframework.datastore.graph.neo4j.template.traversal.Traversal; + +public abstract class NeoApiTest { + protected GraphDatabaseService neo; + + @Before + public void setUp() { + neo = new EmbeddedGraphDatabase("target/var/neo"); + } + + @After + public void tearDown() { + if (neo != null) { + clear(); + neo.shutdown(); + } + } + + private void clear() { + new NeoTemplate(neo).execute(new NeoCallback() { + public void neo(final Status status, final Graph graph) throws Exception { + final DynamicRelationshipType relationshipType = DynamicRelationshipType.withName("HAS"); + final Traverser allNodes = graph.traverse(Traversal.walk().both(relationshipType)); + for (Node node : allNodes) { + for (Relationship relationship : node.getRelationships()) { + relationship.delete(); + } + node.delete(); + } + } + }); + } +} diff --git a/src/test/java/org/springframework/datastore/graph/neo4j/template/NeoGraphDescriptionTest.java b/src/test/java/org/springframework/datastore/graph/neo4j/template/NeoGraphDescriptionTest.java new file mode 100644 index 000000000..60dc6c561 --- /dev/null +++ b/src/test/java/org/springframework/datastore/graph/neo4j/template/NeoGraphDescriptionTest.java @@ -0,0 +1,69 @@ +package org.springframework.datastore.graph.neo4j.template; + +import org.junit.Test; +import org.neo4j.graphdb.Direction; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.RelationshipType; +import org.springframework.datastore.graph.neo4j.template.graph.GraphDescription; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.Properties; + +import static org.junit.Assert.assertEquals; +import static org.springframework.datastore.graph.neo4j.template.NeoGraphDescriptionTest.Type.HAS; + + +public class NeoGraphDescriptionTest extends NeoApiTest { + + enum Type implements RelationshipType { + HAS + } + + @Test + public void testLoadGraph() { + final NeoTemplate template = new NeoTemplate(neo); + template.execute(new NeoCallback() { + public void neo(final Status status, final Graph graph) throws Exception { + final GraphDescription heaven = new GraphDescription(); + heaven.add("adam", "age", 1); + heaven.add("eve", "age", 0); + heaven.relate("adam", HAS, "eve"); + graph.load(heaven); + + checkHeaven(graph); + } + }); + } + + private void checkHeaven(final Graph graph) { + final Node adam = graph.getReferenceNode(); + assertEquals("adam", adam.getProperty("name")); + assertEquals(1, adam.getProperty("age")); + final Node eve = adam.getSingleRelationship(HAS, Direction.OUTGOING).getEndNode(); + assertEquals("eve", eve.getProperty("name")); + assertEquals(0, eve.getProperty("age")); + } + + @Test + public void testLoadGraphProps() { + final NeoTemplate template = new NeoTemplate(neo); + template.execute(new NeoCallback() { + public void neo(final Status status, final Graph graph) throws Exception { + final GraphDescription heaven = new GraphDescription(createGraphProperties()); + graph.load(heaven); + checkHeaven(graph); + } + }); + } + + private Properties createGraphProperties() throws IOException { + Properties props = new Properties(); + props.load(new ByteArrayInputStream(( + "adam.age=Integer:1\n" + + "eve.age=Integer:0\n" + + "adam->HAS=eve" + ).getBytes("UTF-8"))); + return props; + } +} \ No newline at end of file diff --git a/src/test/java/org/springframework/datastore/graph/neo4j/template/NeoTemplateTest.java b/src/test/java/org/springframework/datastore/graph/neo4j/template/NeoTemplateTest.java new file mode 100644 index 000000000..059421dba --- /dev/null +++ b/src/test/java/org/springframework/datastore/graph/neo4j/template/NeoTemplateTest.java @@ -0,0 +1,75 @@ +package org.springframework.datastore.graph.neo4j.template; + +import org.junit.Test; +import org.neo4j.graphdb.Direction; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Relationship; +import org.neo4j.graphdb.RelationshipType; +import org.springframework.datastore.graph.neo4j.template.graph.Property; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.springframework.datastore.graph.neo4j.template.NeoTemplateTest.Type.HAS; + +public class NeoTemplateTest extends NeoApiTest { + + enum Type implements RelationshipType { + HAS + } + + @Test + public void testRefNode() { + new NeoTemplate(neo).execute(new NeoCallback() { + public void neo(final Status status, final Graph graph) throws Exception { + Node refNode = graph.getReferenceNode(); + Node refNodeById = graph.getNodeById(refNode.getId()); + assertEquals("same ref node", refNode, refNodeById); + } + }); + } + + @Test + public void testSingleNode() { + final NeoTemplate template = new NeoTemplate(neo); + template.execute(new NeoCallback() { + public void neo(final Status status, final Graph graph) throws Exception { + Node refNode = graph.getReferenceNode(); + Node node = graph.createNode(Property._("name", "Test"), Property._("size", 100)); + refNode.createRelationshipTo(node, HAS); + + final Relationship toTestNode = refNode.getSingleRelationship(HAS, Direction.OUTGOING); + final Node nodeByRelationship = toTestNode.getEndNode(); + assertEquals("Test", nodeByRelationship.getProperty("name")); + assertEquals(100, nodeByRelationship.getProperty("size")); + } + }); + template.execute(new NeoCallback() { + public void neo(final Status status, final Graph graph) throws Exception { + Node refNode = graph.getReferenceNode(); + final Relationship toTestNode = refNode.getSingleRelationship(HAS, Direction.OUTGOING); + final Node nodeByRelationship = toTestNode.getEndNode(); + assertEquals("Test", nodeByRelationship.getProperty("name")); + assertEquals(100, nodeByRelationship.getProperty("size")); + } + }); + } + + @Test + public void testRollback() { + final NeoTemplate template = new NeoTemplate(neo); + template.execute(new NeoCallback() { + public void neo(final Status status, final Graph graph) throws Exception { + Node node = graph.getReferenceNode(); + node.setProperty("test", "test"); + assertEquals("test", node.getProperty("test")); + status.mustRollback(); + } + }); + template.execute(new NeoCallback() { + public void neo(final Status status, final Graph graph) throws Exception { + Node node = graph.getReferenceNode(); + assertFalse(node.hasProperty("test")); + } + }); + } +} diff --git a/src/test/java/org/springframework/datastore/graph/neo4j/template/NeoTraversalTest.java b/src/test/java/org/springframework/datastore/graph/neo4j/template/NeoTraversalTest.java new file mode 100644 index 000000000..0ac8f745c --- /dev/null +++ b/src/test/java/org/springframework/datastore/graph/neo4j/template/NeoTraversalTest.java @@ -0,0 +1,99 @@ +package org.springframework.datastore.graph.neo4j.template; + +import org.junit.Ignore; +import org.junit.Test; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.RelationshipType; +import org.neo4j.graphdb.StopEvaluator; +import org.springframework.core.convert.converter.Converter; +import org.springframework.datastore.graph.neo4j.template.graph.GraphDescription; +import org.springframework.datastore.graph.neo4j.template.traversal.Traversal; + +import java.util.ArrayList; +import java.util.Collections; + +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.springframework.datastore.graph.neo4j.template.NeoTraversalTest.Type.HAS; +import static org.springframework.datastore.graph.neo4j.template.traversal.Traversal.walk; + +public class NeoTraversalTest extends NeoApiTest { + + enum Type implements RelationshipType { + MARRIED, CHILD, GRANDSON, GRANDDAUGHTER, WIFE, HUSBAND, HAS + } + + @Test + public void testSimpleTraverse() { + runAndCheckTraverse(walk().both(HAS), "wife", "man","son","daughter", "grandma","grandpa" ); + } + + + @Ignore + @Test + public void testComplexTraversal() { + final Traversal traversal = walk().breadthFirst().depthFirst() + .stopOn(StopEvaluator.DEPTH_ONE).first().all() + .incoming(HAS).outgoing(HAS).twoway(HAS); + runAndCheckTraverse(traversal, "grandpa", "grandma", "daughter", "son", "man", "wife"); + } + + private void runAndCheckTraverse(final Traversal traversal, final String... names) { + final NeoTemplate template = new NeoTemplate(neo); + template.execute(new NeoCallback() { + public void neo(final Status status, final Graph graph) throws Exception { + createFamily(graph); + assertEquals("all members",asList(names) , + graph.traverse(graph.getReferenceNode(), traversal, + new Converter() { + public String convert(Node node) { + return (String) node.getProperty("name", ""); + } + })); + } + }); + } + + private void createFamily(final Graph graph) { + final GraphDescription family = new GraphDescription(); + family.add("family", "type", "small"); + family.relate("family", HAS, "wife"); + family.relate("family", HAS, "man"); + + + family.add("man", "age", 35); + family.add("wife", "age", 30); + family.relate("man", Type.MARRIED, "wife"); + family.relate("wife", Type.MARRIED, "man"); + family.relate("man", Type.WIFE, "wife"); + family.relate("wife", Type.HUSBAND, "man"); + + family.add("daughter", "age", 10); + family.add("son", "age", 8); + + family.relate("family", HAS, "son"); + family.relate("family", HAS, "daughter"); + + family.relate("man", Type.CHILD, "son"); + family.relate("wife", Type.CHILD, "son"); + family.relate("man", Type.CHILD, "daughter"); + family.relate("wife", Type.CHILD, "daughter"); + + family.add("grandma", "age", 60); + family.add("grandpa", "age", 75); + + family.relate("family", HAS, "grandma"); + family.relate("family", HAS, "grandpa"); + + family.relate("grandpa", Type.CHILD, "man"); + family.relate("grandma", Type.CHILD, "man"); + + family.relate("grandpa", Type.GRANDSON, "son"); + family.relate("grandma", Type.GRANDSON, "son"); + family.relate("grandpa", Type.GRANDDAUGHTER, "daughter"); + family.relate("grandma", Type.GRANDDAUGHTER, "daughter"); + + // todo even more relationships + graph.load(family); + } +} \ No newline at end of file