From 6401f91c83c8800d26750fb32948653d220500a0 Mon Sep 17 00:00:00 2001 From: Andrey Kovalev Date: Sat, 24 Mar 2018 22:39:42 +0500 Subject: [PATCH] DATAJPA-1303 - Support ignoring case for finding in collections. Original pull request: #261. --- .../jpa/repository/query/JpaQueryCreator.java | 5 +- .../query/ParameterMetadataProvider.java | 34 +++++++++-- ...lipseLinkNamespaceUserRepositoryTests.java | 33 +++++++++++ .../jpa/repository/UserRepositoryTests.java | 57 +++++++++++++++++++ .../jpa/repository/sample/UserRepository.java | 10 ++++ 5 files changed, 133 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java index 58d9cba73..6575a4755 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java @@ -54,6 +54,7 @@ import org.springframework.util.Assert; * @author Mark Paluch * @author Reda.Housni-Alaoui * @author Moritz Becker + * @author Andrey Kovalev */ public class JpaQueryCreator extends AbstractQueryCreator, Predicate> { @@ -270,10 +271,10 @@ public class JpaQueryCreator extends AbstractQueryCreator>) provider.next(part, Collection.class).getExpression()).not(); + return upperIfIgnoreCase(getTypedPath(root, part)).in((Expression>) provider.next(part, Collection.class).getExpression()).not(); case IN: // cast required for eclipselink workaround, see DATAJPA-433 - return getTypedPath(root, part).in((Expression>) provider.next(part, Collection.class).getExpression()); + return upperIfIgnoreCase(getTypedPath(root, part)).in((Expression>) provider.next(part, Collection.class).getExpression()); case STARTING_WITH: case ENDING_WITH: case CONTAINING: diff --git a/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java b/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java index cba60a049..9d15df064 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java @@ -22,6 +22,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.function.Supplier; +import java.util.stream.Collectors; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.ParameterExpression; @@ -31,11 +32,13 @@ import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.data.repository.query.parser.Part; +import org.springframework.data.repository.query.parser.Part.IgnoreCaseType; import org.springframework.data.repository.query.parser.Part.Type; import org.springframework.expression.Expression; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; /** @@ -46,6 +49,7 @@ import org.springframework.util.ObjectUtils; * @author Mark Paluch * @author Christoph Strobl * @author Jens Schauder + * @author Andrey Kovalev */ class ParameterMetadataProvider { @@ -173,7 +177,7 @@ class ParameterMetadataProvider { Object value = bindableParameterValues == null ? ParameterMetadata.PLACEHOLDER : bindableParameterValues.next(); - ParameterMetadata metadata = new ParameterMetadata<>(expression, part.getType(), value, persistenceProvider, escape); + ParameterMetadata metadata = new ParameterMetadata<>(expression, part, value, persistenceProvider, escape); expressions.add(metadata); return metadata; @@ -186,6 +190,7 @@ class ParameterMetadataProvider { /** * @author Oliver Gierke * @author Thomas Darimont + * @author Andrey Kovalev * @param */ static class ParameterMetadata { @@ -196,16 +201,18 @@ class ParameterMetadataProvider { private final ParameterExpression expression; private final PersistenceProvider persistenceProvider; private final EscapeCharacter escape; + private final boolean ignoreCase; /** * Creates a new {@link ParameterMetadata}. */ - public ParameterMetadata(ParameterExpression expression, Type type, @Nullable Object value, + public ParameterMetadata(ParameterExpression expression, Part part, @Nullable Object value, PersistenceProvider provider, EscapeCharacter escape) { this.expression = expression; this.persistenceProvider = provider; - this.type = value == null && Type.SIMPLE_PROPERTY.equals(type) ? Type.IS_NULL : type; + this.type = value == null && Type.SIMPLE_PROPERTY.equals(part.getType()) ? Type.IS_NULL : part.getType(); + this.ignoreCase = IgnoreCaseType.ALWAYS.equals(part.shouldIgnoreCase()); this.escape = escape; } @@ -253,7 +260,7 @@ class ParameterMetadataProvider { } return Collection.class.isAssignableFrom(expressionType) // - ? persistenceProvider.potentiallyConvertEmptyCollection(toCollection(value)) // + ? persistenceProvider.potentiallyConvertEmptyCollection(upperIfIgnoreCase(ignoreCase, toCollection(value))) // : value; } @@ -282,5 +289,24 @@ class ParameterMetadataProvider { return Collections.singleton(value); } + + @Nullable + @SuppressWarnings("unchecked") + private static Collection upperIfIgnoreCase(boolean ignoreCase, @Nullable Collection + collection) { + if (ignoreCase + && !CollectionUtils.isEmpty(collection)) { + return ((Collection) collection).stream() + .map(it -> { + if (it == null) { + return null; + } else { + return it.toUpperCase(); + } + }) + .collect(Collectors.toList()); + } + return collection; + } } } diff --git a/src/test/java/org/springframework/data/jpa/repository/EclipseLinkNamespaceUserRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/EclipseLinkNamespaceUserRepositoryTests.java index 755133b78..f51ba2ce5 100644 --- a/src/test/java/org/springframework/data/jpa/repository/EclipseLinkNamespaceUserRepositoryTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/EclipseLinkNamespaceUserRepositoryTests.java @@ -34,6 +34,7 @@ import org.springframework.test.context.ContextConfiguration; * @author Thomas Darimont * @author Jens Schauder * @author Moritz Becker + * @author Andrey Kovalev */ @ContextConfiguration(value = "classpath:eclipselink.xml") public class EclipseLinkNamespaceUserRepositoryTests extends NamespaceUserRepositoryTests { @@ -134,4 +135,36 @@ public class EclipseLinkNamespaceUserRepositoryTests extends NamespaceUserReposi @Test @Ignore public void savingUserThrowsAnException() {} + + /** + * Ignored until https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477 is resolved. + */ + @Ignore + @Override + @Test // DATAJPA-1303 + public void findByElementCollectionInAttributeIgnoreCase() {} + + /** + * Ignored until https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477 is resolved. + */ + @Ignore + @Override + @Test // DATAJPA-1303 + public void findByElementCollectionNotInAttributeIgnoreCase() {} + + /** + * Ignored until https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477 is resolved. + */ + @Ignore + @Override + @Test // DATAJPA-1303 + public void findByElementVarargInAttributeIgnoreCase() {} + + /** + * Ignored until https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477 is resolved. + */ + @Ignore + @Override + @Test // DATAJPA-1303 + public void findByElementCollectionInAttributeIgnoreCaseWithNulls() {} } diff --git a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java index 7ac95f204..82fd9be02 100644 --- a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java @@ -89,6 +89,7 @@ import com.google.common.base.Optional; * @author Mark Paluch * @author Kevin Peters * @author Jens Schauder + * @author Andrey Kovalev */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:application-context.xml") @@ -2223,6 +2224,62 @@ public class UserRepositoryTests { repository.delete(new User()); } + @Test // DATAJPA-1303 + public void findByElementCollectionInAttributeIgnoreCase() { + + firstUser.getAttributes().add("cOOl"); + secondUser.getAttributes().add("hIp"); + thirdUser.getAttributes().add("roCKsTar"); + + flushTestUsers(); + + List result = repository.findByAttributesIgnoreCaseIn(new HashSet<>(Arrays.asList("cOOl", "hIP"))); + + assertThat(result).containsOnly(firstUser, secondUser); + } + + @Test // DATAJPA-1303 + public void findByElementCollectionNotInAttributeIgnoreCase() { + + firstUser.getAttributes().add("cOOl"); + secondUser.getAttributes().add("hIp"); + thirdUser.getAttributes().add("rOckStAr"); + + flushTestUsers(); + + List result = repository.findByAttributesIgnoreCaseNotIn(Arrays.asList("CooL", "HIp")); + + assertThat(result).containsOnly(thirdUser); + } + + @Test // DATAJPA-1303 + public void findByElementVarargInAttributeIgnoreCase() { + + firstUser.getAttributes().add("cOOl"); + secondUser.getAttributes().add("hIp"); + thirdUser.getAttributes().add("rOckStAr"); + + flushTestUsers(); + + Page result = repository.findByAttributesIgnoreCaseIn(PageRequest.of(0, 20), "CooL", "HIp"); + + assertThat(result).containsOnly(firstUser, secondUser); + } + + @Test // DATAJPA-1303 + public void findByElementCollectionInAttributeIgnoreCaseWithNulls() { + + firstUser.getAttributes().add("cOOl"); + secondUser.getAttributes().add("hIp"); + thirdUser.getAttributes().add("roCKsTar"); + + flushTestUsers(); + + List result = repository.findByAttributesIgnoreCaseIn(Arrays.asList("cOOl", null)); + + assertThat(result).containsOnly(firstUser); + } + private Page executeSpecWithSort(Sort sort) { flushTestUsers(); diff --git a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java index c61dd06c1..1ba992337 100644 --- a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java +++ b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java @@ -51,6 +51,7 @@ import com.google.common.base.Optional; * @author Thomas Darimont * @author Kevin Peters * @author Jeff Sheets + * @author Andrey Kovalev */ public interface UserRepository extends JpaRepository, JpaSpecificationExecutor, UserRepositoryCustom { @@ -591,6 +592,15 @@ public interface UserRepository @Query("select u from User u where u.lastname like %?#{escape([0])}% escape ?#{escapeCharacter()}") List findContainingEscaped(String namePart); + // DATAJPA-1303 + List findByAttributesIgnoreCaseIn(Collection attributes); + + // DATAJPA-1303 + List findByAttributesIgnoreCaseNotIn(Collection attributes); + + // DATAJPA-1303 + Page findByAttributesIgnoreCaseIn(Pageable pageable, String... attributes); + interface RolesAndFirstname { String getFirstname();