diff --git a/spring-data-neo4j-rest/src/main/java/org/springframework/data/neo4j/rest/SpringRestGraphDatabase.java b/spring-data-neo4j-rest/src/main/java/org/springframework/data/neo4j/rest/SpringRestGraphDatabase.java index c80c3cf9f..f1d7768ff 100644 --- a/spring-data-neo4j-rest/src/main/java/org/springframework/data/neo4j/rest/SpringRestGraphDatabase.java +++ b/spring-data-neo4j-rest/src/main/java/org/springframework/data/neo4j/rest/SpringRestGraphDatabase.java @@ -20,12 +20,15 @@ import org.neo4j.graphdb.PropertyContainer; import org.neo4j.graphdb.Relationship; import org.neo4j.graphdb.RelationshipType; import org.neo4j.graphdb.index.Index; +import org.neo4j.graphdb.index.RelationshipIndex; import org.neo4j.graphdb.traversal.TraversalDescription; import org.neo4j.helpers.collection.MapUtil; import org.neo4j.rest.graphdb.ExecutingRestRequest; import org.neo4j.rest.graphdb.RequestResult; import org.neo4j.rest.graphdb.RestAPI; import org.neo4j.rest.graphdb.RestRequest; +import org.neo4j.rest.graphdb.entity.RestNode; +import org.neo4j.rest.graphdb.index.RestIndex; import org.neo4j.rest.graphdb.index.RestIndexManager; import org.neo4j.rest.graphdb.query.RestCypherQueryEngine; import org.neo4j.rest.graphdb.query.RestGremlinQueryEngine; @@ -66,21 +69,23 @@ public class SpringRestGraphDatabase extends org.neo4j.rest.graphdb.RestGraphDat return super.getRestAPI().createNode(props); } - // TODO move to RestAPI - public Node getOrCreateNode(String index, String key, Object value, final Map properties) { - if (index==null || key == null || value==null) throw new IllegalArgumentException("Unique index "+index+" key "+key+" value must not be null"); - final RequestResult result = getRestAPI().getRestRequest().post("index/node/" + index + "?unique", MapUtil.map("key",key,"value",value,"properties",properties)); - if (result.statusIs(Response.Status.CREATED) || result.statusIs(Response.Status.OK)) { - return (Node)getRestAPI().createExtractor().convertFromRepresentation(result); - } - throw new RuntimeException(String.format("Error retrieving or creating node for key %s and value %s with index %s", key, value, index)); + @Override + public Node getOrCreateNode(String indexName, String key, Object value, final Map properties) { + if (indexName ==null || key == null || value==null) throw new IllegalArgumentException("Unique index "+ indexName +" key "+key+" value must not be null"); + final RestIndex nodeIndex = index().forNodes(indexName); + return getRestAPI().getOrCreateNode(nodeIndex, key, value, properties); } + @Override + public Relationship getOrCreateRelationship(String indexName, String key, Object value, Node startNode, Node endNode, String type, Map properties) { + @SuppressWarnings("unchecked") final RestIndex relIndex = (RestIndex) index().forRelationships(indexName); + return getRestAPI().getOrCreateRelationship(relIndex,key,value,(RestNode) startNode,(RestNode) endNode,type, properties); + } @Override - public Relationship createRelationship(Node startNode, Node endNode, RelationshipType type, Map props) { - return super.getRestAPI().createRelationship(startNode, endNode, type, props); + public Relationship createRelationship(Node startNode, Node endNode, RelationshipType type, Map properties) { + return super.getRestAPI().createRelationship(startNode, endNode, type, properties); } @Override diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/GraphDatabase.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/GraphDatabase.java index b0120a6f4..a30a4d58f 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/GraphDatabase.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/GraphDatabase.java @@ -53,7 +53,7 @@ public interface GraphDatabase { * creates the node uniquely or returns an existing node with the same index-key-value combination. * properties are used to initialize the node. */ - Node getOrCreateNode(String index, String key, Object value, final Map properties); + Node getOrCreateNode(String indexName, String key, Object value, final Map properties); /** * @param id relationship id @@ -62,7 +62,27 @@ public interface GraphDatabase { */ Relationship getRelationshipById(long id); - Relationship createRelationship(Node startNode, Node endNode, RelationshipType type, Map props); + /** + * creates the relationship between the startNode, endNode with the given type which will be populated with the provided properties + */ + Relationship createRelationship(Node startNode, Node endNode, RelationshipType type, Map properties); + + + /** + * Creates the relationship uniquely, uses the given index,key,value to achieve that. + * If the relationship for this combination already existed it is returned otherwise created and populated with the provided properties. + */ + Relationship getOrCreateRelationship(String indexName, String key, Object value, Node startNode, Node endNode, String type, Map properties); + + /** + * deletes the Node and its index entries + */ + void remove(Node node); + + /** + * deletes the relationship and its index entries + */ + void remove(Relationship relationship); /** * @param indexName existing index name, not null @@ -86,18 +106,28 @@ public interface GraphDatabase { */ TraversalDescription traversalDescription(); + /** + * returns a query engine for the provided type (Cypher or Gremlin) which is initialized with the default result converter + */ QueryEngine queryEngineFor(QueryType type); - void setConversionService(ConversionService conversionService); - + /** + * returns a query engine for the provided type (Cypher or Gremlin) which is initialized with the provided result converter + */ QueryEngine queryEngineFor(QueryType type, ResultConverter resultConverter); - boolean transactionIsRunning(); - - void remove(Node node); - - void remove(Relationship relationship); + /** + * @param conversionService the conversion service to be used for the default result converter of this database + */ + void setConversionService(ConversionService conversionService); + /** + * @param resultConverter the default result converter to be used with this database + */ void setResultConverter(ResultConverter resultConverter); + /** + * @return true if a transaction is currently running + */ + boolean transactionIsRunning(); } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/DelegatingGraphDatabase.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/DelegatingGraphDatabase.java index 73502d2c0..38fa23c5b 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/DelegatingGraphDatabase.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/DelegatingGraphDatabase.java @@ -16,6 +16,7 @@ package org.springframework.data.neo4j.support; +import org.neo4j.graphdb.DynamicRelationshipType; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.PropertyContainer; @@ -25,6 +26,7 @@ import org.neo4j.graphdb.index.Index; import org.neo4j.graphdb.index.IndexManager; import org.neo4j.graphdb.index.UniqueFactory; import org.neo4j.graphdb.traversal.TraversalDescription; +import org.neo4j.index.lucene.ValueContext; import org.neo4j.kernel.AbstractGraphDatabase; import org.neo4j.kernel.GraphDatabaseAPI; import org.neo4j.kernel.Traversal; @@ -122,8 +124,8 @@ public class DelegatingGraphDatabase implements GraphDatabase { } @Override - public Relationship createRelationship(Node startNode, Node endNode, RelationshipType type, Map props) { - return setProperties(startNode.createRelationshipTo(endNode,type),props); + public Relationship createRelationship(Node startNode, Node endNode, RelationshipType type, Map properties) { + return setProperties(startNode.createRelationshipTo(endNode,type), properties); } @SuppressWarnings("unchecked") @@ -263,13 +265,29 @@ public class DelegatingGraphDatabase implements GraphDatabase { } } - public Node getOrCreateNode(String index, String key, Object value, final Map nodeProperties) { - if (index==null || key == null || value==null) throw new IllegalArgumentException("Unique index "+index+" key "+key+" value must not be null"); - UniqueFactory.UniqueNodeFactory factory = new UniqueFactory.UniqueNodeFactory(delegate, index) { + public Node getOrCreateNode(String indexName, String key, Object value, final Map nodeProperties) { + if (indexName ==null || key == null || value==null) throw new IllegalArgumentException("Unique index "+ indexName +" key "+key+" value must not be null"); + if (value instanceof Number) value= ValueContext.numeric((Number)value); + UniqueFactory.UniqueNodeFactory factory = new UniqueFactory.UniqueNodeFactory(delegate, indexName) { protected void initialize(Node node, Map _) { setProperties(node,nodeProperties); } }; return factory.getOrCreate(key, value); } + + @Override + public Relationship getOrCreateRelationship(String indexName, String key, Object value, final Node startNode, final Node endNode, final String type, final Map properties) { + if (indexName ==null || key == null || value==null) throw new IllegalArgumentException("Unique index "+ indexName +" key "+key+" value must not be null"); + if (startNode ==null || endNode == null || type==null) throw new IllegalArgumentException("StartNode "+ startNode +" EndNode "+ endNode +" and type "+type+" must not be null"); + if (value instanceof Number) value= ValueContext.numeric((Number)value); + UniqueFactory.UniqueRelationshipFactory factory = new UniqueFactory.UniqueRelationshipFactory(delegate, indexName) { + @Override + protected Relationship create(Map _) { + final Relationship relationship = startNode.createRelationshipTo(endNode, DynamicRelationshipType.withName(type)); + return setProperties(relationship, properties); + } + }; + return factory.getOrCreate(key, value); + } } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Neo4jTemplate.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Neo4jTemplate.java index 7d05c58e6..1d2aa51e2 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Neo4jTemplate.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Neo4jTemplate.java @@ -238,7 +238,7 @@ public class Neo4jTemplate implements Neo4jOperations, EntityPersister { */ @Override public Node createNode() { - return infrastructure.getGraphDatabase().createNode(null); + return getGraphDatabase().createNode(null); } /** @@ -247,7 +247,7 @@ public class Neo4jTemplate implements Neo4jOperations, EntityPersister { */ @Override public Node createNode(final Map properties) { - return infrastructure.getGraphDatabase().createNode(properties); + return getGraphDatabase().createNode(properties); } /** @@ -256,7 +256,7 @@ public class Neo4jTemplate implements Neo4jOperations, EntityPersister { */ @Override public Node getOrCreateNode(String index, String key, Object value, final Map properties) { - return infrastructure.getGraphDatabase().getOrCreateNode(index, key, value, properties); + return getGraphDatabase().getOrCreateNode(index, key, value, properties); } @Override @@ -385,6 +385,11 @@ public class Neo4jTemplate implements Neo4jOperations, EntityPersister { }); } + @Override + public Relationship getOrCreateRelationship(String indexName, String key, Object value, Node startNode, Node endNode, String type, Map properties) { + return getGraphDatabase().getOrCreateRelationship(indexName,key,value,startNode,endNode,type,properties); + } + private final Neo4jExceptionTranslator exceptionTranslator = new Neo4jExceptionTranslator(); public DataAccessException translateExceptionIfPossible(Exception ex) { diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/template/Neo4jOperations.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/template/Neo4jOperations.java index 4fc7d4889..e6e1ac60c 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/template/Neo4jOperations.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/template/Neo4jOperations.java @@ -103,7 +103,13 @@ public interface Neo4jOperations { /** * Creates a relationship with the given initial properties. */ - Relationship createRelationshipBetween(Node startNode, Node endNode, String type, Map props); + Relationship createRelationshipBetween(Node startNode, Node endNode, String type, Map properties); + + /** + * Creates the relationship uniquely, uses the given index,key,value to achieve that. + * If the relationship for this combination already existed it is returned otherwise created and populated with the provided properties. + */ + Relationship getOrCreateRelationship(String indexName, String key, Object value, Node startNode, Node endNode, String type, Map properties); /** * Retrieves a single relationship entity between two node entities with the given relationship type projected to the provided diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/support/DelegatingGraphDatabaseTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/support/DelegatingGraphDatabaseTest.java new file mode 100644 index 000000000..b32fb360a --- /dev/null +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/support/DelegatingGraphDatabaseTest.java @@ -0,0 +1,75 @@ +/** + * Copyright 2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.support; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Relationship; +import org.neo4j.graphdb.Transaction; +import org.neo4j.helpers.collection.MapUtil; +import org.neo4j.test.ImpermanentGraphDatabase; + +import static org.junit.Assert.assertEquals; +import static org.neo4j.helpers.collection.MapUtil.map; + +/** + * @author mh + * @since 11.04.12 + */ +public class DelegatingGraphDatabaseTest { + + private DelegatingGraphDatabase graphDatabase; + private ImpermanentGraphDatabase gdb; + + @Before + public void setUp() throws Exception { + gdb = new ImpermanentGraphDatabase(); + graphDatabase = new DelegatingGraphDatabase(gdb); + } + + @After + public void tearDown() throws Exception { + graphDatabase.shutdown(); + } + + @Test + public void testGetOrCreateNode() throws Exception { + final Node node = graphDatabase.getOrCreateNode("user", "name", "David", map("name", "David")); + final Node node2 = graphDatabase.getOrCreateNode("user", "name", "David", map("name", "David")); + assertEquals("David",node.getProperty("name")); + assertEquals(node,node2); + assertEquals(node,gdb.index().forNodes("user").get("name","David").getSingle()); + } + + @Test + public void testGetOrCreateRelationship() throws Exception { + final Transaction tx = gdb.beginTx(); + final Node david = graphDatabase.createNode(map("name", "David")); + final Node michael = graphDatabase.createNode(map("name", "Michael")); + final Relationship rel1 = graphDatabase.getOrCreateRelationship("knows", "whom", "david_michael", david, michael, "KNOWS", map("whom", "david_michael")); + final Relationship rel2 = graphDatabase.getOrCreateRelationship("knows", "whom", "david_michael", david, michael, "KNOWS", map("whom", "david_michael")); + assertEquals("david_michael",rel1.getProperty("whom")); + assertEquals("KNOWS",rel1.getType().name()); + assertEquals(david,rel1.getStartNode()); + assertEquals(michael,rel1.getEndNode()); + assertEquals(rel1,rel2); + assertEquals(rel1,gdb.index().forRelationships("knows").get("whom","david_michael").getSingle()); + tx.success(); + tx.finish(); + } +}