/*
* Copyright (c) [2011-2016] "Pivotal Software, Inc." / "Neo Technology" / "Graph Aware Ltd."
*
* This product is licensed to you under the Apache License, Version 2.0 (the "License").
* You may not use this product except in compliance with the License.
*
* This product may include a number of subcomponents with
* separate copyright notices and license terms. Your use of the source
* code for these subcomponents is subject to the terms and
* conditions of the subcomponent's license, as noted in the LICENSE file.
*
*/
package org.springframework.data.neo4j.queries;
import static org.junit.Assert.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.neo4j.ogm.session.SessionFactory;
import org.neo4j.ogm.testutil.MultiDriverTestClass;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.neo4j.examples.movies.domain.Rating;
import org.springframework.data.neo4j.examples.movies.domain.TempMovie;
import org.springframework.data.neo4j.examples.movies.domain.User;
import org.springframework.data.neo4j.examples.movies.domain.queryresult.*;
import org.springframework.data.neo4j.examples.movies.repo.CinemaRepository;
import org.springframework.data.neo4j.examples.movies.repo.UnmanagedUserPojo;
import org.springframework.data.neo4j.examples.movies.repo.UserRepository;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
import org.springframework.data.neo4j.transaction.Neo4jTransactionManager;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
/**
* @author Vince Bickers
* @author Luanne Misquitta
* @author Mark Angrish
*/
@ContextConfiguration(classes = {QueryIntegrationTests.MoviesContext.class})
@RunWith(SpringJUnit4ClassRunner.class)
public class QueryIntegrationTests extends MultiDriverTestClass {
@Autowired
PlatformTransactionManager platformTransactionManager;
@Autowired
private UserRepository userRepository;
@Autowired
private CinemaRepository cinemaRepository;
private TransactionTemplate transactionTemplate;
@Before
public void clearDatabase() {
transactionTemplate = new TransactionTemplate(platformTransactionManager);
getGraphDatabaseService().execute("MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE r, n");
}
private void executeUpdate(String cypher) {
getGraphDatabaseService().execute(cypher);
}
@Test
public void shouldFindArbitraryGraph() {
executeUpdate(
"CREATE " +
"(dh:Movie {name:'Die Hard'}), " +
"(fe:Movie {name: 'The Fifth Element'}), " +
"(bw:User {name: 'Bruce Willis'}), " +
"(ar:User {name: 'Alan Rickman'}), " +
"(mj:User {name: 'Milla Jovovich'}), " +
"(mj)-[:ACTED_IN]->(fe), " +
"(ar)-[:ACTED_IN]->(dh), " +
"(bw)-[:ACTED_IN]->(dh), " +
"(bw)-[:ACTED_IN]->(fe)");
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
List<Map<String, Object>> graph = userRepository.getGraph();
assertNotNull(graph);
int i = 0;
for (Map<String, Object> properties : graph) {
i++;
assertNotNull(properties);
}
assertEquals(2, i);
}
});
}
@Test
public void shouldFindScalarValues() {
executeUpdate("CREATE (m:User {name:'Michal'})<-[:FRIEND_OF]-(a:User {name:'Adam'})");
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
List<Integer> ids = userRepository.getUserIds();
assertEquals(2, ids.size());
List<Long> nodeIds = userRepository.getUserNodeIds();
assertEquals(2, nodeIds.size());
}
});
}
@Test
public void shouldFindUserByName() {
executeUpdate("CREATE (m:User {name:'Michal'})<-[:FRIEND_OF]-(a:User {name:'Adam'})");
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
User user = userRepository.findUserByName("Michal");
assertEquals("Michal", user.getName());
}
});
}
@Test
public void shouldFindTotalUsers() {
executeUpdate("CREATE (m:User {name:'Michal'})<-[:FRIEND_OF]-(a:User {name:'Adam'})");
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
int users = userRepository.findTotalUsers();
assertEquals(users, 2);
}
});
}
@Test
public void shouldFindUsers() {
executeUpdate("CREATE (m:User {name:'Michal'})<-[:FRIEND_OF]-(a:User {name:'Adam'})");
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
Collection<User> users = userRepository.getAllUsers();
assertEquals(users.size(), 2);
}
});
}
@Test
public void shouldFindUserByNameWithNamedParam() {
executeUpdate("CREATE (m:User {name:'Michal'})<-[:FRIEND_OF]-(a:User {name:'Adam'})");
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
User user = userRepository.findUserByNameWithNamedParam("Michal");
assertEquals("Michal", user.getName());
}
});
}
@Test
public void shouldFindUsersAsProperties() {
executeUpdate("CREATE (m:User {name:'Michal'})<-[:FRIEND_OF]-(a:User {name:'Adam'})");
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
Iterable<Map<String, Object>> users = userRepository.getUsersAsProperties();
assertNotNull(users);
int i = 0;
for (Map<String, Object> properties : users) {
i++;
assertNotNull(properties);
}
assertEquals(2, i);
}
});
}
/**
* @see DATAGRAPH-698, DATAGRAPH-861
*/
@Test
public void shouldFindUsersAndMapThemToConcreteQueryResultObjectCollection() {
executeUpdate("CREATE (g:User {name:'Gary', age:32}), (s:User {name:'Sheila', age:29}), (v:User {name:'Vince', age:66})");
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
assertEquals("There should be some users in the database", 3, userRepository.findTotalUsers());
Iterable<UserQueryResult> expected = Arrays.asList(new UserQueryResult("Sheila", 29),
new UserQueryResult("Gary", 32), new UserQueryResult("Vince", 66));
Iterable<UserQueryResult> queryResult = userRepository.retrieveAllUsersAndTheirAges();
assertNotNull("The query result shouldn't be null", queryResult);
assertEquals(expected, queryResult);
for (UserQueryResult userQueryResult : queryResult) {
assertNotNull(userQueryResult.getUserId());
assertNotNull(userQueryResult.getId());
}
}
});
}
/**
* This limitation about not handling unmanaged types may be addressed after M2 if there's demand for it.
*/
@Test(expected = InvalidDataAccessApiUsageException.class)
public void shouldThrowMappingExceptionIfQueryResultTypeIsNotManagedInMappingMetadata() {
executeUpdate("CREATE (:User {name:'Colin'}), (:User {name:'Jeff'})");
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
// NB: UnmanagedUserPojo is not scanned with the other domain classes
UnmanagedUserPojo queryResult = userRepository.findIndividualUserAsDifferentObject("Jeff");
assertNotNull("The query result shouldn't be null", queryResult);
assertEquals("Jeff", queryResult.getName());
}
});
}
@Test
public void shouldFindUsersAndMapThemToProxiedQueryResultInterface() {
executeUpdate("CREATE (:User {name:'Morne', age:30}), (:User {name:'Abraham', age:31}), (:User {name:'Virat', age:27})");
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
UserQueryResultObject result = userRepository.findIndividualUserAsProxiedObject("Abraham");
assertNotNull("The query result shouldn't be null", result);
assertEquals("The wrong user was returned", "Abraham", result.getName());
assertEquals("The wrong user was returned", 31, result.getAgeOfUser());
}
});
}
@Test
public void shouldRetrieveUsersByGenderAndConvertToCorrectTypes() {
executeUpdate("CREATE (:User {name:'David Warner', gender:'MALE'}), (:User {name:'Shikhar Dhawan', gender:'MALE'}), "
+ "(:User {name:'Sarah Taylor', gender:'FEMALE', account: '3456789', deposits:['12345.6','45678.9']})");
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
Iterable<RichUserQueryResult> usersByGender = userRepository.findUsersByGender(Gender.FEMALE);
assertNotNull("The resultant users list shouldn't be null", usersByGender);
Iterator<RichUserQueryResult> userIterator = usersByGender.iterator();
assertTrue(userIterator.hasNext());
RichUserQueryResult userQueryResult = userIterator.next();
assertEquals(Gender.FEMALE, userQueryResult.getUserGender());
assertEquals("Sarah Taylor", userQueryResult.getUserName());
assertEquals(BigInteger.valueOf(3456789), userQueryResult.getUserAccount());
assertArrayEquals(new BigDecimal[]{BigDecimal.valueOf(12345.6), BigDecimal.valueOf(45678.9)}, userQueryResult.getUserDeposits());
assertFalse(userIterator.hasNext());
}
});
}
/**
* @see DATAGRAPH-694
*/
@Test
public void shouldSubstituteUserId() {
executeUpdate("CREATE (m:User {name:'Michal'})<-[:FRIEND_OF]-(a:User {name:'Adam'})");
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
User michal = userRepository.findUserByName("Michal");
assertNotNull(michal);
User user = userRepository.loadUserById(michal);
assertEquals("Michal", user.getName());
}
});
}
/**
* @see DATAGRAPH-694
*/
@Test
public void shouldSubstituteNamedParamUserId() {
executeUpdate("CREATE (m:User {name:'Michal'})<-[:FRIEND_OF]-(a:User {name:'Adam'})");
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
User michal = userRepository.findUserByName("Michal");
assertNotNull(michal);
User user = userRepository.loadUserByNamedId(michal);
assertEquals("Michal", user.getName());
}
});
}
/**
* @see DATAGRAPH-727
*/
@Test
public void shouldFindIterableUsers() {
executeUpdate("CREATE (m:User {name:'Michal'})<-[:FRIEND_OF]-(a:User {name:'Adam'})");
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
Iterable<User> users = userRepository.getAllUsersIterable();
int count = 0;
for (User user : users) {
count++;
}
assertEquals(2, count);
}
});
}
/**
* @see DATAGRAPH-772
*/
@Test
public void shouldAllowNullParameters() {
executeUpdate("CREATE (m:User {name:'Michal'})<-[:FRIEND_OF]-(a:User {name:'Adam'})");
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
userRepository.setNamesNull(null);
Iterable<User> users = userRepository.findAll();
for (User u : users) {
assertNull(u.getName());
}
}
});
}
/**
* @see DATAGRAPH-772
*/
@Test
public void shouldMapNullsToQueryResults() {
executeUpdate("CREATE (g:User), (s:User)");
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
assertEquals("There should be some users in the database", 2, userRepository.findTotalUsers());
Iterable<UserQueryResult> expected = Arrays.asList(new UserQueryResult(null, 0),
new UserQueryResult(null, 0));
Iterable<UserQueryResult> queryResult = userRepository.retrieveAllUsersAndTheirAges();
assertNotNull("The query result shouldn't be null", queryResult);
assertEquals(expected, queryResult);
for (UserQueryResult userQueryResult : queryResult) {
assertNotNull(userQueryResult.getUserId());
}
}
});
}
/**
* @see DATAGRAPH-700
*/
@Test
public void shouldMapNodeEntitiesIntoQueryResultObjects() {
executeUpdate("CREATE (:User {name:'Abraham'}), (:User {name:'Barry'}), (:User {name:'Colin'})");
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
EntityWrappingQueryResult wrappedUser = userRepository.findWrappedUserByName("Barry");
assertNotNull("The loaded wrapper object shouldn't be null", wrappedUser);
assertNotNull("The enclosed user shouldn't be null", wrappedUser.getUser());
assertEquals("Barry", wrappedUser.getUser().getName());
}
});
}
/**
* @see DATAGRAPH-700
*/
@Test
public void shouldMapNodeCollectionsIntoQueryResultObjects() {
executeUpdate("CREATE (d:User {name:'Daniela'}), (e:User {name:'Ethan'}), (f:User {name:'Finn'}), (d)-[:FRIEND_OF]->(e), (d)-[:FRIEND_OF]->(f)");
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
EntityWrappingQueryResult result = userRepository.findWrappedUserAndFriendsDepth1("Daniela");
assertNotNull("The result shouldn't be null", result);
assertNotNull("The enclosed user shouldn't be null", result.getUser());
assertEquals("Daniela", result.getUser().getName());
assertEquals(2, result.getFriends().size());
List<String> friends = new ArrayList<>();
for (User u : result.getFriends()) {
friends.add(u.getName());
}
assertTrue(friends.contains("Ethan"));
assertTrue(friends.contains("Finn"));
assertEquals(2, result.getUser().getFriends().size()); //we expect friends to be mapped since the relationships were returned
}
});
}
/**
* @see DATAGRAPH-700
*/
@Test
public void shouldMapRECollectionsIntoQueryResultObjects() {
executeUpdate("CREATE (g:User {name:'Gary'}), (sw:Movie {name: 'Star Wars: The Force Awakens'}), (hob:Movie {name:'The Hobbit: An Unexpected Journey'}), (g)-[:RATED {stars : 5}]->(sw), (g)-[:RATED {stars: 4}]->(hob) ");
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
EntityWrappingQueryResult result = userRepository.findWrappedUserAndRatingsByName("Gary");
assertNotNull("The loaded wrapper object shouldn't be null", result);
assertNotNull("The enclosed user shouldn't be null", result.getUser());
assertEquals("Gary", result.getUser().getName());
assertEquals(2, result.getRatings().size());
for (Rating rating : result.getRatings()) {
if (rating.getStars() == 4) {
assertEquals("The Hobbit: An Unexpected Journey", rating.getMovie().getName());
} else {
assertEquals("Star Wars: The Force Awakens", rating.getMovie().getName());
}
}
assertEquals(4.5f, result.getAvgRating(), 0);
assertEquals(2, result.getMovies().length);
List<String> titles = new ArrayList<>();
for (TempMovie movie : result.getMovies()) {
titles.add(movie.getName());
}
assertTrue(titles.contains("The Hobbit: An Unexpected Journey"));
assertTrue(titles.contains("Star Wars: The Force Awakens"));
}
});
}
/**
* @see DATAGRAPH-700
*/
@Test
public void shouldMapRelationshipCollectionsWithDepth0IntoQueryResultObjects() {
executeUpdate("CREATE (i:User {name:'Ingrid'}), (j:User {name:'Jake'}), (k:User {name:'Kate'}), (i)-[:FRIEND_OF]->(j), (i)-[:FRIEND_OF]->(k)");
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
EntityWrappingQueryResult result = userRepository.findWrappedUserAndFriendsDepth0("Ingrid");
assertNotNull("The result shouldn't be null", result);
assertNotNull("The enclosed user shouldn't be null", result.getUser());
assertEquals("Ingrid", result.getUser().getName());
assertEquals(2, result.getFriends().size());
List<String> friends = new ArrayList<>();
for (User u : result.getFriends()) {
friends.add(u.getName());
}
assertTrue(friends.contains("Kate"));
assertTrue(friends.contains("Jake"));
assertEquals(0, result.getUser().getFriends().size()); //we do not expect friends to be mapped since the relationships were not returned
}
});
}
/**
* @see DATAGRAPH-700
*/
@Test
public void shouldReturnMultipleQueryResultObjects() {
executeUpdate("CREATE (g:User {name:'Gary'}), (h:User {name:'Harry'}), (sw:Movie {name: 'Star Wars: The Force Awakens'}), (hob:Movie {name:'The Hobbit: An Unexpected Journey'}), (g)-[:RATED {stars : 5}]->(sw), (g)-[:RATED {stars: 4}]->(hob), (h)-[:RATED {stars: 3}]->(hob) ");
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
List<EntityWrappingQueryResult> results = userRepository.findAllUserRatings();
assertEquals(2, results.size());
EntityWrappingQueryResult result = results.get(0);
assertNotNull("The loaded wrapper object shouldn't be null", result);
assertNotNull("The enclosed user shouldn't be null", result.getUser());
assertEquals("Harry", result.getUser().getName());
assertEquals(1, result.getRatings().size());
Rating rating = result.getRatings().get(0);
assertEquals("The Hobbit: An Unexpected Journey", rating.getMovie().getName());
assertEquals(3, rating.getStars());
assertEquals(3f, result.getAvgRating(), 0);
assertEquals(1, result.getMovies().length);
assertEquals("The Hobbit: An Unexpected Journey", result.getMovies()[0].getName());
result = results.get(1);
assertNotNull("The loaded wrapper object shouldn't be null", result);
assertNotNull("The enclosed user shouldn't be null", result.getUser());
assertEquals("Gary", result.getUser().getName());
for (Rating r : result.getRatings()) {
if (r.getStars() == 4) {
assertEquals("The Hobbit: An Unexpected Journey", r.getMovie().getName());
} else {
assertEquals("Star Wars: The Force Awakens", r.getMovie().getName());
}
}
assertEquals(4.5f, result.getAvgRating(), 0);
assertEquals(2, result.getMovies().length);
List<String> titles = new ArrayList<>();
for (TempMovie movie : result.getMovies()) {
titles.add(movie.getName());
}
assertTrue(titles.contains("The Hobbit: An Unexpected Journey"));
assertTrue(titles.contains("Star Wars: The Force Awakens"));
}
});
}
/**
* @see DATAGRAPH-700
*/
@Test
public void shouldMapEntitiesToProxiedQueryResultInterface() {
executeUpdate("CREATE (:User {name:'Morne', age:30}), (:User {name:'Abraham', age:31}), (:User {name:'Virat', age:27})");
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
UserQueryResultObject result = userRepository.findWrappedUserAsProxiedObject("Abraham");
assertNotNull("The query result shouldn't be null", result);
assertNotNull("The mapped user shouldn't be null", result.getUser());
assertEquals("The wrong user was returned", "Abraham", result.getUser().getName());
assertEquals("The wrong user was returned", 31, result.getAgeOfUser());
}
});
}
/**
* @see DATAGRAPH-860
*/
@Test
public void shouldMapEmptyNullCollectionsToQueryResultInterface() {
executeUpdate("CREATE (g:User {name:'Gary'})");
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
EntityWrappingQueryResult result = userRepository.findAllRatingsNull();
assertNotNull(result);
assertEquals(0, result.getAllRatings().size());
}
});
}
@Configuration
@ComponentScan({"org.springframework.data.neo4j.examples.movies.service"})
@EnableNeo4jRepositories("org.springframework.data.neo4j.examples.movies.repo")
@EnableTransactionManagement
static class MoviesContext {
@Bean
public PlatformTransactionManager transactionManager() {
return new Neo4jTransactionManager(sessionFactory());
}
@Bean
public SessionFactory sessionFactory() {
return new SessionFactory(getBaseConfiguration().build(), "org.springframework.data.neo4j.examples.movies.domain");
}
}
}