DATAGRAPH-257 Fixed QueryResultBuilder cannot be cast to java.util.Set in NodeEntity for SDN 3.1+ context

This commit is contained in:
Nicki Watt
2014-06-08 16:34:00 +01:00
committed by Michael Hunger
parent 60e639d0c5
commit 9ce0bcebed
12 changed files with 311 additions and 31 deletions

View File

@@ -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 <T, C extends Iterable<T>> C toContainer(Class<C> container, Iterable<T> 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<T>());
if (Set.class.isAssignableFrom(container)) return (C) IteratorUtil.addToCollection(data, new HashSet<T>());

View File

@@ -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

View File

@@ -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<? extends Iterable> collectionType = (Class<? extends Iterable>) typeInformation.getType();
return result.to(targetType).as(collectionType);
}
return result.to(targetType).singleOrNull();
}

View File

@@ -149,19 +149,10 @@ public class GraphQueryMethod extends QueryMethod {
return new CypherGraphRepositoryQuery(this, template);
}
public boolean isSetResult() {
final Class<Set> 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;

View File

@@ -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<? extends Iterable> collectionType = (Class<? extends Iterable>) 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<String, Object> params) {

View File

@@ -334,8 +334,18 @@ class Neo4jPersistentPropertyImpl extends AnnotationBasedPersistentProperty<Neo4
}
public boolean isTransient() {
if (super.isTransient()) {
boolean primaryIsTransient = super.isTransient();
// DATAGRAPH-257
// We need to ignore @Query annotated fields for the moment. Entities should be able
// to be marked with the Java transient modified so that they are not serialized,
// however the still need @Query functionality to be able to execute and this will
// not be done if the property is marked as transient
if (field != null && field.isAnnotationPresent(Query.class)) {
return false;
}
if (primaryIsTransient) {
return true;
}

View File

@@ -0,0 +1,157 @@
package org.springframework.data.neo4j.entity;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.neo4j.model.Group;
import org.springframework.data.neo4j.model.Person;
import org.springframework.data.neo4j.repositories.FriendshipRepository;
import org.springframework.data.neo4j.repositories.GroupRepository;
import org.springframework.data.neo4j.repositories.PersonRepository;
import org.springframework.data.neo4j.repository.*;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.data.neo4j.support.node.Neo4jHelper;
import org.springframework.test.annotation.DirtiesContext;
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.BeforeTransaction;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import java.util.Collection;
import java.util.Set;
import static junit.framework.Assert.assertNull;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertNotNull;
import static org.neo4j.helpers.collection.IteratorUtil.asCollection;
/**
* These tests are all focused on testing @Query annotated fields on entities.
*
* @author Nicki Watt
* @since 08.06.2014
*/
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
public class EntityQueryTests {
@Autowired
private Neo4jTemplate neo4jTemplate;
@Autowired
private PersonRepository personRepository;
@Autowired
GroupRepository groupRepository;
@Autowired
FriendshipRepository friendshipRepository;
@Autowired
private PlatformTransactionManager transactionManager;
private TestTeam testTeam;
private MatrixTeam matrixTeam;
@BeforeTransaction
public void cleanDb() {
Neo4jHelper.cleanDb(neo4jTemplate);
}
@Before
public void setUp() throws Exception {
testTeam = new TestTeam();
matrixTeam = new MatrixTeam();
new TransactionTemplate(transactionManager).execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
personRepository.deleteAll();
groupRepository.deleteAll();
friendshipRepository.deleteAll();
testTeam.createSDGTeam(personRepository, groupRepository, friendshipRepository);
matrixTeam.createMatrixTeam(personRepository, groupRepository, friendshipRepository);
}
});
}
// DATAGRAPH-257
@Transactional
@Test
public void testPropertyQueryOfTypeSetPopulatedCorrectly() {
Group grp = neo4jTemplate.findOne(matrixTeam.matrixGroup.getId(), Group.class);
Set<Person> 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<Person> itMembers = grp.getTeamMembersAsIterableViaQuery();
assertNotNull(itMembers);
Collection<Person> 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<Person> 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<Person> itMembers = grp.getFetchedTeamMembersAsIterableViaQuery();
Collection<Person> 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<Person> members = grp.getFetchedTeamMembersAsSetViaQuery();
assertExpectionsOfMatrixTeamSetResult(members);
}
private void assertExpectionsOfMatrixTeamSetResult(Collection<Person> 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());
}
}

View File

@@ -230,4 +230,39 @@ public class Group implements IGroup , Serializable {
public Collection<Person> 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<Person> teamMembersAsSetViaQuery;
@Query("match (g3:g)-[:persons]->(member) where id(g3) = {self} return member")
private transient Iterable<Person> teamMembersAsIterableViaQuery;
//@Fetch //(breaks Neo4jEntityConverterTests - investigate)
@Query("match (g4)-[:persons]->(member) where id(g4) = {self} return member")
private transient Iterable<Person> fetchedTeamMembersAsIterableViaQuery;
//@Fetch //(breaks Neo4jEntityConverterTests - investigate)
@Query("match (g1)-[:persons]-(member1) where id(g1) = {self} return member1")
private transient Set<Person> fetchedTeamMembersAsSetViaQuery;
public Iterable<Person> getFetchedTeamMembersAsIterableViaQuery() {
return fetchedTeamMembersAsIterableViaQuery;
}
public Iterable<Person> getTeamMembersAsIterableViaQuery() {
return teamMembersAsIterableViaQuery;
}
public Set<Person> getTeamMembersAsSetViaQuery() {
return teamMembersAsSetViaQuery;
}
public Set<Person> getFetchedTeamMembersAsSetViaQuery() {
return fetchedTeamMembersAsSetViaQuery;
}
}

View File

@@ -87,21 +87,20 @@ public class Person implements Being , Serializable {
@RelatedToVia(type = "knows", elementClass = Friendship.class)
private Iterable<Friendship> 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<Person> 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<Map<String,Object>> otherTeamMemberData;
// NW - all queries should be transient
@RelatedTo(elementClass = Group.class, type = "interface_test", direction = Direction.OUTGOING)
private Set<IGroup> groups;

View File

@@ -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<Group>, NamedIndexRepository<Group> {
Iterable<Group> findByFullTextNameLike(String name);
Page<Group> findByName(String name, Pageable page);
@Query("match (group:g{name:{0}})-[:persons]->(aMember) return aMember")
Set<Person> getTeamMembersAsSetViaQuery(String groupName);
}

View File

@@ -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<Person> repo, GraphRepository<Group> groupRepo, GraphRepository<Friendship> 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;
}
}

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:neo4j="http://www.springframework.org/schema/data/neo4j"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/data/neo4j http://www.springframework.org/schema/data/neo4j/spring-neo4j.xsd">
<context:annotation-config/>
<neo4j:config graphDatabaseService="graphDatabaseService" base-package="org.springframework.data.neo4j.model"/>
<neo4j:repositories base-package="org.springframework.data.neo4j.repositories"/>
<bean id="graphDatabaseService" class="org.neo4j.test.ImpermanentGraphDatabase" destroy-method="shutdown"/>
</beans>