diff --git a/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/RelationshipEntityTests.java b/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/RelationshipEntityTests.java index e668de43b..57f6cd586 100644 --- a/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/RelationshipEntityTests.java +++ b/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/RelationshipEntityTests.java @@ -23,6 +23,8 @@ import org.neo4j.helpers.collection.IteratorUtil; import org.neo4j.helpers.collection.IteratorWrapper; import org.springframework.data.neo4j.aspects.Friendship; import org.springframework.data.neo4j.aspects.Person; +import org.springframework.data.neo4j.core.GraphDatabase; +import org.springframework.data.neo4j.template.GraphCallback; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.annotation.Transactional; @@ -81,6 +83,25 @@ public class RelationshipEntityTests extends EntityTestBase { friends.add(friendship); assertEquals(1, IteratorUtil.count(friends)); assertEquals(friendship,IteratorUtil.first(friends)); + assertEquals(friendship.getYears(),IteratorUtil.first(friends).getYears()); + } + + @Test + @Transactional + public void shouldSupportManagedSetAddOfRelationshipEntitiesSave() { + Person p = persistedPerson("Michael", 35); + Person p2 = persistedPerson("David", 25); + final Set friends = p.getFriendshipsSet(); + assertEquals(0, IteratorUtil.count(friends)); + final Friendship friendship = new Friendship(p, p2, 10); + Friendship friends2 = neo4jTemplate.save(friendship); + assertEquals(friendship, friends2); + assertEquals(friendship.getYears(),friends2.getYears()); + neo4jTemplate.fetch(p); + Set friendships = neo4jTemplate.fetch(p.getFriendshipsSet()); + Friendship friends3 = IteratorUtil.first(friendships); + assertEquals(friendship, friends3); + assertEquals(friendship.getYears(),friends3.getYears()); } @Test @@ -106,6 +127,21 @@ public class RelationshipEntityTests extends EntityTestBase { assertEquals(1, getRelationshipState(f).getProperty("Friendship.years")); } + @Test + @Transactional + public void testRelationshipSetPropertyLater() { + Person p = persistedPerson("Michael", 35); + Person p2 = persistedPerson("David", 25); + Friendship f = new Friendship(p,p2,1); + f = neo4jTemplate.save(f); + Relationship r = getRelationshipState(f); + assertEquals(1, r.getProperty("Friendship.years")); + Friendship f2 = neo4jTemplate.findOne(r.getId(), Friendship.class); + f2.setYears(2); + f2 = neo4jTemplate.save(f2); + assertEquals(2, getRelationshipState(f2).getProperty("Friendship.years")); + } + @Test @Transactional public void testRelationshipGetProperty() { @@ -135,7 +171,6 @@ public class RelationshipEntityTests extends EntityTestBase { assertEquals(f, neo4jTemplate.getRelationshipBetween(p, p2, Friendship.class, "knows")); } - //@Ignore("The NodeBacking.getRelationshipTo() method is broken at the moment") @Test @Transactional public void testGetRelationshipTo() { diff --git a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPI.java b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPI.java index ccd7f610f..878709851 100644 --- a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPI.java +++ b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPI.java @@ -86,12 +86,6 @@ public interface RestAPI extends RestAPIIndex, RestAPIInternal { // internal - RestRequest getRestRequest(); - - RestNode addToCache(RestNode restNode); - RestNode getFromCache(long id); - void removeFromCache(long id); - void close(); Relationship getOrCreateRelationship(Node start, Node end, RelationshipType type, Direction direction, Map props); diff --git a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPICypherImpl.java b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPICypherImpl.java index 8589c5854..05063f0db 100644 --- a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPICypherImpl.java +++ b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPICypherImpl.java @@ -104,7 +104,7 @@ public class RestAPICypherImpl implements RestAPI { @Override public RestNode getNodeById(long id, Load force) { if (force != Load.ForceFromServer) { - RestNode restNode = getFromCache(id); + RestNode restNode = getNodeFromCache(id); if (restNode != null) return restNode; } if (force == Load.FromCache) return new RestNode(RestNode.nodeUri(this, id), this); @@ -116,13 +116,49 @@ public class RestAPICypherImpl implements RestAPI { return addToCache(toNode(row)); } - public RestNode getFromCache(long id) { - return restAPI.getFromCache(id); + @Override + public RestRelationship addToCache(RestRelationship restRelationship) { + return restAPI.addToCache(restRelationship); } @Override - public void removeFromCache(long id) { - restAPI.removeFromCache(id); + public RestRelationship getRelationshipById(long id, Load force) { + if (force != Load.ForceFromServer) { + RestRelationship restRel = getRelFromCache(id); + if (restRel != null) return restRel; + } + if (force == Load.FromCache) return new RestRelationship(RestRelationship.relUri(this, id), this); + try { + Iterator> result = runQuery(GET_REL_QUERY, map("id", id)).getRows().iterator(); + if (!result.hasNext()) { + throw new NotFoundException("Relationship not found " + id); + } + List row = result.next(); + return addToCache(toRel(row)); + } catch (NotFoundException e) { + throw e; + } catch (CypherTransactionExecutionException ctee) { + if (ctee.contains("Neo.DatabaseError.Statement.ExecutionFailure","not found")) { + throw new NotFoundException("Relationship not found " + id); + } + throw ctee; + } + } + + public RestNode getNodeFromCache(long id) { + return restAPI.getNodeFromCache(id); + } + public RestRelationship getRelFromCache(long id) { + return restAPI.getRelFromCache(id); + } + + @Override + public void removeNodeFromCache(long id) { + restAPI.removeNodeFromCache(id); + } + @Override + public void removeRelFromCache(long id) { + restAPI.removeRelFromCache(id); } @Override @@ -130,6 +166,12 @@ public class RestAPICypherImpl implements RestAPI { return getNodeById(id, Load.FromServer); } + @Override + public RestRelationship getRelationshipById(long id) { + return getRelationshipById(id, Load.FromServer); + } + + private RestNode toNode(List row) { long id = ((Number) row.get(0)).longValue(); List labels = (List) row.get(1); @@ -146,26 +188,6 @@ public class RestAPICypherImpl implements RestAPI { return RestRelationship.fromCypher(id, type, props, start, end, this); } - @Override - public RestRelationship getRelationshipById(long id) { - try { - Iterator> result = runQuery(GET_REL_QUERY, map("id", id)).getRows().iterator(); - if (!result.hasNext()) { - throw new NotFoundException("Relationship not found " + id); - } - List row = result.next(); - return toRel(row); - } catch (NotFoundException e) { - throw e; - } catch (CypherTransactionExecutionException ctee) { - if (ctee.contains("Neo.DatabaseError.Statement.ExecutionFailure","not found")) { - throw new NotFoundException("Relationship not found " + id); - } - throw ctee; - } - } - - @Override public RestNode createNode(Map props) { return createNode(props, Collections.emptyList()); @@ -381,9 +403,10 @@ public class RestAPICypherImpl implements RestAPI { public void deleteEntity(RestEntity entity) { if (entity instanceof Node) { runQuery(_MATCH_NODE_QUERY + " DELETE n", map("id", entity.getId())); - restAPI.removeFromCache(entity.getId()); + restAPI.removeNodeFromCache(entity.getId()); } else if (entity instanceof Relationship) { runQuery(_MATCH_REL_QUERY + " DELETE r", map("id", entity.getId())); + restAPI.removeRelFromCache(entity.getId()); } } diff --git a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPIImpl.java b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPIImpl.java index 638938e23..5d12ace1c 100644 --- a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPIImpl.java +++ b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPIImpl.java @@ -47,8 +47,6 @@ import org.neo4j.rest.graphdb.traversal.RestTraverser; import org.neo4j.rest.graphdb.util.JsonHelper; import org.neo4j.rest.graphdb.util.QueryResult; import org.neo4j.rest.graphdb.util.ResultConverter; -import org.springframework.data.neo4j.mapping.Neo4jPersistentEntity; -import org.springframework.data.neo4j.support.mapping.Neo4jMappingContext; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; @@ -119,36 +117,63 @@ public class RestAPIImpl implements RestAPI { return entityCache.addToCache(node); } + @Override + public RestRelationship getRelationshipById(long id, Load force) { + if (force != Load.ForceFromServer) { + RestRelationship restRel = entityCache.getRelationship(id); + if (restRel != null) return restRel; + } + if (force == Load.FromCache) return new RestRelationship(RestRelationship.relUri(this, id),this); + +// BatchRestAPI batchRestAPI = new BatchRestAPI(this); +// RestNode node = batchRestAPI.getNodeById(id); + RequestResult response = restRequest.get("relationship/" + id); + if (response.statusIs(Status.NOT_FOUND)) { + throw new NotFoundException("" + id); + } + Map data = (Map) response.toMap(); + RestRelationship rel = new RestRelationship(data, this); + return entityCache.addToCache(rel); + } + @Override public RestNode addToCache(RestNode restNode) { return entityCache.addToCache(restNode); } @Override - public RestNode getFromCache(long id) { + public RestRelationship addToCache(RestRelationship rel) { + return entityCache.addToCache(rel); + } + + @Override + public RestNode getNodeFromCache(long id) { return entityCache.getNode(id); } @Override - public void removeFromCache(long id) { - entityCache.remove(id); + public RestRelationship getRelFromCache(long id) { + return entityCache.getRelationship(id); + } + + @Override + public void removeNodeFromCache(long id) { + entityCache.removeNode(id); + } + @Override + public void removeRelFromCache(long id) { + entityCache.removeRelationship(id); } @Override public RestNode getNodeById(long id) { return getNodeById(id, Load.FromServer); } - @Override public RestRelationship getRelationshipById(long id) { - RequestResult requestResult = restRequest.get("relationship/" + id); - if (requestResult.statusIs(Status.NOT_FOUND)) { - throw new NotFoundException("" + id); - } - return new RestRelationship(requestResult.toMap(), this); + return getRelationshipById(id, Load.FromServer); } - @Override public RestNode createNode(Map props) { return createNode(props,Collections.emptyList()); @@ -582,7 +607,7 @@ public class RestAPIImpl implements RestAPI { @Override public void deleteEntity(RestEntity entity) { getRestRequest().with(entity.getUri()).delete( "" ); - entityCache.remove(entity.getId()); + entityCache.removeNode(entity.getId()); } @Override public IndexInfo indexInfo(final String indexType) { diff --git a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPIInternal.java b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPIInternal.java index 35cb9daa9..ef09fff90 100644 --- a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPIInternal.java +++ b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPIInternal.java @@ -4,6 +4,7 @@ import org.neo4j.graphdb.PropertyContainer; import org.neo4j.rest.graphdb.converter.RestEntityExtractor; import org.neo4j.rest.graphdb.entity.RestEntity; import org.neo4j.rest.graphdb.entity.RestNode; +import org.neo4j.rest.graphdb.entity.RestRelationship; import java.util.Map; @@ -13,6 +14,7 @@ import java.util.Map; */ public interface RestAPIInternal { RestNode getNodeById(long id, RestAPI.Load force); + RestRelationship getRelationshipById(long id, RestAPI.Load force); boolean hasToUpdate(long lastUpdate); @@ -30,4 +32,13 @@ public interface RestAPIInternal { FromServer, ForceFromServer } + + RestRequest getRestRequest(); + + RestNode addToCache(RestNode restNode); + RestRelationship addToCache(RestRelationship restRelationship); + RestNode getNodeFromCache(long id); + RestRelationship getRelFromCache(long id); + void removeNodeFromCache(long id); + void removeRelFromCache(long id); } diff --git a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/entity/RestEntityCache.java b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/entity/RestEntityCache.java index 7e03655f5..326db024a 100644 --- a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/entity/RestEntityCache.java +++ b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/entity/RestEntityCache.java @@ -9,7 +9,8 @@ import org.neo4j.rest.graphdb.RestAPI; */ public class RestEntityCache { - private LruCache lruCache = new LruCache<>("RestNode",10000); + private LruCache lruNodeCache = new LruCache<>("RestNode",10000); + private LruCache lruRelCache = new LruCache<>("RestRelationship",10000); private final RestAPI restAPI; @@ -21,22 +22,42 @@ public class RestEntityCache { if (node == null) return null; long id = node.getId(); if (id != -1) { - RestNode existing = lruCache.get(id); + RestNode existing = lruNodeCache.get(id); if (existing !=null) { if (existing != node) existing.updateFrom(node, restAPI); return existing; } else { - lruCache.put(id, node); + lruNodeCache.put(id, node); } } return node; } + public RestRelationship addToCache(RestRelationship rel) { + if (rel == null) return null; + long id = rel.getId(); + if (id != -1) { + RestRelationship existing = lruRelCache.get(id); + if (existing !=null) { + if (existing != rel) existing.updateFrom(rel, restAPI); + return existing; + } else { + lruRelCache.put(id, rel); + } + } + return rel; + } public RestNode getNode(long id) { - return lruCache.get(id); + return lruNodeCache.get(id); + } + public RestRelationship getRelationship(long id) { + return lruRelCache.get(id); } - public void remove(long id) { - lruCache.remove(id); + public void removeNode(long id) { + lruNodeCache.remove(id); + } + public void removeRelationship(long id) { + lruRelCache.remove(id); } } diff --git a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/entity/RestRelationship.java b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/entity/RestRelationship.java index 29505b658..7a428a030 100644 --- a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/entity/RestRelationship.java +++ b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/entity/RestRelationship.java @@ -50,7 +50,7 @@ public class RestRelationship extends RestEntity implements Relationship { @Override protected void doUpdate() { - updateFrom(restApi.getRelationshipById(getId()), restApi); + updateFrom(restApi.getRelationshipById(getId(),RestAPIInternal.Load.ForceFromServer), restApi); } public Node getEndNode() { diff --git a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestRelationshipTests.java b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestRelationshipTests.java new file mode 100644 index 000000000..a18fbca8e --- /dev/null +++ b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestRelationshipTests.java @@ -0,0 +1,123 @@ +/** + * 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.rest.integration; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.neo4j.graphdb.GraphDatabaseService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.data.neo4j.annotation.*; +import org.springframework.data.neo4j.config.Neo4jConfiguration; +import org.springframework.data.neo4j.rest.SpringCypherRestGraphDatabase; +import org.springframework.data.neo4j.rest.SpringRestGraphDatabase; +import org.springframework.data.neo4j.rest.support.RestTestBase; +import org.springframework.data.neo4j.support.Neo4jTemplate; +import org.springframework.test.context.CleanContextCacheTestExecutionListener; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestExecutionListeners; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; +import org.springframework.test.context.transaction.TransactionalTestExecutionListener; + +import static org.junit.Assert.assertEquals; + +/** +* @author mh +* @since 28.03.11 +*/ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = RestRelationshipTests.MyConfig.class) +@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class}) +public class RestRelationshipTests { + + static class MyConfig extends Neo4jConfiguration { + public MyConfig() { + setBasePackage("org.springframework.data.neo4j.rest.integration"); + } + + @Bean + public GraphDatabaseService graphDatabaseService() { + return new SpringCypherRestGraphDatabase(RestTestBase.SERVER_ROOT_URI); +// return new SpringRestGraphDatabase(RestTestBase.SERVER_ROOT_URI); + } + } + + @NodeEntity + public static class A { + @GraphId + Long id; + String name; + + public A(String name) { + this.name = name; + } + + public A() { } + } + + @RelationshipEntity(type = "AREL") + public static class ARel { + @GraphId + Long id; + @StartNode A a1; + @EndNode + A a2; + + String prop; + + public ARel(A a1, A a2, String prop) { + this.a1 = a1; + this.a2 = a2; + this.prop = prop; + } + + public ARel() { } + } + + @Autowired + Neo4jTemplate neo4jTemplate; + + @BeforeClass + public static void startDb() throws Exception { + RestTestBase.startDb(); + } + + @Before + public void cleanDb() { + RestTestBase.cleanDb(); + } + + @AfterClass + public static void shutdownDb() { + RestTestBase.shutdownDb(); + + } + + @Test + public void testRelationshipSaveProperty() { + A a1 = neo4jTemplate.save(new A("a1")); + A a2 = neo4jTemplate.save(new A("a2")); + ARel rel = neo4jTemplate.save(new ARel(a1, a2, "foo")); + ARel rel2 = neo4jTemplate.findOne(rel.id, ARel.class); + assertEquals("foo",rel2.prop); + } + +} diff --git a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/RestQueryEngineTests.java b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/RestQueryEngineTests.java index 8fa622480..09371f39a 100644 --- a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/RestQueryEngineTests.java +++ b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/RestQueryEngineTests.java @@ -25,7 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.neo4j.aspects.support.query.QueryEngineTests; import org.springframework.data.neo4j.core.GraphDatabase; import org.springframework.data.neo4j.rest.SpringCypherRestGraphDatabase; -import org.springframework.data.neo4j.rest.SpringRestGraphDatabase; +import org.springframework.data.neo4j.rest.SpringCypherRestGraphDatabase; import org.springframework.test.context.CleanContextCacheTestExecutionListener; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestExecutionListeners; diff --git a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/RestTestBase.java b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/RestTestBase.java index ce758466b..43a488fca 100644 --- a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/RestTestBase.java +++ b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/RestTestBase.java @@ -36,7 +36,7 @@ import org.neo4j.server.configuration.ServerConfigurator; import org.neo4j.test.ImpermanentGraphDatabase; import org.springframework.data.neo4j.core.GraphDatabase; import org.springframework.data.neo4j.rest.SpringCypherRestGraphDatabase; -import org.springframework.data.neo4j.rest.SpringRestGraphDatabase; +import org.springframework.data.neo4j.rest.SpringCypherRestGraphDatabase; import java.util.Iterator; diff --git a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/RestTestHelper.java b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/RestTestHelper.java index c1fccf9d0..ecc6cbbe1 100644 --- a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/RestTestHelper.java +++ b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/RestTestHelper.java @@ -47,6 +47,7 @@ public class RestTestHelper } public GraphDatabase createGraphDatabase() throws URISyntaxException { +// return new SpringRestGraphDatabase(SERVER_ROOT_URI); return new SpringCypherRestGraphDatabase(SERVER_ROOT_URI); }