DATAJPA-1303 - Support ignoring case for finding in collections.

Original pull request: #261.
This commit is contained in:
Andrey Kovalev
2018-03-24 22:39:42 +05:00
committed by Jens Schauder
parent 6765db0e4e
commit 6401f91c83
5 changed files with 133 additions and 6 deletions

View File

@@ -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<CriteriaQuery<? extends Object>, Predicate> {
@@ -270,10 +271,10 @@ public class JpaQueryCreator extends AbstractQueryCreator<CriteriaQuery<? extend
return getTypedPath(root, part).isNotNull();
case NOT_IN:
// cast required for eclipselink workaround, see DATAJPA-433
return getTypedPath(root, part).in((Expression<Collection<?>>) provider.next(part, Collection.class).getExpression()).not();
return upperIfIgnoreCase(getTypedPath(root, part)).in((Expression<Collection<?>>) provider.next(part, Collection.class).getExpression()).not();
case IN:
// cast required for eclipselink workaround, see DATAJPA-433
return getTypedPath(root, part).in((Expression<Collection<?>>) provider.next(part, Collection.class).getExpression());
return upperIfIgnoreCase(getTypedPath(root, part)).in((Expression<Collection<?>>) provider.next(part, Collection.class).getExpression());
case STARTING_WITH:
case ENDING_WITH:
case CONTAINING:

View File

@@ -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<T> metadata = new ParameterMetadata<>(expression, part.getType(), value, persistenceProvider, escape);
ParameterMetadata<T> 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 <T>
*/
static class ParameterMetadata<T> {
@@ -196,16 +201,18 @@ class ParameterMetadataProvider {
private final ParameterExpression<T> expression;
private final PersistenceProvider persistenceProvider;
private final EscapeCharacter escape;
private final boolean ignoreCase;
/**
* Creates a new {@link ParameterMetadata}.
*/
public ParameterMetadata(ParameterExpression<T> expression, Type type, @Nullable Object value,
public ParameterMetadata(ParameterExpression<T> 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<String>) collection).stream()
.map(it -> {
if (it == null) {
return null;
} else {
return it.toUpperCase();
}
})
.collect(Collectors.toList());
}
return collection;
}
}
}

View File

@@ -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() {}
}

View File

@@ -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<User> 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<User> 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<User> 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<User> result = repository.findByAttributesIgnoreCaseIn(Arrays.asList("cOOl", null));
assertThat(result).containsOnly(firstUser);
}
private Page<User> executeSpecWithSort(Sort sort) {
flushTestUsers();

View File

@@ -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<User, Integer>, JpaSpecificationExecutor<User>, UserRepositoryCustom {
@@ -591,6 +592,15 @@ public interface UserRepository
@Query("select u from User u where u.lastname like %?#{escape([0])}% escape ?#{escapeCharacter()}")
List<User> findContainingEscaped(String namePart);
// DATAJPA-1303
List<User> findByAttributesIgnoreCaseIn(Collection<String> attributes);
// DATAJPA-1303
List<User> findByAttributesIgnoreCaseNotIn(Collection<String> attributes);
// DATAJPA-1303
Page<User> findByAttributesIgnoreCaseIn(Pageable pageable, String... attributes);
interface RolesAndFirstname {
String getFirstname();