diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/support/IndexingNodeTypeStrategy.java b/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/support/IndexingNodeTypeStrategy.java new file mode 100644 index 000000000..671d30bce --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/support/IndexingNodeTypeStrategy.java @@ -0,0 +1,95 @@ +package org.springframework.data.graph.neo4j.support; + +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.NotFoundException; +import org.neo4j.graphdb.index.Index; +import org.neo4j.graphdb.index.IndexHits; +import org.neo4j.helpers.Predicate; +import org.neo4j.helpers.collection.FilteringIterable; +import org.neo4j.helpers.collection.IterableWrapper; +import org.springframework.data.graph.annotation.NodeEntity; +import org.springframework.data.graph.core.NodeBacked; +import org.springframework.data.graph.core.NodeTypeStrategy; +import org.springframework.persistence.support.EntityInstantiator; + +public class IndexingNodeTypeStrategy implements NodeTypeStrategy { + + private EntityInstantiator graphEntityInstantiator; + private GraphDatabaseService graphDb; + + public IndexingNodeTypeStrategy(GraphDatabaseService graphDb, EntityInstantiator graphEntityInstantiator) { + this.graphDb = graphDb; + this.graphEntityInstantiator = graphEntityInstantiator; + } + + private Index getTypesIndex() { + return graphDb.index().forNodes("__types__"); + } + + @Override + public void postEntityCreation(NodeBacked entity) { + Node node = entity.getPersistentState(); + Class entityClass = entity.getClass(); + addToTypesIndex(node, entityClass); + node.setProperty("__type__", entityClass.getName()); + } + + private void addToTypesIndex(Node node, Class entityClass) { + Class klass = entityClass; + while (klass.getAnnotation(NodeEntity.class) != null) { + getTypesIndex().add(node, "className", klass.getName()); + klass = klass.getSuperclass(); + } + } + + @Override + public Iterable findAll(Class clazz) { + final IndexHits allEntitiesOfType = getTypesIndex().get("className", clazz.getName()); + return new FilteringIterable(new IterableWrapper(allEntitiesOfType) { + @Override + @SuppressWarnings("unchecked") + protected ENTITY underlyingObjectToObject(Node node) { + Class javaType = (Class) getJavaType(node); + if (javaType == null) return null; + return graphEntityInstantiator.createEntityFromState(node, javaType); + } + }, new Predicate() { + @Override + public boolean accept(ENTITY item) { + return item != null; + } + }); + } + + @Override + public long count(Class entityClass) { + return getTypesIndex().get("className", entityClass.getName()).size(); + } + + @Override + @SuppressWarnings("unchecked") + public Class getJavaType(Node node) { + if (node == null) throw new IllegalArgumentException("Node is null"); + try { + return (Class) Class.forName((String) node.getProperty("__type__")); + } catch (NotFoundException e) { + return null; + } catch (ClassNotFoundException e) { + return null; + } + } + + @Override + public void preEntityRemoval(NodeBacked entity) { + getTypesIndex().remove(entity.getPersistentState(), "className", entity.getClass().getName()); + } + + @Override + public Class confirmType(Node node, Class type) { + Class javaType = getJavaType(node); + if (javaType == null) throw new IllegalStateException("No type stored on node."); + if (type.isAssignableFrom(javaType)) return javaType; + throw new IllegalArgumentException(String.format("%s does not correspond to the node type %s of node %s", type, javaType, node)); + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/support/NoopNodeTypeStrategy.java b/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/support/NoopNodeTypeStrategy.java new file mode 100644 index 000000000..76c0822a7 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/support/NoopNodeTypeStrategy.java @@ -0,0 +1,35 @@ +package org.springframework.data.graph.neo4j.support; + +import org.neo4j.graphdb.Node; +import org.springframework.data.graph.core.NodeBacked; +import org.springframework.data.graph.core.NodeTypeStrategy; + +public class NoopNodeTypeStrategy implements NodeTypeStrategy { + @Override + public void postEntityCreation(NodeBacked entity) { + } + + @Override + public Iterable findAll(Class clazz) { + throw new UnsupportedOperationException("findAll not supported by NoopNodeTypeStrategy."); + } + + @Override + public long count(Class entityClass) { + throw new UnsupportedOperationException("count not supported by NoopNodeTypeStrategy."); + } + + @Override + public Class getJavaType(Node node) { + throw new UnsupportedOperationException("getJavaType not supported NoopNodeTypeStrategy."); + } + + @Override + public void preEntityRemoval(NodeBacked entity) { + } + + @Override + public Class confirmType(Node node, Class type) { + return type; + } +} diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/graph/neo4j/support/IndexingNodeTypeStrategyTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/graph/neo4j/support/IndexingNodeTypeStrategyTest.java new file mode 100644 index 000000000..0353215e3 --- /dev/null +++ b/spring-data-neo4j/src/test/java/org/springframework/data/graph/neo4j/support/IndexingNodeTypeStrategyTest.java @@ -0,0 +1,159 @@ +package org.springframework.data.graph.neo4j.support; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Transaction; +import org.neo4j.graphdb.index.Index; +import org.neo4j.graphdb.index.IndexHits; +import org.neo4j.helpers.collection.IteratorUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.graph.annotation.NodeEntity; +import org.springframework.data.graph.neo4j.support.node.Neo4jHelper; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.transaction.BeforeTransaction; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import static org.junit.Assert.assertEquals; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = {"classpath:org/springframework/data/graph/neo4j/support/Neo4jGraphPersistenceTest-context.xml", + "classpath:org/springframework/data/graph/neo4j/support/IndexingNodeTypeStrategyOverride-context.xml"}) +@Ignore +public class IndexingNodeTypeStrategyTest { + + @Autowired + private GraphDatabaseService graphDatabaseService; + @Autowired + private IndexingNodeTypeStrategy nodeTypeStrategy; + + private Thing thing; + private SubThing subThing; + + @BeforeTransaction + public void cleanDb() { + Neo4jHelper.cleanDb(graphDatabaseService); + } + + @Before + public void setUp() throws Exception { + if (thing == null) { + createThings(); + } + } + + @Test + @Transactional + public void testPostEntityCreation() throws Exception { + Index typesIndex = graphDatabaseService.index().forNodes("__types__"); + IndexHits thingHits = typesIndex.get("className", thing.getClass().getName()); + assertEquals(set(node(thing), node(subThing)), IteratorUtil.addToCollection((Iterable)thingHits, new HashSet())); + assertEquals(thing.getClass().getName(), node(thing).getProperty("__type__")); + assertEquals(subThing.getClass().getName(), node(subThing).getProperty("__type__")); + } + + @Test + public void testFindAll() throws Exception { + assertEquals("Did not find all things.", + Arrays.asList(thing, subThing), IteratorUtil.addToCollection(nodeTypeStrategy.findAll(Thing.class), new ArrayList())); + } + + @Test + public void testCount() throws Exception { + assertEquals(2, nodeTypeStrategy.count(Thing.class)); + } + + @Test + public void testGetJavaType() throws Exception { + assertEquals(Thing.class, nodeTypeStrategy.getJavaType(node(thing))); + assertEquals(SubThing.class, nodeTypeStrategy.getJavaType(node(subThing))); + } + + @Test + public void testPreEntityRemoval() throws Exception { + manualCleanDb(); + Transaction tx; + tx = graphDatabaseService.beginTx(); + try { + nodeTypeStrategy.preEntityRemoval(thing); + nodeTypeStrategy.preEntityRemoval(subThing); + tx.success(); + } finally { + tx.finish(); + } + Index typesIndex = graphDatabaseService.index().forNodes("__types__"); + IndexHits thingHits = typesIndex.get("className", thing.getClass().getName()); + assertEquals(0, thingHits.size()); + } + + @Test + public void testConfirmType() throws Exception { + assertEquals(Thing.class, nodeTypeStrategy.confirmType(node(thing), Thing.class)); + assertEquals(SubThing.class, nodeTypeStrategy.confirmType(node(subThing), Thing.class)); + } + + private static Node node(Thing thing) { + return thing.getPersistentState(); + } + + private Thing createThings() { + Transaction tx = graphDatabaseService.beginTx(); + try { + thing = new Thing(graphDatabaseService.createNode()); + nodeTypeStrategy.postEntityCreation(thing); + subThing = new SubThing(graphDatabaseService.createNode()); + nodeTypeStrategy.postEntityCreation(subThing); + tx.success(); + return thing; + } finally { + tx.finish(); + } + } + + @NodeEntity + public static class Thing { + + String name; + + public Thing() { + } + public Thing(Node n) { + setPersistentState(n); + } + + } + + public static class SubThing extends Thing { + + public SubThing() { + super(); + } + + public SubThing(Node n) { + super(n); + } + } + + private static Set set(Node... nodes) { + return new HashSet(Arrays.asList(nodes)); + } + + private void manualCleanDb() { + Transaction tx = graphDatabaseService.beginTx(); + try { + cleanDb(); + tx.success(); + } finally { + tx.finish(); + } + } +} diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/graph/neo4j/support/NoopNodeTypeStrategyTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/graph/neo4j/support/NoopNodeTypeStrategyTest.java new file mode 100644 index 000000000..8aac2fc4b --- /dev/null +++ b/spring-data-neo4j/src/test/java/org/springframework/data/graph/neo4j/support/NoopNodeTypeStrategyTest.java @@ -0,0 +1,89 @@ +package org.springframework.data.graph.neo4j.support; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Transaction; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.graph.annotation.NodeEntity; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.assertEquals; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = {"classpath:org/springframework/data/graph/neo4j/support/Neo4jGraphPersistenceTest-context.xml", + "classpath:org/springframework/data/graph/neo4j/support/NoopNodeTypeStrategyOverride-context.xml"}) +public class NoopNodeTypeStrategyTest { + + @Autowired + private GraphDatabaseContext graphDatabaseContext; + @Autowired + private NoopNodeTypeStrategy nodeTypeStrategy; + + private Thing thing; + + @Before + public void setUp() throws Exception { + thing = createThing(); + } + + @Test + public void testPostEntityCreation() throws Exception { + } + + @Test(expected = UnsupportedOperationException.class) + public void testFindAll() throws Exception { + nodeTypeStrategy.findAll(Thing.class); + } + + @Test(expected = UnsupportedOperationException.class) + public void testCount() throws Exception { + nodeTypeStrategy.count(Thing.class); + } + + @Test(expected = UnsupportedOperationException.class) + public void testGetJavaType() throws Exception { + nodeTypeStrategy.getJavaType(node(thing)); + } + + @Test + public void testPreEntityRemoval() throws Exception { + nodeTypeStrategy.preEntityRemoval(thing); + } + + @Test + public void testConfirmType() throws Exception { + assertEquals(Thing.class, nodeTypeStrategy.confirmType(node(thing), Thing.class)); + } + + private static Node node(Thing thing) { + return thing.getPersistentState(); + } + + private Thing createThing() { + Transaction tx = graphDatabaseContext.beginTx(); + try { + Node node = graphDatabaseContext.createNode(); + Thing thing = new Thing(node); + nodeTypeStrategy.postEntityCreation(thing); + tx.success(); + return thing; + } finally { + tx.finish(); + } + } + + @NodeEntity + public static class Thing { + String name; + + public Thing() { + } + + public Thing(Node n) { + setPersistentState(n); + } + } +} diff --git a/spring-data-neo4j/src/test/resources/org/springframework/data/graph/neo4j/support/IndexingNodeTypeStrategyOverride-context.xml b/spring-data-neo4j/src/test/resources/org/springframework/data/graph/neo4j/support/IndexingNodeTypeStrategyOverride-context.xml new file mode 100644 index 000000000..8c0941652 --- /dev/null +++ b/spring-data-neo4j/src/test/resources/org/springframework/data/graph/neo4j/support/IndexingNodeTypeStrategyOverride-context.xml @@ -0,0 +1,21 @@ + + + + + + + + \ No newline at end of file diff --git a/spring-data-neo4j/src/test/resources/org/springframework/data/graph/neo4j/support/Neo4jGraphPersistenceTest-context.xml b/spring-data-neo4j/src/test/resources/org/springframework/data/graph/neo4j/support/Neo4jGraphPersistenceTest-context.xml index b86ea1633..1e2ea1075 100644 --- a/spring-data-neo4j/src/test/resources/org/springframework/data/graph/neo4j/support/Neo4jGraphPersistenceTest-context.xml +++ b/spring-data-neo4j/src/test/resources/org/springframework/data/graph/neo4j/support/Neo4jGraphPersistenceTest-context.xml @@ -82,26 +82,27 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + - + + + + + + + diff --git a/spring-data-neo4j/src/test/resources/org/springframework/data/graph/neo4j/support/NoopNodeTypeStrategyOverride-context.xml b/spring-data-neo4j/src/test/resources/org/springframework/data/graph/neo4j/support/NoopNodeTypeStrategyOverride-context.xml new file mode 100644 index 000000000..b535caf13 --- /dev/null +++ b/spring-data-neo4j/src/test/resources/org/springframework/data/graph/neo4j/support/NoopNodeTypeStrategyOverride-context.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file