From e3668c621d96a68054b036a1d02aa36d0db6bec0 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Tue, 21 Jul 2015 15:45:58 +0200 Subject: [PATCH] DATACMNS-734 - Improved Querydsl predicate binding using formatters. We now default to a FormattingConversionService to make sure comma-separated values are transformed into arrays correctly. Also, we now build up dedicated TypeDescriptor instances for the property or field about to be bound to potentially pick up formatting annotations on those. --- .../binding/QuerydslPredicateBuilder.java | 35 ++++++++++++++++--- .../springframework/data/querydsl/User.java | 5 ++- .../QuerydslPredicateBuilderUnitTests.java | 25 +++++++++++-- 3 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilder.java b/src/main/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilder.java index 1b7c6325a..748fae33d 100644 --- a/src/main/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilder.java +++ b/src/main/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilder.java @@ -15,6 +15,7 @@ */ package org.springframework.data.querydsl.binding; +import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; @@ -24,8 +25,11 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import org.springframework.beans.BeanUtils; import org.springframework.beans.PropertyValues; import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.Property; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.data.mapping.PropertyPath; import org.springframework.data.mapping.PropertyReferenceException; import org.springframework.data.querydsl.EntityPathResolver; @@ -191,8 +195,8 @@ public class QuerydslPredicateBuilder { */ private Collection convertToPropertyPathSpecificType(List source, PropertyPath path) { - Class targetType = path.getLeafProperty().getOwningType().getProperty(path.getLeafProperty().getSegment()) - .getType(); + PropertyPath leafProperty = path.getLeafProperty(); + Class targetType = leafProperty.getOwningType().getProperty(leafProperty.getSegment()).getType(); if (source.isEmpty() || isSingleElementCollectionWithoutText(source)) { return Collections.emptyList(); @@ -202,13 +206,36 @@ public class QuerydslPredicateBuilder { for (String value : source) { - target.add(conversionService.canConvert(String.class, targetType) ? conversionService.convert( - targetType.isArray() ? value.split(",") : value, targetType) : value); + target.add(conversionService.canConvert(String.class, targetType) + ? conversionService.convert(value, TypeDescriptor.forObject(value), getTargetTypeDescriptor(path)) : value); } return target; } + /** + * Returns the target {@link TypeDescriptor} for the given {@link PropertyPath} by either inspecting the field or + * property (the latter preferred) to pick up annotations potentially defined for formatting purposes. + * + * @param path must not be {@literal null}. + * @return + */ + private static TypeDescriptor getTargetTypeDescriptor(PropertyPath path) { + + PropertyPath leafProperty = path.getLeafProperty(); + Class owningType = leafProperty.getOwningType().getType(); + + PropertyDescriptor descriptor = BeanUtils.getPropertyDescriptor(owningType, leafProperty.getSegment()); + + if (descriptor == null) { + return TypeDescriptor.nested(ReflectionUtils.findField(owningType, leafProperty.getSegment()), 0); + } + + return TypeDescriptor.nested( + new Property(owningType, descriptor.getReadMethod(), descriptor.getWriteMethod(), leafProperty.getSegment()), + 0); + } + /** * Returns whether the given collection has exactly one element that doesn't contain any text. This is basically an * indicator that a request parameter has been submitted but no value for it. diff --git a/src/test/java/org/springframework/data/querydsl/User.java b/src/test/java/org/springframework/data/querydsl/User.java index 567def2b8..fd12adae0 100644 --- a/src/test/java/org/springframework/data/querydsl/User.java +++ b/src/test/java/org/springframework/data/querydsl/User.java @@ -18,6 +18,9 @@ package org.springframework.data.querydsl; import java.util.Date; import java.util.List; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.format.annotation.DateTimeFormat.ISO; + import com.mysema.query.annotations.QueryEntity; /** @@ -29,7 +32,7 @@ import com.mysema.query.annotations.QueryEntity; public class User { public String firstname, lastname; - public Date dateOfBirth; + public @DateTimeFormat(iso = ISO.DATE) Date dateOfBirth; public Address address; public List nickNames; public Long inceptionYear; diff --git a/src/test/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilderUnitTests.java b/src/test/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilderUnitTests.java index a12721d17..5e7583d26 100644 --- a/src/test/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilderUnitTests.java +++ b/src/test/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilderUnitTests.java @@ -19,16 +19,20 @@ import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import static org.springframework.test.util.ReflectionTestUtils.*; +import java.text.ParseException; import java.util.List; +import org.joda.time.DateTime; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; import org.junit.Before; import org.junit.Test; -import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.data.querydsl.QUser; import org.springframework.data.querydsl.SimpleEntityPathResolver; import org.springframework.data.querydsl.User; import org.springframework.data.querydsl.Users; import org.springframework.data.util.ClassTypeInformation; +import org.springframework.format.support.DefaultFormattingConversionService; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -53,7 +57,8 @@ public class QuerydslPredicateBuilderUnitTests { @Before public void setUp() { - this.builder = new QuerydslPredicateBuilder(new DefaultConversionService(), SimpleEntityPathResolver.INSTANCE); + this.builder = new QuerydslPredicateBuilder(new DefaultFormattingConversionService(), + SimpleEntityPathResolver.INSTANCE); this.values = new LinkedMultiValueMap(); } @@ -185,4 +190,20 @@ public class QuerydslPredicateBuilderUnitTests { assertThat(constant.getConstant(), instanceOf(String.class)); assertThat((String) (constant.getConstant()), equalTo("rivers,two")); } + + /** + * @see DATACMNS-734 + */ + @Test + public void bindsDateCorrectly() throws ParseException { + + DateTimeFormatter format = DateTimeFormat.forPattern("yyyy-MM-dd"); + String date = format.print(new DateTime()); + + values.add("dateOfBirth", format.print(new DateTime())); + + Predicate predicate = builder.getPredicate(USER_TYPE, values, DEFAULT_BINDINGS); + + assertThat(predicate, is((Predicate) QUser.user.dateOfBirth.eq(format.parseDateTime(date).toDate()))); + } }