diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/derived/CypherFinderQuery.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/derived/CypherFinderQuery.java index 75108542d..ca529f491 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/derived/CypherFinderQuery.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/derived/CypherFinderQuery.java @@ -1,5 +1,5 @@ /* - * Copyright (c) [2011-2016] "Pivotal Software, Inc." / "Neo Technology" / "Graph Aware Ltd." + * Copyright (c) [2011-2017] "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. @@ -12,11 +12,22 @@ */ package org.springframework.data.neo4j.repository.query.derived; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Stack; import org.neo4j.ogm.cypher.BooleanOperator; import org.neo4j.ogm.cypher.Filter; -import org.springframework.data.neo4j.repository.query.derived.builder.*; +import org.springframework.data.neo4j.repository.query.derived.builder.BetweenComparisonBuilder; +import org.springframework.data.neo4j.repository.query.derived.builder.BooleanComparisonBuilder; +import org.springframework.data.neo4j.repository.query.derived.builder.ContainsComparisonBuilder; +import org.springframework.data.neo4j.repository.query.derived.builder.DistanceComparisonBuilder; +import org.springframework.data.neo4j.repository.query.derived.builder.ExistsFilterBuilder; +import org.springframework.data.neo4j.repository.query.derived.builder.FilterBuilder; +import org.springframework.data.neo4j.repository.query.derived.builder.IsNullFilterBuilder; +import org.springframework.data.neo4j.repository.query.derived.builder.PropertyComparisonBuilder; import org.springframework.data.repository.query.parser.Part; /** @@ -25,6 +36,7 @@ import org.springframework.data.repository.query.parser.Part; * @author Luanne Misquitta * @author Jasper Blues * @author Nicolas Mervaillie + * @author Gerrit Meier */ public class CypherFinderQuery implements DerivedQueryDefinition { @@ -76,7 +88,10 @@ public class CypherFinderQuery implements DerivedQueryDefinition { return new DistanceComparisonBuilder(part, booleanOperator, entityType); case BETWEEN: return new BetweenComparisonBuilder(part, booleanOperator, entityType); - case IS_NULL: + case NOT_CONTAINING: + case CONTAINING: + return resolveMatchingContainsFilterBuilder(part, booleanOperator); + case IS_NULL: case IS_NOT_NULL: return new IsNullFilterBuilder(part, booleanOperator, entityType); case EXISTS: @@ -88,4 +103,12 @@ public class CypherFinderQuery implements DerivedQueryDefinition { return new PropertyComparisonBuilder(part, booleanOperator, entityType); } } + + private FilterBuilder resolveMatchingContainsFilterBuilder(Part part, BooleanOperator booleanOperator) { + boolean usePropertyComparison = !part.getProperty().getTypeInformation().isCollectionLike(); + if (usePropertyComparison) { + return new PropertyComparisonBuilder(part, booleanOperator, entityType); + } + return new ContainsComparisonBuilder(part, booleanOperator, entityType); + } } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/derived/builder/ContainsComparisonBuilder.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/derived/builder/ContainsComparisonBuilder.java new file mode 100644 index 000000000..4aebb0b8b --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/derived/builder/ContainsComparisonBuilder.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) [2011-2017] "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.repository.query.derived.builder; + +import java.util.Collections; +import java.util.List; +import java.util.Stack; + +import org.neo4j.ogm.cypher.BooleanOperator; +import org.neo4j.ogm.cypher.ComparisonOperator; +import org.neo4j.ogm.cypher.Filter; +import org.neo4j.ogm.cypher.function.ContainsAnyComparison; +import org.springframework.data.repository.query.parser.Part; + +/** + * Filter for entities having a collection like property (not) containing a given element. + * + * @author Gerrit Meier + */ +public class ContainsComparisonBuilder extends FilterBuilder { + + public ContainsComparisonBuilder(Part part, BooleanOperator booleanOperator, Class entityType) { + super(part, booleanOperator, entityType); + } + + @Override + public List build(Stack params) { + final Object containingValue = params.pop(); + Filter containingFilter = new Filter(propertyName(), ComparisonOperator.IN, containingValue); + containingFilter.setOwnerEntityType(entityType); + containingFilter.setBooleanOperator(booleanOperator); + containingFilter.setNegated(isNegated()); + containingFilter.setFunction(new ContainsAnyComparison(containingValue)); + setNestedAttributes(part, containingFilter); + + return Collections.singletonList(containingFilter); + } + +} diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/MoviesIntegrationTests.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/MoviesIntegrationTests.java index 33f063d3c..e73c18c7a 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/MoviesIntegrationTests.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/MoviesIntegrationTests.java @@ -16,7 +16,15 @@ package org.springframework.data.neo4j.examples.movies; import static org.junit.Assert.*; import static org.neo4j.ogm.testutil.GraphTestUtils.*; -import java.util.*; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.TimeZone; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -33,14 +41,26 @@ import org.neo4j.ogm.session.SessionFactory; import org.neo4j.ogm.testutil.MultiDriverTestClass; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - 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.DataAccessException; -import org.springframework.data.neo4j.examples.movies.domain.*; -import org.springframework.data.neo4j.examples.movies.repo.*; +import org.springframework.data.neo4j.examples.movies.domain.Actor; +import org.springframework.data.neo4j.examples.movies.domain.Cinema; +import org.springframework.data.neo4j.examples.movies.domain.Genre; +import org.springframework.data.neo4j.examples.movies.domain.Movie; +import org.springframework.data.neo4j.examples.movies.domain.Rating; +import org.springframework.data.neo4j.examples.movies.domain.ReleasedMovie; +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.repo.AbstractAnnotatedEntityRepository; +import org.springframework.data.neo4j.examples.movies.repo.AbstractEntityRepository; +import org.springframework.data.neo4j.examples.movies.repo.ActorRepository; +import org.springframework.data.neo4j.examples.movies.repo.CinemaRepository; +import org.springframework.data.neo4j.examples.movies.repo.RatingRepository; +import org.springframework.data.neo4j.examples.movies.repo.TempMovieRepository; +import org.springframework.data.neo4j.examples.movies.repo.UserRepository; import org.springframework.data.neo4j.examples.movies.service.UserService; import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories; import org.springframework.data.neo4j.transaction.Neo4jTransactionManager; @@ -59,11 +79,15 @@ import org.springframework.transaction.support.TransactionTemplate; * @author Mark Angrish * @author Mark Paluch * @author Jens Schauder + * @author Gerrit Meier */ @ContextConfiguration(classes = {MoviesIntegrationTests.MoviesContext.class}) @RunWith(SpringJUnit4ClassRunner.class) public class MoviesIntegrationTests extends MultiDriverTestClass { + private static final String KNOWN_MAIL_ADDRESS_1 = "a@example.org"; + private static final String KNOWN_MAIL_ADDRESS_2 = "b@example.org"; + private static final String UNKNOWN_MAIL_ADDRESS = "c@example.org"; private final Logger logger = LoggerFactory.getLogger(MoviesIntegrationTests.class); @Autowired @@ -650,6 +674,62 @@ public class MoviesIntegrationTests extends MultiDriverTestClass { } } + /** + * @see DATAGRAPH-992 + */ + @Test + public void findUserByContainingEmailAddresses() { + createUserForContainsTest(); + + User foundUser = userRepository.findByEmailAddressesContains(Collections.singletonList(KNOWN_MAIL_ADDRESS_1)); + assertNotNull(foundUser); + + foundUser = userRepository.findByEmailAddressesContains(Arrays.asList(KNOWN_MAIL_ADDRESS_2, UNKNOWN_MAIL_ADDRESS)); + assertNotNull(foundUser); + } + + /** + * @see DATAGRAPH-992 + */ + @Test + public void findNoUserByContainingEmailAddresses() { + createUserForContainsTest(); + + User foundUser = userRepository.findByEmailAddressesContains(Collections.singletonList(UNKNOWN_MAIL_ADDRESS)); + assertNull(foundUser); + } + + /** + * @see DATAGRAPH-992 + */ + @Test + public void findUserByNotContainingEmailAddresses() { + createUserForContainsTest(); + + List foundUser = userRepository.findByEmailAddressesNotContaining(UNKNOWN_MAIL_ADDRESS); + assertNotNull(foundUser.get(0)); + } + + /** + * @see DATAGRAPH-992 + */ + @Test + public void findNoUserByNotContainingEmailAddresses() { + createUserForContainsTest(); + + List foundUser = userRepository.findByEmailAddressesNotContaining(KNOWN_MAIL_ADDRESS_1); + assertTrue(foundUser.isEmpty()); + } + + private void createUserForContainsTest() { + User user = new User("Somebody"); + Set emailAddresses = new HashSet<>(); + emailAddresses.add(KNOWN_MAIL_ADDRESS_1); + emailAddresses.add(KNOWN_MAIL_ADDRESS_2); + user.setEmailAddresses(emailAddresses); + userRepository.save(user); + } + private Calendar createDate(int y, int m, int d, String tz) { Calendar calendar = Calendar.getInstance(); diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/domain/User.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/domain/User.java index 9fe180750..14f293bc9 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/domain/User.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/domain/User.java @@ -1,5 +1,5 @@ /* - * Copyright (c) [2011-2016] "Pivotal Software, Inc." / "Neo Technology" / "Graph Aware Ltd." + * Copyright (c) [2011-2017] "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. @@ -22,6 +22,7 @@ import java.util.Set; /** * @author Michal Bachman * @author Luanne Misquitta + * @author Gerrit Meier */ public class User extends Person{ @@ -36,7 +37,10 @@ public class User extends Person{ @Relationship(type = "RATED") private Set ratings = new HashSet<>(); - public User() { + private Set emailAddresses; + + + public User() { } public User(String name) { @@ -89,4 +93,12 @@ public class User extends Person{ public String getSurname() { return surname; } + + public Set getEmailAddresses() { + return emailAddresses; + } + + public void setEmailAddresses(Set emailAddresses) { + this.emailAddresses = emailAddresses; + } } diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/repo/UserRepository.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/repo/UserRepository.java index 1f2afab72..49612a942 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/repo/UserRepository.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/repo/UserRepository.java @@ -1,5 +1,5 @@ /* - * Copyright (c) [2011-2016] "Pivotal Software, Inc." / "Neo Technology" / "Graph Aware Ltd." + * Copyright (c) [2011-2017] "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. @@ -34,6 +34,7 @@ import org.springframework.stereotype.Repository; /** * @author Michal Bachman * @author Luanne Misquitta + * @author Gerrit Meier */ @Repository public interface UserRepository extends PersonRepository { @@ -127,4 +128,8 @@ public interface UserRepository extends PersonRepository { @Query("invalid") void invalidQuery(); + + User findByEmailAddressesContains(List emails); + + List findByEmailAddressesNotContaining(String email); }