diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/ContainerConverter.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/ContainerConverter.java index 5522e2f1d..943cd90d2 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/ContainerConverter.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/ContainerConverter.java @@ -25,8 +25,16 @@ import java.util.*; * @since 11.11.11 */ public class ContainerConverter { + /* + + if (Set.class.isAssignableFrom(collectionLikeType)) return (C)IteratorUtil.addToCollection(this, new LinkedHashSet()); + if (Collection.class.isAssignableFrom(collectionLikeType)) return (C)IteratorUtil.addToCollection(this,new ArrayList()); + return (C)this; + + */ @SuppressWarnings("unchecked") public static > C toContainer(Class container, Iterable data) { + if (container.isInstance(data)) return (C) data; if (Iterable.class.equals(container)) return (C) data; if (SortedSet.class.isAssignableFrom(container)) return (C) IteratorUtil.addToCollection(data, new TreeSet()); if (Set.class.isAssignableFrom(container)) return (C) IteratorUtil.addToCollection(data, new HashSet()); diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/QueryResultBuilder.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/QueryResultBuilder.java index e3c60c6a4..966e77e58 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/QueryResultBuilder.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/QueryResultBuilder.java @@ -20,14 +20,12 @@ import org.neo4j.graphdb.index.IndexHits; import org.neo4j.helpers.collection.ClosableIterable; import org.neo4j.helpers.collection.IterableWrapper; import org.neo4j.helpers.collection.IteratorUtil; -import org.neo4j.helpers.collection.IteratorWrapper; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.neo4j.mapping.MappingPolicy; -import java.util.Arrays; -import java.util.Iterator; +import java.util.*; /** * @author mh diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/QueryFieldAccessorFactory.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/QueryFieldAccessorFactory.java index bc4588a2e..a4bbce69b 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/QueryFieldAccessorFactory.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/QueryFieldAccessorFactory.java @@ -19,13 +19,11 @@ package org.springframework.data.neo4j.fieldaccess; import org.neo4j.graphdb.Node; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.neo4j.annotation.Query; - import org.springframework.data.neo4j.conversion.Result; import org.springframework.data.neo4j.mapping.MappingPolicy; import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty; import org.springframework.data.neo4j.support.Neo4jTemplate; import org.springframework.data.neo4j.support.query.CypherQueryEngine; -import org.springframework.data.neo4j.support.query.QueryEngine; import org.springframework.data.util.TypeInformation; import java.util.HashMap; @@ -106,8 +104,10 @@ public class QueryFieldAccessorFactory implements FieldAccessorFactory { if (actualType.isMap()) { return result; } + if (typeInformation.isCollectionLike()) { - return result.to(targetType); + Class collectionType = (Class) typeInformation.getType(); + return result.to(targetType).as(collectionType); } return result.to(targetType).singleOrNull(); } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/GraphQueryMethod.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/GraphQueryMethod.java index 3fdd6d98b..c6fe29e77 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/GraphQueryMethod.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/GraphQueryMethod.java @@ -149,19 +149,10 @@ public class GraphQueryMethod extends QueryMethod { return new CypherGraphRepositoryQuery(this, template); } - public boolean isSetResult() { - final Class superClass = Set.class; - return hasResultOfType(superClass); - } - public boolean hasResultOfType(Class superClass) { return superClass.isAssignableFrom(getReturnType()); } - public boolean isCollectionResult() { - return hasResultOfType(Collection.class); - } - @Override public String toString() { return "Repository-Graph-Query-Method for "+method; diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/GraphRepositoryQuery.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/GraphRepositoryQuery.java index 7787e9718..81e6f7436 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/GraphRepositoryQuery.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/GraphRepositoryQuery.java @@ -21,6 +21,7 @@ import org.neo4j.helpers.collection.IteratorUtil; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.SliceImpl; +import org.springframework.data.neo4j.conversion.QueryResultBuilder; import org.springframework.data.neo4j.conversion.Result; import org.springframework.data.neo4j.core.GraphDatabase; import org.springframework.data.neo4j.support.Neo4jTemplate; @@ -103,18 +104,19 @@ abstract class GraphRepositoryQuery implements RepositoryQuery, ParameterResolve GraphQueryMethod queryMethod = getQueryMethod(); final QueryEngine queryEngine = getQueryEngine(); final Class compoundType = queryMethod.getCompoundType(); + Result queryResult = queryEngine.query(queryString, params); + Class returnType = queryMethod.getReturnType(); if (queryMethod.isPageQuery() || queryMethod.isSliceQuery()) { - @SuppressWarnings("unchecked") final Iterable result = queryEngine.query(queryString, params).to(compoundType); + @SuppressWarnings("unchecked") final Iterable result = queryResult.to(compoundType); Long count = computeCount(params); - return createPage(result, accessor.getPageable(),count, queryMethod.isPageQuery()); + return createPage(result, accessor.getPageable(), count, queryMethod.isPageQuery()); } + if (queryMethod.isIterableResult()) { - final Result result = queryEngine.query(queryString, params).to(compoundType); - if (queryMethod.isSetResult()) return IteratorUtil.addToCollection(result,new LinkedHashSet()); - if (queryMethod.isCollectionResult()) return IteratorUtil.addToCollection(result,new ArrayList()); - return result; + Class collectionType = (Class) returnType; + return queryResult.to(compoundType).as(collectionType); } - return queryEngine.query(queryString, params).to(queryMethod.getReturnType()).singleOrNull(); + return queryResult.to(returnType).singleOrNull(); } private Long computeCount(Map params) { diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/Neo4JPersistentPropertyImpl.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/Neo4JPersistentPropertyImpl.java index afe66d3a5..8bf783aa8 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/Neo4JPersistentPropertyImpl.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/Neo4JPersistentPropertyImpl.java @@ -334,8 +334,18 @@ class Neo4jPersistentPropertyImpl extends AnnotationBasedPersistentProperty members = grp.getTeamMembersAsSetViaQuery(); + assertThat( members, hasSize(3)); + assertThat( members, hasItems( matrixTeam.neo, matrixTeam.trinity, matrixTeam.cypher)); + + assertNull(members.iterator().next().getName()); + } + + // DATAGRAPH-257 + @Transactional + @Test + public void testPropertyQueryOfTypeIterablePopulatedCorrectly() { + Group grp = neo4jTemplate.findOne(matrixTeam.matrixGroup.getId(), Group.class); + Iterable itMembers = grp.getTeamMembersAsIterableViaQuery(); + assertNotNull(itMembers); + Collection members = asCollection(itMembers); + assertThat( members, hasSize(3)); + assertThat( members, hasItems( matrixTeam.neo, matrixTeam.trinity, matrixTeam.cypher)); + assertNull(members.iterator().next().getName()); + } + + // DATAGRAPH-257 + @Transactional + @Test + public void testRepositoryQueryOfTypeSetPopulatedCorrectly() { + + // This is really for comparison purposes - comparing query via + // a repository vs via an entity property with @Fetch i.e + // testFetchedPropertyQueryOfTypeSetPopulatedCorrectly + // (theoretically, they should return the same result) + Set members = groupRepository.getTeamMembersAsSetViaQuery(matrixTeam.matrixGroup.getName()); + assertExpectionsOfMatrixTeamSetResult(members); + } + + // DATAGRAPH-257 + @Transactional + @Test + @Ignore("@Fetch breaks Neo4jEntityConverterTests - investigation required") + public void testFetchedPropertyQueryOfTypeIterablePopulatedCorrectly() { + Group grp = neo4jTemplate.findOne(matrixTeam.matrixGroup.getId(), Group.class); + Iterable itMembers = grp.getFetchedTeamMembersAsIterableViaQuery(); + Collection members = asCollection(itMembers); + assertExpectionsOfMatrixTeamSetResult(members); + } + + // DATAGRAPH-257 + @Transactional + @Test + @Ignore("@Fetch breaks Neo4jEntityConverterTests - investigation required") + public void testFetchedPropertyQueryOfTypeSetPopulatedCorrectly() { + Group grp = neo4jTemplate.findOne(matrixTeam.matrixGroup.getId(), Group.class); + Set members = grp.getFetchedTeamMembersAsSetViaQuery(); + assertExpectionsOfMatrixTeamSetResult(members); + } + + private void assertExpectionsOfMatrixTeamSetResult(Collection members) { + assertThat( members, hasSize(3)); + assertThat( members, hasItems( matrixTeam.neo, matrixTeam.trinity, matrixTeam.cypher)); + // Queries should return the fully populated object (at least at the first level) + assertNotNull(members.iterator().next().getName()); + } + +} \ No newline at end of file diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/model/Group.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/model/Group.java index b19363f77..cfabc75db 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/model/Group.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/model/Group.java @@ -230,4 +230,39 @@ public class Group implements IGroup , Serializable { public Collection getFetchedPersons() { return fetchedPersons; } + + /* + Note: All @Query annotated fields should be transient so that they are not serialized + */ + + @Query("match (g2:g)-[:persons]->(member) where id(g2) = {self} return member") + private transient Set teamMembersAsSetViaQuery; + + @Query("match (g3:g)-[:persons]->(member) where id(g3) = {self} return member") + private transient Iterable teamMembersAsIterableViaQuery; + + //@Fetch //(breaks Neo4jEntityConverterTests - investigate) + @Query("match (g4)-[:persons]->(member) where id(g4) = {self} return member") + private transient Iterable fetchedTeamMembersAsIterableViaQuery; + + //@Fetch //(breaks Neo4jEntityConverterTests - investigate) + @Query("match (g1)-[:persons]-(member1) where id(g1) = {self} return member1") + private transient Set fetchedTeamMembersAsSetViaQuery; + + public Iterable getFetchedTeamMembersAsIterableViaQuery() { + return fetchedTeamMembersAsIterableViaQuery; + } + + public Iterable getTeamMembersAsIterableViaQuery() { + return teamMembersAsIterableViaQuery; + } + + public Set getTeamMembersAsSetViaQuery() { + return teamMembersAsSetViaQuery; + } + + public Set getFetchedTeamMembersAsSetViaQuery() { + return fetchedTeamMembersAsSetViaQuery; + } + } diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/model/Person.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/model/Person.java index 1ec43f0d7..3724a3f46 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/model/Person.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/model/Person.java @@ -87,21 +87,20 @@ public class Person implements Being , Serializable { @RelatedToVia(type = "knows", elementClass = Friendship.class) private Iterable friendships; - @Query("start person=node({self}) match (person)<-[?:boss]-(boss) return boss") + /* + Note: All @Query annotated fields should be transient so that they are not serialized + */ + @Query("start person=node({self}) OPTIONAL MATCH (person)<-[:boss]-(boss) return boss") transient private Person bossByQuery; - // NW - all queries should be transient - @Query("start person=node({self}) match (person)<-[?:boss]-(boss) return boss.name") + @Query("start person=node({self}) OPTIONAL MATCH (person)<-[:boss]-(boss) return boss.name") transient private String bossName; - // NW - all queries should be transient @Query("start person=node({self}) match (person)<-[:persons]-(team)-[:persons]->(member) return member") transient private Iterable otherTeamMembers; - // NW - all queries should be transient - @Query("start person=node({self}) match (person)<-[:persons]-(team)-[:persons]->(member) return member.name?, member.age?") + @Query("start person=node({self}) match (person)<-[:persons]-(team)-[:persons]->(member) return member.name, member.age") transient private Iterable> otherTeamMemberData; - // NW - all queries should be transient @RelatedTo(elementClass = Group.class, type = "interface_test", direction = Direction.OUTGOING) private Set groups; diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repositories/GroupRepository.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repositories/GroupRepository.java index 3318a576d..ec07bba20 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repositories/GroupRepository.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repositories/GroupRepository.java @@ -18,10 +18,14 @@ package org.springframework.data.neo4j.repositories; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.neo4j.annotation.Query; import org.springframework.data.neo4j.model.Group; +import org.springframework.data.neo4j.model.Person; import org.springframework.data.neo4j.repository.GraphRepository; import org.springframework.data.neo4j.repository.NamedIndexRepository; +import java.util.Set; + /** * @author mh @@ -30,4 +34,7 @@ import org.springframework.data.neo4j.repository.NamedIndexRepository; public interface GroupRepository extends GraphRepository, NamedIndexRepository { Iterable findByFullTextNameLike(String name); Page findByName(String name, Pageable page); + + @Query("match (group:g{name:{0}})-[:persons]->(aMember) return aMember") + Set getTeamMembersAsSetViaQuery(String groupName); } diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/MatrixTeam.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/MatrixTeam.java new file mode 100644 index 000000000..a0c33845b --- /dev/null +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/MatrixTeam.java @@ -0,0 +1,60 @@ +/** + * 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.repository; + +import org.springframework.data.neo4j.model.Friendship; +import org.springframework.data.neo4j.model.Group; +import org.springframework.data.neo4j.model.Person; +import org.springframework.data.neo4j.model.Personality; +import org.springframework.data.neo4j.template.Neo4jOperations; + +import java.util.Arrays; + +/** + * @author Nicki Watt + * @since 02.09.2013 + */ +public class MatrixTeam { + public Person neo; + public Person cypher; + public Person trinity; + public Group matrixGroup; + + public MatrixTeam() { + } + + public MatrixTeam createMatrixTeam(GraphRepository repo, GraphRepository groupRepo, GraphRepository friendshipRepository) { + cypher = new Person("Cypher", 30); + neo = new Person("Neo", 36); + neo.setPersonality(Personality.EXTROVERT); + neo.setLocation(16, 56); + + trinity = new Person("Trinity", 25); + trinity.setBoss(cypher); + trinity.setLocation( 16.5, 56.5 ); + matrixGroup = new Group(); + matrixGroup.setName("Matrix"); + matrixGroup.addPerson(neo); + matrixGroup.addPerson(cypher); + matrixGroup.addPerson(trinity); + + repo.save(Arrays.asList(cypher, trinity, neo)); + groupRepo.save(matrixGroup); + return this; + } + +} \ No newline at end of file diff --git a/spring-data-neo4j/src/test/resources/org/springframework/data/neo4j/entity/EntityQueryTests-context.xml b/spring-data-neo4j/src/test/resources/org/springframework/data/neo4j/entity/EntityQueryTests-context.xml new file mode 100644 index 000000000..759b7ece3 --- /dev/null +++ b/spring-data-neo4j/src/test/resources/org/springframework/data/neo4j/entity/EntityQueryTests-context.xml @@ -0,0 +1,13 @@ + + + + + + + \ No newline at end of file