diff --git a/src/main/java/org/springframework/data/web/querydsl/QuerydslBinding.java b/src/main/java/org/springframework/data/web/querydsl/MultiValueBinding.java similarity index 64% rename from src/main/java/org/springframework/data/web/querydsl/QuerydslBinding.java rename to src/main/java/org/springframework/data/web/querydsl/MultiValueBinding.java index 1b324bb59..681e0a0c3 100644 --- a/src/main/java/org/springframework/data/web/querydsl/QuerydslBinding.java +++ b/src/main/java/org/springframework/data/web/querydsl/MultiValueBinding.java @@ -15,22 +15,15 @@ */ package org.springframework.data.web.querydsl; +import java.util.Collection; + import com.mysema.query.types.Path; import com.mysema.query.types.Predicate; /** - * {@link QuerydslBinding} creates a {@link Predicate} out of given {@link Path} and value. Used for specific parameter - * treatment in {@link QuerydslBindings}.
- * - * @author Christoph Strobl - * @since 1.11 + * @author Oliver Gierke */ -public interface QuerydslBinding> { +public interface MultiValueBinding, S> { - /** - * @param path {@link Path} to the property. Must not be {@literal null}. - * @param value - * @return - */ - Predicate bind(T path, Object value); + Predicate bind(T path, Collection value); } diff --git a/src/main/java/org/springframework/data/web/querydsl/QuerydslBindingContext.java b/src/main/java/org/springframework/data/web/querydsl/QuerydslBindingContext.java deleted file mode 100644 index 5a182377d..000000000 --- a/src/main/java/org/springframework/data/web/querydsl/QuerydslBindingContext.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.web.querydsl; - -import org.springframework.core.convert.ConversionService; -import org.springframework.core.convert.support.DefaultConversionService; -import org.springframework.data.mapping.PropertyPath; -import org.springframework.data.util.TypeInformation; -import org.springframework.util.Assert; - -import com.mysema.query.types.Path; - -/** - * {@link QuerydslBindingContext} holds parameters required for executing {@link QuerydslBinding} on a specific property - * and {@link Path}. - * - * @author Christoph Strobl - * @since 1.11 - */ -class QuerydslBindingContext { - - private final TypeInformation rootTypeInformation; - private final ConversionService conversionService; - private final QuerydslBindings bindings; - - /** - * @param rootTypeInformation Root type information. Must not be {@literal null}. - * @param bindings Path specific bindings. Defaulted to {@link QuerydslBindings} if {@literal null}. - * @param conversionService {@link ConversionService} to be used for converting request parameters into target type. - * Defaulted to {@link DefaultConversionService} if {@literal null}. - */ - public QuerydslBindingContext(TypeInformation rootTypeInformation, QuerydslBindings bindings, - ConversionService conversionService) { - - Assert.notNull(rootTypeInformation, "TypeInformation must not be null!"); - - this.conversionService = conversionService == null ? new DefaultConversionService() : conversionService; - this.rootTypeInformation = rootTypeInformation; - this.bindings = bindings == null ? new QuerydslBindings() : bindings; - } - - /** - * {@link ConversionService} to be used for converting parameter values. - * - * @return never {@literal null}. - */ - public ConversionService getConversionService() { - return this.conversionService; - } - - /** - * Root type information - * - * @return never {@literal null}. - */ - public TypeInformation getTypeInformation() { - return rootTypeInformation; - } - - /** - * {@link QuerydslBindings} to be considered. - * - * @return never {@literal null}. - */ - public QuerydslBindings getBindings() { - return bindings; - } - - /** - * Specific {@link QuerydslBinding} for {@link PropertyPath}. - * - * @param path must not be {@literal null}. - * @return {@literal null} when no specific {@link QuerydslBinding} set for path. - * @see QuerydslBindings#getBindingForPath(PropertyPath) - */ - public QuerydslBinding> getBindingForPath(PropertyPath path) { - return bindings.getBindingForPath(path); - } - - /** - * Specific {@link Path} for {@link PropertyPath}. - * - * @param path - * @return - * @see QuerydslBindings#getPath(PropertyPath) - */ - public Path getPathForPropertyPath(PropertyPath path) { - return bindings.getPath(path); - } - - /** - * Checks visibility of given path against {@link QuerydslBindings}. - * - * @param path must not be {@literal null}. - * @return true when given path is visible by declaration. - * @see QuerydslBindings#isPathVisible(PropertyPath) - */ - public boolean isPathVisible(PropertyPath path) { - return bindings.isPathVisible(path); - } - - /** - * Get the actual raw type of the used root. - * - * @return - * @see TypeInformation#getActualType() - */ - public Class getTargetType() { - return rootTypeInformation.getActualType().getType(); - } -} diff --git a/src/main/java/org/springframework/data/web/querydsl/QuerydslBindings.java b/src/main/java/org/springframework/data/web/querydsl/QuerydslBindings.java index f0bbb3c71..c4ab1b510 100644 --- a/src/main/java/org/springframework/data/web/querydsl/QuerydslBindings.java +++ b/src/main/java/org/springframework/data/web/querydsl/QuerydslBindings.java @@ -16,6 +16,7 @@ package org.springframework.data.web.querydsl; import java.util.Arrays; +import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; @@ -27,92 +28,135 @@ import org.springframework.util.Assert; import org.springframework.util.StringUtils; import com.mysema.query.types.Path; +import com.mysema.query.types.PathMetadata; +import com.mysema.query.types.Predicate; /** - * {@link QuerydslBindings} allows definition of path specific {@link QuerydslBinding}. * + * {@link QuerydslBindings} allows definition of path specific {@link SingleValueBinding}. * *
  * 
  * new QuerydslBindings() {
  *   {
- *     bind(QUser.user.address.city, (path, value) -> path.like(value.toString()));
- *     bind(new StringPath("address.city"), (path, value) -> path.like(value.toString()));
+ *     bind(QUser.user.address.city).using((path, value) -> path.like(value.toString()));
+ *     bind("lastname").using((path, value) -> path.like(value.toString()));
  *   }
  * }
  * 
  * 
* * @author Christoph Strobl + * @author Oliver Gierke * @since 1.11 */ public class QuerydslBindings { - private final Map pathSpecs; - private final Map, PathAndBinding> typeSpecs; - private Set whiteList; + private final Map> pathSpecs; + private final Map, PathAndBinding> typeSpecs; + private final Set whiteList; private final Set blackList; + private boolean excludeUnlistedProperties; + + /** + * Creates a new {@link QuerydslBindings} instance. + */ public QuerydslBindings() { - this.pathSpecs = new LinkedHashMap(); - this.typeSpecs = new LinkedHashMap, PathAndBinding>(); + this.pathSpecs = new LinkedHashMap>(); + this.typeSpecs = new LinkedHashMap, PathAndBinding>(); this.whiteList = new HashSet(); this.blackList = new HashSet(); } /** - * @param path - * @param builder + * Returns a new {@link PathBinder} for the given {@link Path}s to define bindings for them. + * + * @param paths must not be {@literal null} or emtpy. + * @return */ - protected QuerydslBindings bind(String path, QuerydslBinding> builder) { - - Assert.hasText(path, "Cannot bind to null/empty path"); - this.pathSpecs.put(path, new PathAndBinding(null, builder)); - return this; + public final , S> PathBinder bind(T... paths) { + return new PathBinder(paths); } /** - * @param type - * @param builder + * Defines a binding for the given + * + * @param paths + * @return */ - protected QuerydslBindings bind(Class type, QuerydslBinding> builder) { + public final PropertyBinder bind(String... paths) { + return new PropertyBinder(Arrays.asList(paths)); + } - Assert.notNull(type, "Cannot bind to null type!"); - this.typeSpecs.put(type, new PathAndBinding(null, builder)); - return this; + public final TypeBinder bind(Class type) { + return new TypeBinder(type); } /** - * @param path - * @param builder + * Exclude properties from binding. Exclusion of all properties of a nested type can be done by exclusion on a higher + * level. E.g. {@code address} would exclude both {@code address.city} and {@code address.street}. + * + * @param paths must not be {@literal null} or empty. */ - protected > QuerydslBindings bind(T path, QuerydslBinding builder) { + public final void excluding(Path... paths) { - Assert.notNull(path, "Cannot bind to null path!"); - this.pathSpecs.put(extractPropertyPath(path), new PathAndBinding(path, builder)); - return this; + Assert.notEmpty(paths, "At least one path has to be provided!"); + + for (Path path : paths) { + this.blackList.add(toDotPath(path)); + } } /** - * Exclude properties from binding.
- * Exclusion of all properties of a nested type can be done by exclusion on a higher level. Eg. {@code address} would - * exclude both {@code address.city} and {@code address.street}. + * Exclude properties from binding. Exclusion of all properties of a nested type can be done by exclusion on a higher + * level. E.g. {@code address} would exclude both {@code address.city} and {@code address.street}. * * @param properties */ - protected QuerydslBindings excluding(String... properties) { + public final void excluding(String... properties) { this.blackList.addAll(Arrays.asList(properties)); - return this; } /** - * Include properties for binding.
- * Include the property considered a binding candidate. + * Include properties for binding. Include the property considered a binding candidate. + * + * @param properties must not be {@literal null} or empty. + */ + public final void including(Path... paths) { + + Assert.notEmpty(paths, "At least one path has to be provided!"); + + for (Path path : paths) { + this.whiteList.add(toDotPath(path)); + } + } + + /** + * Include properties for binding. Include the property considered a binding candidate. * * @param properties */ - protected QuerydslBindings including(String... properties) { + public final void including(String... properties) { + + Assert.notEmpty(properties, "At least one property has to be provided!"); + this.whiteList.addAll(Arrays.asList(properties)); + } + + /** + * Returns whether to exclude all properties for which no explicit binding has been defined or it has been explicitly + * white-listed. This defaults to {@literal false} which means that for properties without an explicitly defined + * binding a type specific default binding will be applied. + * + * @param excludeUnlistedProperties + * @return + * @see #including(String...) + * @see #including(Path...) + */ + public final QuerydslBindings excludeUnlistedProperties(boolean excludeUnlistedProperties) { + + this.excludeUnlistedProperties = excludeUnlistedProperties; return this; } @@ -122,7 +166,7 @@ public class QuerydslBindings { * @param path * @return */ - public boolean isPathVisible(PropertyPath path) { + boolean isPathVisible(PropertyPath path) { List segments = Arrays.asList(path.toDotPath().split("\\.")); @@ -134,6 +178,7 @@ public class QuerydslBindings { if (!whiteList.isEmpty()) { return whiteList.contains(path.toDotPath()); } + return false; } } @@ -141,96 +186,179 @@ public class QuerydslBindings { return true; } + /** + * Returns the {@link SingleValueBinding} for the given {@link PropertyPath}. Prefers a path configured for the + * specific path but falls back to the builder registered for a given type. + * + * @param path must not be {@literal null}. + * @return + */ + @SuppressWarnings("unchecked") + , T> MultiValueBinding getBindingForPath(PropertyPath path) { + + Assert.notNull(path, "PropertyPath must not be null!"); + + PathAndBinding pathAndBinding = (PathAndBinding) pathSpecs.get(path.toDotPath()); + + if (pathAndBinding != null) { + return pathAndBinding.getBinding(); + } + + pathAndBinding = (PathAndBinding) typeSpecs.get(path.getLeafProperty().getType()); + + return pathAndBinding == null ? null : pathAndBinding.getBinding(); + } + + /** + * Returns a {@link Path} for the {@link PropertyPath} instance. + * + * @param path must not be {@literal null}. + * @return + */ + Path getExistingPath(PropertyPath path) { + + PathAndBinding pathAndBuilder = pathSpecs.get(path.toDotPath()); + return pathAndBuilder == null ? null : pathAndBuilder.getPath(); + } + + /** + * Returns whether the given path is visible, which means either on the white list or not on the black list if no + * white list configured. + * + * @param path must not be {@literal null}. + * @return + */ private boolean isPathVisible(String path) { - if (whiteList.isEmpty() && blackList.isEmpty()) { - return true; - } - - if (!blackList.isEmpty()) { - - if (blackList.contains(path)) { - - if (!whiteList.isEmpty() && whiteList.contains(path)) { - return true; - } - return false; - } - - return true; - } - if (!whiteList.isEmpty()) { if (whiteList.contains(path)) { return true; } + return false; } - return true; + return excludeUnlistedProperties ? false : !blackList.contains(path); } /** - * Returns the {@link QuerydslBinding} for the given {@link PropertyPath}. Prefers a path configured for the specific - * path but falls back to the builder registered for a given type. + * Returns the property path for the given {@link Path}. * - * @param path must not be {@literal null}. + * @param path can be {@literal null}. * @return */ - public QuerydslBinding> getBindingForPath(PropertyPath path) { - - Assert.notNull(path, "PropertyPath must not be null!"); - - PathAndBinding pathAndBinding = pathSpecs.get(path.toDotPath()); - - if (pathAndBinding != null) { - return pathAndBinding.getBinding(); - } - - pathAndBinding = typeSpecs.get(path.getLeafProperty().getType()); - - return pathAndBinding == null ? null : pathAndBinding.getBinding(); - } - - Path getPath(PropertyPath path) { - return getPathForStringPath(path.toDotPath()); - } - - private Path getPathForStringPath(String path) { - - PathAndBinding pathAndBuilder = pathSpecs.get(path); - if (pathAndBuilder == null) { - return null; - } - - return pathAndBuilder.getPath(); - } - - private String extractPropertyPath(Path path) { + private String toDotPath(Path path) { if (path == null) { return ""; } - if (path.getMetadata().getParent() != null && !path.getMetadata().getParent().getMetadata().isRoot()) { - return extractPropertyPath(path.getMetadata().getParent()) + "." + path.getMetadata().getName(); + PathMetadata metadata = path.getMetadata(); + + return path.toString().substring(metadata.getRoot().getMetadata().getName().length() + 1); + } + + /** + * A binder for {@link Path}s. + * + * @author Oliver Gierke + */ + protected final class PathBinder

, T> { + + private final List

paths; + + /** + * Creates a new {@link PathBinder} for the given {@link Path}s. + * + * @param paths must not be {@literal null} or empty. + */ + public PathBinder(P... paths) { + + Assert.notEmpty(paths, "At least one path has to be provided!"); + this.paths = Arrays.asList(paths); } - return path.getMetadata().getName(); + /** + * Defines the given {@link SingleValueBinding} to be used for the paths, + * + * @param binding must not be {@literal null}. + * @return + */ + public void single(SingleValueBinding binding) { + Assert.notNull(binding, "Binding must not be null!"); + + multi(new MultiValueBindingAdapter(binding)); + } + + public void multi(MultiValueBinding binding) { + + Assert.notNull(binding, "Binding must not be null!"); + + for (P path : paths) { + QuerydslBindings.this.pathSpecs.put(toDotPath(path), new PathAndBinding(path, binding)); + } + } + } + + protected final class TypeBinder { + + private final Class type; + + public TypeBinder(Class type) { + this.type = type; + } + + public

> void single(SingleValueBinding binding) { + + Assert.notNull(binding, "Binding must not be null!"); + multi(new MultiValueBindingAdapter(binding)); + } + + public

> void multi(MultiValueBinding binding) { + + Assert.notNull(binding, "Binding must not be null!"); + QuerydslBindings.this.typeSpecs.put(type, new PathAndBinding(null, binding)); + } + } + + protected final class PropertyBinder { + + private final List paths; + + private PropertyBinder(List paths) { + this.paths = paths; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void using(SingleValueBinding binding) { + + Assert.notNull(binding, "Binding must not be null!"); + using(new MultiValueBindingAdapter(binding)); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void using(MultiValueBinding binding) { + + Assert.notNull(binding, "Binding must not be null!"); + + for (String path : paths) { + QuerydslBindings.this.pathSpecs.put(path, new PathAndBinding(null, binding)); + } + } } /** * @author Christoph Strobl * @since 1.11 */ - private static class PathAndBinding { + private static class PathAndBinding, T> { private final Path path; - private final QuerydslBinding> binding; + private final MultiValueBinding binding; - public PathAndBinding(Path path, QuerydslBinding> binding) { + public PathAndBinding(S path, MultiValueBinding binding) { this.path = path; this.binding = binding; @@ -240,9 +368,32 @@ public class QuerydslBindings { return path; } - public QuerydslBinding> getBinding() { + public MultiValueBinding getBinding() { return binding; } + } + /** + * @author Oliver Gierke + */ + static class MultiValueBindingAdapter, S> implements MultiValueBinding { + + private final SingleValueBinding delegate; + + /** + * @param delegate + */ + public MultiValueBindingAdapter(SingleValueBinding delegate) { + this.delegate = delegate; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.web.querydsl.MultiValueBinding#bind(com.mysema.query.types.Path, java.util.Collection) + */ + @Override + public Predicate bind(T path, Collection value) { + return delegate.bind(path, value.iterator().next()); + } } } diff --git a/src/main/java/org/springframework/data/web/querydsl/QuerydslDefaultBinding.java b/src/main/java/org/springframework/data/web/querydsl/QuerydslDefaultBinding.java index 631f7a219..31e05f2d0 100644 --- a/src/main/java/org/springframework/data/web/querydsl/QuerydslDefaultBinding.java +++ b/src/main/java/org/springframework/data/web/querydsl/QuerydslDefaultBinding.java @@ -23,7 +23,8 @@ import com.mysema.query.types.expr.SimpleExpression; import com.mysema.query.types.path.CollectionPathBase; /** - * Default implementation of {@link QuerydslBinding} creating {@link Predicate} based on the {@link Path}s type. Binds: + * Default implementation of {@link MultiValueBinding} creating {@link Predicate} based on the {@link Path}s type. + * Binds: *

    *
  • {@literal null} as {@link SimpleExpression#isNull()}.
  • *
  • {@link java.lang.Object} as {@link SimpleExpression#eq()} on simple properties.
  • @@ -32,9 +33,10 @@ import com.mysema.query.types.path.CollectionPathBase; *
* * @author Christoph Strobl + * @author Oliver Gierke * @since 1.11 */ -class QuerydslDefaultBinding implements QuerydslBinding> { +class QuerydslDefaultBinding implements MultiValueBinding, Object> { /* * (non-Javadoc) @@ -42,27 +44,28 @@ class QuerydslDefaultBinding implements QuerydslBinding> { */ @Override @SuppressWarnings({ "unchecked", "rawtypes" }) - public Predicate bind(Path path, Object source) { + public Predicate bind(Path path, Collection source) { - if (source == null && path instanceof SimpleExpression) { + if ((source == null || source.isEmpty())) { return ((SimpleExpression) path).isNull(); } + Object firstValue = source.iterator().next(); + if (path instanceof CollectionPathBase) { - return ((CollectionPathBase) path).contains(source); + return ((CollectionPathBase) path).contains(firstValue); } if (path instanceof SimpleExpression) { - if (source instanceof Collection) { - return ((SimpleExpression) path).in((Collection) source); + if (source.size() > 1) { + return ((SimpleExpression) path).in(source); } - return ((SimpleExpression) path).eq(source); + return ((SimpleExpression) path).eq(firstValue); } - throw new IllegalArgumentException(String.format("Cannot create predicate for path '%s' with type '%s'.", path, - path.getMetadata().getPathType())); + throw new IllegalArgumentException( + String.format("Cannot create predicate for path '%s' with type '%s'.", path, path.getMetadata().getPathType())); } - } diff --git a/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolver.java b/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolver.java index ac28b0b3f..caa2ba55c 100644 --- a/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolver.java +++ b/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolver.java @@ -16,7 +16,11 @@ package org.springframework.data.web.querydsl; import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeansException; import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; import org.springframework.core.MethodParameter; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.DefaultConversionService; @@ -34,20 +38,32 @@ import com.mysema.query.types.Predicate; * controller methods. * * @author Christoph Strobl + * @author Oliver Gierke * @since 1.11 */ -public class QuerydslPredicateArgumentResolver implements HandlerMethodArgumentResolver { +public class QuerydslPredicateArgumentResolver implements HandlerMethodArgumentResolver, ApplicationContextAware { private final QuerydslPredicateBuilder predicateBuilder; - private final ConversionService conversionService; + + private AutowireCapableBeanFactory beanFactory; /** - * @param conversionService Defaulted to {@link DefaultConversionService} if {@literal null}. + * Creates a new {@link QuerydslPredicateArgumentResolver} using the given {@link ConversionService}. + * + * @param conversionService defaults to {@link DefaultConversionService} if {@literal null}. */ public QuerydslPredicateArgumentResolver(ConversionService conversionService) { + this.predicateBuilder = new QuerydslPredicateBuilder( + conversionService == null ? new DefaultConversionService() : conversionService); + } - this.conversionService = conversionService == null ? new DefaultConversionService() : conversionService; - this.predicateBuilder = new QuerydslPredicateBuilder(); + /* + * (non-Javadoc) + * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) + */ + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.beanFactory = applicationContext.getAutowireCapableBeanFactory(); } /* @@ -62,9 +78,8 @@ public class QuerydslPredicateArgumentResolver implements HandlerMethodArgumentR } if (parameter.hasParameterAnnotation(QuerydslPredicate.class)) { - throw new IllegalArgumentException(String.format( - "Parameter at position %s must be of type Predicate but was %s.", parameter.getParameterIndex(), - parameter.getParameterType())); + throw new IllegalArgumentException(String.format("Parameter at position %s must be of type Predicate but was %s.", + parameter.getParameterIndex(), parameter.getParameterType())); } return false; @@ -75,29 +90,27 @@ public class QuerydslPredicateArgumentResolver implements HandlerMethodArgumentR * @see org.springframework.web.method.support.HandlerMethodArgumentResolver#resolveArgument(org.springframework.core.MethodParameter, org.springframework.web.method.support.ModelAndViewContainer, org.springframework.web.context.request.NativeWebRequest, org.springframework.web.bind.support.WebDataBinderFactory) */ @Override - public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, + public Predicate resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { return predicateBuilder.getPredicate(new MutablePropertyValues(webRequest.getParameterMap()), - createBindingContext(parameter)); - } - - private QuerydslBindingContext createBindingContext(MethodParameter parameter) throws InstantiationException, - IllegalAccessException { - return new QuerydslBindingContext(extractTypeInfo(parameter), extractBindings(parameter), conversionService); + createBindings(parameter), extractTypeInfo(parameter).getActualType()); } private TypeInformation extractTypeInfo(MethodParameter parameter) { Class type = parameter.getParameterAnnotation(QuerydslPredicate.class).root(); - return type == Object.class ? ClassTypeInformation.fromReturnTypeOf(parameter.getMethod()) : ClassTypeInformation - .from(type); + return type == Object.class ? ClassTypeInformation.fromReturnTypeOf(parameter.getMethod()) + : ClassTypeInformation.from(type); } - private QuerydslBindings extractBindings(MethodParameter parameter) throws InstantiationException, - IllegalAccessException { + private QuerydslBindings createBindings(MethodParameter parameter) + throws InstantiationException, IllegalAccessException { - return BeanUtils.instantiateClass(parameter.getParameterAnnotation(QuerydslPredicate.class).bindings()); + Class bindingsType = parameter.getParameterAnnotation(QuerydslPredicate.class) + .bindings(); + + return beanFactory != null ? beanFactory.createBean(bindingsType) : BeanUtils.instantiateClass(bindingsType); } } diff --git a/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateBuilder.java b/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateBuilder.java index 93507738f..a8dd4c226 100644 --- a/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateBuilder.java +++ b/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateBuilder.java @@ -15,42 +15,49 @@ */ package org.springframework.data.web.querydsl; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; -import java.util.List; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import org.springframework.beans.PropertyValue; import org.springframework.beans.PropertyValues; +import org.springframework.core.convert.ConversionService; import org.springframework.data.mapping.PropertyPath; +import org.springframework.data.querydsl.SimpleEntityPathResolver; +import org.springframework.data.util.TypeInformation; import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; +import org.springframework.util.ReflectionUtils; import com.mysema.query.BooleanBuilder; +import com.mysema.query.types.EntityPath; import com.mysema.query.types.Path; -import com.mysema.query.types.PathMetadata; -import com.mysema.query.types.PathMetadataFactory; import com.mysema.query.types.Predicate; -import com.mysema.query.types.path.CollectionPath; -import com.mysema.query.types.path.NumberPath; -import com.mysema.query.types.path.PathBuilder; -import com.mysema.query.types.path.PathBuilderFactory; -import com.mysema.query.types.path.SimplePath; -import com.mysema.query.types.path.StringPath; /** * Builder assembling {@link Predicate} out of {@link PropertyValues}. * * @author Christoph Strobl + * @author Oliver Gierke * @since 1.11 */ class QuerydslPredicateBuilder { - private final QuerydslBinding defaultBinding; + private final ConversionService conversionService; + private final MultiValueBinding defaultBinding; + private final Map> paths; + + public QuerydslPredicateBuilder(ConversionService conversionService) { + + Assert.notNull(conversionService, "ConversionService must not be null!"); - public QuerydslPredicateBuilder() { this.defaultBinding = new QuerydslDefaultBinding(); + this.conversionService = conversionService; + this.paths = new HashMap>(); } /** @@ -58,10 +65,10 @@ class QuerydslPredicateBuilder { * @param context * @return */ - @SuppressWarnings({ "unchecked", "rawtypes" }) - public Predicate getPredicate(PropertyValues values, QuerydslBindingContext context) { - Assert.notNull(context, "Context must not be null!"); + public Predicate getPredicate(PropertyValues values, QuerydslBindings bindings, TypeInformation type) { + + Assert.notNull(bindings, "Context must not be null!"); if (values.isEmpty()) { return new BooleanBuilder(); @@ -71,109 +78,98 @@ class QuerydslPredicateBuilder { for (PropertyValue propertyValue : values.getPropertyValues()) { - PropertyPath propertyPath = PropertyPath.from(propertyValue.getName(), context.getTargetType()); + PropertyPath propertyPath = PropertyPath.from(propertyValue.getName(), type); - if (context.isPathVisible(propertyPath)) { + if (bindings.isPathVisible(propertyPath)) { - Object value = convertToPropertyPathSpecificType(propertyValue.getValue(), propertyPath, context); - QuerydslBinding binding = getPredicateBuilderForPath(propertyPath, context); - - builder.and(binding.bind(getPath(propertyPath, context), value)); + Collection value = convertToPropertyPathSpecificType(propertyValue.getValue(), propertyPath); + builder.and(invokeBinding(propertyPath, bindings, value)); } } return builder.getValue(); } - private QuerydslBinding> getPredicateBuilderForPath(PropertyPath dotPath, - QuerydslBindingContext context) { + @SuppressWarnings({ "unchecked", "rawtypes" }) + private Predicate invokeBinding(PropertyPath dotPath, QuerydslBindings bindings, Collection value) { - QuerydslBinding> binding = context.getBindingForPath(dotPath); - return binding == null ? defaultBinding : binding; + Path path = getPath(dotPath, bindings); + + MultiValueBinding binding = bindings.getBindingForPath(dotPath); + binding = binding == null ? defaultBinding : binding; + + return binding.bind(path, value); } - private Path getPath(PropertyPath propertyPath, QuerydslBindingContext context) { + private Path getPath(PropertyPath path, QuerydslBindings bindings) { - Path path = context.getPathForPropertyPath(propertyPath); - if (path != null) { - return path; + Path resolvedPath = bindings.getExistingPath(path); + + if (resolvedPath != null) { + return resolvedPath; } - return new PropertyPathPathBuilder(context.getTargetType()).forProperty(propertyPath); + resolvedPath = paths.get(resolvedPath); + + if (resolvedPath != null) { + return resolvedPath; + } + + resolvedPath = reifyPath(path, null); + paths.put(path, resolvedPath); + + return resolvedPath; } - private Object convertToPropertyPathSpecificType(Object source, PropertyPath path, QuerydslBindingContext context) { + private static Path reifyPath(PropertyPath path, EntityPath base) { + + EntityPath entityPath = base != null ? base + : SimpleEntityPathResolver.INSTANCE.createPath(path.getOwningType().getType()); + + Field field = ReflectionUtils.findField(entityPath.getClass(), path.getSegment()); + Object value = ReflectionUtils.getField(field, entityPath); + + if (path.hasNext() && value instanceof EntityPath) { + return reifyPath(path.next(), (EntityPath) value); + } + + return (Path) value; + } + + private Collection convertToPropertyPathSpecificType(Object source, PropertyPath path) { + + Class targetType = path.getLeafProperty().getType(); + + if (targetType.isInstance(source)) { + return Collections.singleton(source); + } Object value = source; if (ObjectUtils.isArray(value)) { - - List list = CollectionUtils.arrayToList(value); - if (!list.isEmpty() && list.size() == 1) { - value = list.get(0); - } else { - value = list; - } + value = CollectionUtils.arrayToList(value); } if (value instanceof Collection) { - return potentiallyConvertCollectionValues((Collection) value, path.getType(), context); + return potentiallyConvertCollectionValues((Collection) value, targetType); } - return potentiallyConvertValue(value, path.getType(), context); + return Collections.singleton(potentiallyConvertValue(value, targetType)); } - @SuppressWarnings({ "rawtypes", "unchecked" }) - private Collection potentiallyConvertCollectionValues(Collection source, Class targetType, - QuerydslBindingContext context) { + private Collection potentiallyConvertCollectionValues(Collection source, Class elementType) { + + Collection target = new ArrayList(source.size()); - Collection target = new ArrayList(source.size()); for (Object value : source) { - target.add(potentiallyConvertValue(value, targetType, context)); + target.add(potentiallyConvertValue(value, elementType)); } return target; } - private Object potentiallyConvertValue(Object source, Class targetType, QuerydslBindingContext context) { - - return context.getConversionService().canConvert(source.getClass(), targetType) - ? context.getConversionService().convert(source, targetType) : source; + private Object potentiallyConvertValue(Object source, Class targetType) { + return conversionService.canConvert(source.getClass(), targetType) ? conversionService.convert(source, targetType) + : source; } - - /** - * {@link PropertyPathPathBuilder} creates a typed {@link Path} based on type information contained in - * {@link PropertyPath}. - * - * @author Christoph Strobl - */ - @SuppressWarnings("rawtypes") - static class PropertyPathPathBuilder { - - final PathBuilder pathBuilder; - - public PropertyPathPathBuilder(Class type) { - pathBuilder = new PathBuilderFactory().create(type); - } - - @SuppressWarnings({ "unchecked" }) - public Path forProperty(PropertyPath source) { - - PathMetadata metadata = PathMetadataFactory.forVariable(source.toDotPath()); - - Path path = null; - if (source.isCollection()) { - path = pathBuilder.get(new CollectionPath(Collection.class, source.getType(), metadata)); - } else if (ClassUtils.isAssignable(String.class, source.getType())) { - path = pathBuilder.get(new StringPath(metadata)); - } else if (ClassUtils.isAssignable(Number.class, source.getType())) { - path = pathBuilder.get(new NumberPath(source.getType(), metadata)); - } else { - path = pathBuilder.get(new SimplePath(source.getType(), metadata)); - } - - return path; - } - } - } diff --git a/src/main/java/org/springframework/data/web/querydsl/SingleValueBinding.java b/src/main/java/org/springframework/data/web/querydsl/SingleValueBinding.java new file mode 100644 index 000000000..4fda5551b --- /dev/null +++ b/src/main/java/org/springframework/data/web/querydsl/SingleValueBinding.java @@ -0,0 +1,40 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.web.querydsl; + +import com.mysema.query.types.Path; +import com.mysema.query.types.Predicate; + +/** + * {@link SingleValueBinding} creates a {@link Predicate} out of given {@link Path} and value. Used for specific parameter + * treatment in {@link QuerydslBindings}. + * + * @author Christoph Strobl + * @author Oliver Gierke + * @since 1.11 + */ +@FunctionalInterface +public interface SingleValueBinding, S> { + + /** + * Returns the predicate to be applied to the given {@link Path} for the given value. + * + * @param path {@link Path} to the property. Must not be {@literal null}. + * @param value the value that should be bound. Can be {@literal null}. + * @return must not be {@literal null}. + */ + Predicate bind(T path, S value); +} diff --git a/src/test/java/org/springframework/data/querydsl/Address.java b/src/test/java/org/springframework/data/querydsl/Address.java index 38bb0a604..703bfabcf 100644 --- a/src/test/java/org/springframework/data/querydsl/Address.java +++ b/src/test/java/org/springframework/data/querydsl/Address.java @@ -22,6 +22,11 @@ import com.mysema.query.annotations.QueryEntity; */ @QueryEntity public class Address { - String street; - String city; + + public String street, city; + + public Address(String street, String city) { + this.street = street; + this.city = city; + } } diff --git a/src/test/java/org/springframework/data/querydsl/User.java b/src/test/java/org/springframework/data/querydsl/User.java index ba7b52a83..567def2b8 100644 --- a/src/test/java/org/springframework/data/querydsl/User.java +++ b/src/test/java/org/springframework/data/querydsl/User.java @@ -28,11 +28,16 @@ import com.mysema.query.annotations.QueryEntity; @QueryEntity public class User { - String firstname; - String lastname; - Date dateOfBirth; + public String firstname, lastname; + public Date dateOfBirth; + public Address address; + public List nickNames; + public Long inceptionYear; - Address address; - List nickNames; - Long inceptionYear; + public User(String firstname, String lastname, Address address) { + + this.firstname = firstname; + this.lastname = lastname; + this.address = address; + } } diff --git a/src/test/java/org/springframework/data/querydsl/Users.java b/src/test/java/org/springframework/data/querydsl/Users.java new file mode 100644 index 000000000..56c1a9f2c --- /dev/null +++ b/src/test/java/org/springframework/data/querydsl/Users.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.querydsl; + +import java.util.Arrays; +import java.util.List; + +/** + * @author Oliver Gierke + */ +public class Users { + + public static final User OLIVER, CHRISTOPH; + + public static final List USERS; + + static { + + OLIVER = new User("Oliver", "Gierke", new Address("Somewhere", "Dresden")); + CHRISTOPH = new User("Christoph", "Strobl", new Address("Somewhere", "Linz")); + + USERS = Arrays.asList(OLIVER, CHRISTOPH); + } +} diff --git a/src/test/java/org/springframework/data/web/querydsl/QuerydslBindingsUnitTests.java b/src/test/java/org/springframework/data/web/querydsl/QuerydslBindingsUnitTests.java index ba0bdcc76..02f999451 100644 --- a/src/test/java/org/springframework/data/web/querydsl/QuerydslBindingsUnitTests.java +++ b/src/test/java/org/springframework/data/web/querydsl/QuerydslBindingsUnitTests.java @@ -15,21 +15,18 @@ */ package org.springframework.data.web.querydsl; -import static org.hamcrest.core.Is.*; -import static org.hamcrest.core.IsNull.*; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; -import java.util.Collections; - import org.junit.Before; import org.junit.Test; -import org.springframework.beans.MutablePropertyValues; +import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.data.mapping.PropertyPath; -import org.springframework.data.querydsl.Address; -import org.springframework.data.querydsl.QAddress; +import org.springframework.data.querydsl.QUser; import org.springframework.data.querydsl.User; -import org.springframework.data.util.ClassTypeInformation; +import org.springframework.test.util.ReflectionTestUtils; +import com.mysema.query.types.Path; import com.mysema.query.types.Predicate; import com.mysema.query.types.path.StringPath; @@ -41,69 +38,50 @@ import com.mysema.query.types.path.StringPath; */ public class QuerydslBindingsUnitTests { - private QuerydslPredicateBuilder builder; - private QuerydslBindings typeBasedBindings; - private QuerydslBindings pathBasedBindings; + QuerydslPredicateBuilder builder; + QuerydslBindings bindings; + + static final SingleValueBinding CONTAINS_BINDING = new SingleValueBinding() { + + @Override + public Predicate bind(StringPath path, String value) { + return path.contains(value); + } + }; @Before public void setUp() { - builder = new QuerydslPredicateBuilder(); - typeBasedBindings = new QuerydslBindings() { - { - bind(String.class, new QuerydslBinding() { - - @Override - public Predicate bind(StringPath path, Object value) { - return path.contains(value.toString()); - } - }); - } - }; - - pathBasedBindings = new QuerydslBindings() { - { - bind("address.street", new QuerydslBinding() { - - @Override - public Predicate bind(StringPath path, Object value) { - return path.contains(value.toString()); - } - }); - - bind("firstname", new QuerydslBinding() { - - @Override - public Predicate bind(StringPath path, Object value) { - return path.contains(value.toString()); - } - }); - } - }; + this.builder = new QuerydslPredicateBuilder(new DefaultConversionService()); + this.bindings = new QuerydslBindings(); } /** * @see DATACMNS-669 */ @Test(expected = IllegalArgumentException.class) - public void getBindingForPathShouldThrowErrorWhenPathIsNull() { - pathBasedBindings.getBindingForPath(null); + public void rejectsNullPath() { + bindings.getBindingForPath(null); } /** * @see DATACMNS-669 */ @Test - public void getBindingForPathShouldReturnNullWhenNoSpecifcBindingAvailable() { - assertThat(pathBasedBindings.getBindingForPath(PropertyPath.from("lastname", User.class)), nullValue()); + public void returnsNullIfNoBindingRegisteredForPath() { + assertThat(bindings.getBindingForPath(PropertyPath.from("lastname", User.class)), nullValue()); } /** * @see DATACMNS-669 */ @Test - public void getBindingForPathShouldReturnSpeficicBindingWhenAvailable() { - assertThat(pathBasedBindings.getBindingForPath(PropertyPath.from("firstname", User.class)), notNullValue()); + public void returnsRegisteredBindingForSimplePath() { + + bindings.bind("firstname").using(CONTAINS_BINDING); + + assertAdapterWithTargetBinding(bindings.getBindingForPath(PropertyPath.from("firstname", User.class)), + CONTAINS_BINDING); } /** @@ -111,7 +89,11 @@ public class QuerydslBindingsUnitTests { */ @Test public void getBindingForPathShouldReturnSpeficicBindingForNestedElementsWhenAvailable() { - assertThat(pathBasedBindings.getBindingForPath(PropertyPath.from("address.street", User.class)), notNullValue()); + + bindings.bind("address.street").using(CONTAINS_BINDING); + + assertAdapterWithTargetBinding(bindings.getBindingForPath(PropertyPath.from("address.street", User.class)), + CONTAINS_BINDING); } /** @@ -119,100 +101,112 @@ public class QuerydslBindingsUnitTests { */ @Test public void getBindingForPathShouldReturnSpeficicBindingForTypes() { - assertThat(typeBasedBindings.getBindingForPath(PropertyPath.from("address.street", User.class)), notNullValue()); + + bindings.bind(String.class).single(CONTAINS_BINDING); + + assertAdapterWithTargetBinding(bindings.getBindingForPath(PropertyPath.from("address.street", User.class)), + CONTAINS_BINDING); } /** * @see DATACMNS-669 */ @Test - public void getBindingForPathShouldIgnoreSpeficicBindingForTypesWhenTypesDoNotMatch() { - assertThat(typeBasedBindings.getBindingForPath(PropertyPath.from("inceptionYear", User.class)), nullValue()); + public void propertyNotExplicitlyIncludedAndWithoutTypeBindingIsInvisible() { + + bindings.bind(String.class).single(CONTAINS_BINDING); + + assertThat(bindings.getBindingForPath(PropertyPath.from("inceptionYear", User.class)), nullValue()); } /** * @see DATACMNS-669 */ @Test - public void isPathVisibleShouldReturnTrueWhenNoRestrictionDefined() { - assertThat(typeBasedBindings.isPathVisible(PropertyPath.from("inceptionYear", User.class)), is(true)); + public void pathIsVisibleIfTypeBasedBindingWasRegistered() { + + bindings.bind(String.class).single(CONTAINS_BINDING); + + assertThat(bindings.isPathVisible(PropertyPath.from("inceptionYear", User.class)), is(true)); } /** * @see DATACMNS-669 */ @Test - public void isPathVisibleShouldReturnTrueWhenPathContainedInIncluding() { + public void explicitlyIncludedPathIsVisible() { - assertThat( - new QuerydslBindings().including("inceptionYear").isPathVisible(PropertyPath.from("inceptionYear", User.class)), - is(true)); + bindings.including("inceptionYear"); + + assertThat(bindings.isPathVisible(PropertyPath.from("inceptionYear", User.class)), is(true)); } /** * @see DATACMNS-669 */ @Test - public void isPathVisibleShouldReturnFalseWhenPathNotContainedInIncluding() { + public void notExplicitlyIncludedPathIsInvisible() { - assertThat( - new QuerydslBindings().including("inceptionYear").isPathVisible(PropertyPath.from("firstname", User.class)), - is(false)); + bindings.including("inceptionYear"); + + assertThat(bindings.isPathVisible(PropertyPath.from("firstname", User.class)), is(false)); } /** * @see DATACMNS-669 */ @Test - public void isPathVisibleShouldReturnFalseWhenPathContainedInExcluding() { + public void excludedPathIsInvisible() { - assertThat( - new QuerydslBindings().excluding("inceptionYear").isPathVisible(PropertyPath.from("inceptionYear", User.class)), - is(false)); + bindings.excluding("inceptionYear"); + + assertThat(bindings.isPathVisible(PropertyPath.from("inceptionYear", User.class)), is(false)); } /** * @see DATACMNS-669 */ @Test - public void isPathVisibleShouldReturnTrueWhenPathNotContainedInExcluding() { + public void pathIsVisibleIfNotExplicitlyExcluded() { - assertThat( - new QuerydslBindings().excluding("inceptionYear").isPathVisible(PropertyPath.from("firstname", User.class)), - is(true)); + bindings.excluding("inceptionYear"); + + assertThat(bindings.isPathVisible(PropertyPath.from("firstname", User.class)), is(true)); } /** * @see DATACMNS-669 */ @Test - public void isPathVisibleShouldReturnTrueWhenPathContainedInExcludingAndIncluding() { + public void pathIsVisibleIfItsBothBlackAndWhitelisted() { - assertThat( - new QuerydslBindings().excluding("inceptionYear").isPathVisible(PropertyPath.from("firstname", User.class)), - is(true)); + bindings.excluding("firstname"); + bindings.including("firstname"); + + assertThat(bindings.isPathVisible(PropertyPath.from("firstname", User.class)), is(true)); } /** * @see DATACMNS-669 */ @Test - public void isPathVisibleShouldReturnFalseWhenPartialPathContainedInExcluding() { + public void nestedPathIsInvisibleIfAParanetPathWasExcluded() { - assertThat( - new QuerydslBindings().excluding("address").isPathVisible(PropertyPath.from("address.city", User.class)), - is(false)); + bindings.excluding("address"); + + assertThat(bindings.isPathVisible(PropertyPath.from("address.city", User.class)), is(false)); } /** * @see DATACMNS-669 */ @Test - public void isPathVisibleShouldReturnTrueWhenPartialPathContainedInExcludingButConcretePathIsIncluded() { + public void pathIsVisibleIfConcretePathIsVisibleButParentExcluded() { - assertThat( - new QuerydslBindings().excluding("address").including("address.city") - .isPathVisible(PropertyPath.from("address.city", User.class)), is(true)); + bindings.excluding("address"); + bindings.including("address.city"); + + assertThat(bindings.isPathVisible(PropertyPath.from("address.city", User.class)), is(true)); } /** @@ -221,22 +215,32 @@ public class QuerydslBindingsUnitTests { @Test public void isPathVisibleShouldReturnFalseWhenPartialPathContainedInExcludingAndConcretePathToDifferentPropertyIsIncluded() { - assertThat( - new QuerydslBindings().excluding("address").including("address.city") - .isPathVisible(PropertyPath.from("address.street", User.class)), is(false)); + bindings.excluding("address"); + bindings.including("address.city"); + + assertThat(bindings.isPathVisible(PropertyPath.from("address.street", User.class)), is(false)); } - /** - * @see DATACMNS-669 - */ @Test - public void usesTypeBasedBindingIfConfigured() { + public void testname() { - MutablePropertyValues values = new MutablePropertyValues(Collections.singletonMap("city", "Dresden")); + PropertyPath firstname = PropertyPath.from("firstname", User.class); + PropertyPath lastname = PropertyPath.from("lastname", User.class); + PropertyPath city = PropertyPath.from("address.city", User.class); + PropertyPath street = PropertyPath.from("address.street", User.class); - QuerydslBindingContext context = new QuerydslBindingContext(ClassTypeInformation.from(Address.class), - this.typeBasedBindings, null); + bindings.including(QUser.user.firstname, QUser.user.address.street); - assertThat(builder.getPredicate(values, context), is((Predicate) QAddress.address.city.contains("Dresden"))); + assertThat(bindings.isPathVisible(firstname), is(true)); + assertThat(bindings.isPathVisible(street), is(true)); + assertThat(bindings.isPathVisible(lastname), is(false)); + assertThat(bindings.isPathVisible(city), is(false)); + } + + private static

, S> void assertAdapterWithTargetBinding(MultiValueBinding binding, + SingleValueBinding, ?> expected) { + + assertThat(binding, is(instanceOf(QuerydslBindings.MultiValueBindingAdapter.class))); + assertThat(ReflectionTestUtils.getField(binding, "delegate"), is((Object) expected)); } } diff --git a/src/test/java/org/springframework/data/web/querydsl/QuerydslDefaultBindingUnitTests.java b/src/test/java/org/springframework/data/web/querydsl/QuerydslDefaultBindingUnitTests.java index c2473bb77..30d6cb7fa 100644 --- a/src/test/java/org/springframework/data/web/querydsl/QuerydslDefaultBindingUnitTests.java +++ b/src/test/java/org/springframework/data/web/querydsl/QuerydslDefaultBindingUnitTests.java @@ -18,6 +18,7 @@ package org.springframework.data.web.querydsl; import static org.hamcrest.core.Is.*; import java.util.Arrays; +import java.util.Collections; import org.hamcrest.Matcher; import org.hamcrest.core.Is; @@ -47,7 +48,7 @@ public class QuerydslDefaultBindingUnitTests { @Test public void shouldCreatePredicateCorrectlyWhenPropertyIsInRoot() { - Predicate predicate = builder.bind(QUser.user.firstname, "tam"); + Predicate predicate = builder.bind(QUser.user.firstname, Collections.singleton("tam")); assertThat(predicate, is(QUser.user.firstname.eq("tam"))); } @@ -58,7 +59,7 @@ public class QuerydslDefaultBindingUnitTests { @Test public void shouldCreatePredicateCorrectlyWhenPropertyIsInNestedElement() { - Predicate predicate = builder.bind(QUser.user.address.city, "two rivers"); + Predicate predicate = builder.bind(QUser.user.address.city, Collections.singleton("two rivers")); Assert.assertThat(predicate.toString(), is(QUser.user.address.city.eq("two rivers").toString())); } @@ -69,7 +70,7 @@ public class QuerydslDefaultBindingUnitTests { @Test public void shouldCreatePredicateCorrectlyWhenValueIsNull() { - Predicate predicate = builder.bind(QUser.user.firstname, null); + Predicate predicate = builder.bind(QUser.user.firstname, Collections.emptySet()); assertThat(predicate, is(QUser.user.firstname.isNull())); } @@ -80,7 +81,7 @@ public class QuerydslDefaultBindingUnitTests { @Test public void shouldCreatePredicateWithContainingWhenPropertyIsCollectionLikeAndValueIsObject() { - Predicate predicate = builder.bind(QUser.user.nickNames, "dragon reborn"); + Predicate predicate = builder.bind(QUser.user.nickNames, Collections.singleton("dragon reborn")); assertThat(predicate, is(QUser.user.nickNames.contains("dragon reborn"))); } diff --git a/src/test/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolverUnitTests.java b/src/test/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolverUnitTests.java index 5cbb9a0e0..ae9b18d55 100644 --- a/src/test/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolverUnitTests.java +++ b/src/test/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolverUnitTests.java @@ -18,7 +18,6 @@ package org.springframework.data.web.querydsl; import static org.hamcrest.core.Is.*; import static org.junit.Assert.*; -import org.hamcrest.core.Is; import org.junit.Before; import org.junit.Test; import org.springframework.core.MethodParameter; @@ -34,7 +33,10 @@ import com.mysema.query.types.expr.BooleanExpression; import com.mysema.query.types.path.StringPath; /** + * Unit tests for {@link QuerydslPredicateArgumentResolver}. + * * @author Christoph Strobl + * @author Oliver Gierke */ public class QuerydslPredicateArgumentResolverUnitTests { @@ -90,10 +92,10 @@ public class QuerydslPredicateArgumentResolverUnitTests { request.addParameter("firstname", "rand"); - Object predicate = (BooleanExpression) resolver.resolveArgument( + Predicate predicate = (BooleanExpression) resolver.resolveArgument( getMethodParameterFor("simpleFind", Predicate.class), null, new ServletWebRequest(request), null); - assertThat(predicate, Is. is(QUser.user.firstname.eq("rand"))); + assertThat(predicate, is((Predicate) QUser.user.firstname.eq("rand"))); } /** @@ -105,10 +107,10 @@ public class QuerydslPredicateArgumentResolverUnitTests { request.addParameter("firstname", "rand"); request.addParameter("lastname", "al'thor"); - Object predicate = resolver.resolveArgument(getMethodParameterFor("simpleFind", Predicate.class), null, + Predicate predicate = resolver.resolveArgument(getMethodParameterFor("simpleFind", Predicate.class), null, new ServletWebRequest(request), null); - assertThat(predicate, Is. is(QUser.user.firstname.eq("rand").and(QUser.user.lastname.eq("al'thor")))); + assertThat(predicate, is((Predicate) QUser.user.firstname.eq("rand").and(QUser.user.lastname.eq("al'thor")))); } /** @@ -119,10 +121,12 @@ public class QuerydslPredicateArgumentResolverUnitTests { request.addParameter("address.city", "two rivers"); - Object predicate = resolver.resolveArgument(getMethodParameterFor("simpleFind", Predicate.class), null, + Predicate predicate = resolver.resolveArgument(getMethodParameterFor("simpleFind", Predicate.class), null, new ServletWebRequest(request), null); - assertThat(predicate.toString(), is(QUser.user.address.city.eq("two rivers").toString())); + BooleanExpression eq = QUser.user.address.city.eq("two rivers"); + + assertThat(predicate, is((Predicate) eq)); } /** @@ -133,10 +137,10 @@ public class QuerydslPredicateArgumentResolverUnitTests { request.addParameter("address.city", "tar valon"); - Object predicate = resolver.resolveArgument(getMethodParameterFor("pagedFind", Predicate.class, Pageable.class), + Predicate predicate = resolver.resolveArgument(getMethodParameterFor("pagedFind", Predicate.class, Pageable.class), null, new ServletWebRequest(request), null); - assertThat(predicate.toString(), is(QUser.user.address.city.eq("tar valon").toString())); + assertThat(predicate, is((Predicate) QUser.user.address.city.eq("tar valon"))); } /** @@ -148,12 +152,11 @@ public class QuerydslPredicateArgumentResolverUnitTests { request.addParameter("firstname", "egwene"); request.addParameter("lastname", "al'vere"); - Object predicate = resolver.resolveArgument(getMethodParameterFor("specificFind", Predicate.class), null, + Predicate predicate = resolver.resolveArgument(getMethodParameterFor("specificFind", Predicate.class), null, new ServletWebRequest(request), null); - assertThat(predicate.toString(), - is(QUser.user.firstname.eq("egwene".toUpperCase()).and(QUser.user.lastname.toLowerCase().eq("al'vere")) - .toString())); + assertThat(predicate, is((Predicate) QUser.user.firstname.eq("egwene".toUpperCase()) + .and(QUser.user.lastname.toLowerCase().eq("al'vere")))); } /** @@ -164,10 +167,10 @@ public class QuerydslPredicateArgumentResolverUnitTests { request.addParameter("inceptionYear", "978"); - Object predicate = (BooleanExpression) resolver.resolveArgument( + Predicate predicate = (BooleanExpression) resolver.resolveArgument( getMethodParameterFor("specificFind", Predicate.class), null, new ServletWebRequest(request), null); - assertThat(predicate, Is. is(QUser.user.inceptionYear.eq(978L))); + assertThat(predicate, is((Predicate) QUser.user.inceptionYear.eq(978L))); } /** @@ -178,10 +181,10 @@ public class QuerydslPredicateArgumentResolverUnitTests { request.addParameter("inceptionYear", new String[] { "978", "998" }); - Object predicate = (BooleanExpression) resolver.resolveArgument( + Predicate predicate = (BooleanExpression) resolver.resolveArgument( getMethodParameterFor("specificFind", Predicate.class), null, new ServletWebRequest(request), null); - assertThat(predicate, Is. is(QUser.user.inceptionYear.in(978L, 998L))); + assertThat(predicate, is((Predicate) QUser.user.inceptionYear.in(978L, 998L))); } /** @@ -199,7 +202,7 @@ public class QuerydslPredicateArgumentResolverUnitTests { assertThat(predicate.toString(), is(QUser.user.inceptionYear.eq(973L).toString())); } - private MethodParameter getMethodParameterFor(String methodName, Class... args) throws RuntimeException { + private static MethodParameter getMethodParameterFor(String methodName, Class... args) throws RuntimeException { try { return new MethodParameter(Sample.class.getMethod(methodName, args), 0); @@ -212,19 +215,19 @@ public class QuerydslPredicateArgumentResolverUnitTests { public SpecificBinding() { - bind("firstname", new QuerydslBinding() { + bind("firstname").using(new SingleValueBinding() { @Override - public Predicate bind(StringPath path, Object value) { - return path.eq(value.toString().toUpperCase()); + public Predicate bind(StringPath path, String value) { + return path.eq(value.toUpperCase()); } }); - bind(QUser.user.lastname, new QuerydslBinding() { + bind(QUser.user.lastname).single(new SingleValueBinding() { @Override - public Predicate bind(StringPath path, Object value) { - return path.toLowerCase().eq(value.toString()); + public Predicate bind(StringPath path, String value) { + return path.toLowerCase().eq(value); } }); diff --git a/src/test/java/org/springframework/data/web/querydsl/QuerydslPredicateBuilderUnitTests.java b/src/test/java/org/springframework/data/web/querydsl/QuerydslPredicateBuilderUnitTests.java index 19ef24907..96b9475a8 100644 --- a/src/test/java/org/springframework/data/web/querydsl/QuerydslPredicateBuilderUnitTests.java +++ b/src/test/java/org/springframework/data/web/querydsl/QuerydslPredicateBuilderUnitTests.java @@ -15,32 +15,49 @@ */ package org.springframework.data.web.querydsl; -import static org.hamcrest.core.Is.*; +import static java.util.Collections.*; +import static org.hamcrest.Matchers.*; +import static org.hamcrest.core.Is.is; import static org.junit.Assert.*; -import java.util.Collections; +import java.util.List; -import org.hamcrest.core.Is; import org.junit.Before; import org.junit.Test; import org.springframework.beans.MutablePropertyValues; +import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.data.querydsl.QUser; import org.springframework.data.querydsl.User; +import org.springframework.data.querydsl.Users; import org.springframework.data.util.ClassTypeInformation; import com.mysema.query.BooleanBuilder; +import com.mysema.query.collections.CollQueryFactory; import com.mysema.query.types.Predicate; /** + * Unit tests for {@link QuerydslPredicateBuilder}. + * * @author Christoph Strobl + * @author Oliver Gierke */ public class QuerydslPredicateBuilderUnitTests { + static final QuerydslBindings DEFAULT_BINDINGS = new QuerydslBindings(); + QuerydslPredicateBuilder builder; @Before public void setUp() { - builder = new QuerydslPredicateBuilder(); + builder = new QuerydslPredicateBuilder(new DefaultConversionService()); + } + + /** + * @see DATACMNS-669 + */ + @Test(expected = IllegalArgumentException.class) + public void rejectsNullConversionService() { + new QuerydslPredicateBuilder(null); } /** @@ -48,7 +65,7 @@ public class QuerydslPredicateBuilderUnitTests { */ @Test(expected = IllegalArgumentException.class) public void getPredicateShouldThrowErrorWhenBindingContextIsNull() { - builder.getPredicate(new MutablePropertyValues(), null); + builder.getPredicate(new MutablePropertyValues(), null, null); } /** @@ -57,8 +74,8 @@ public class QuerydslPredicateBuilderUnitTests { @Test public void getPredicateShouldReturnEmptyPredicateWhenPropertiesAreEmpty() { - assertThat(builder.getPredicate(new MutablePropertyValues(), new QuerydslBindingContext( - ClassTypeInformation.OBJECT, null, null)), is((Predicate) new BooleanBuilder())); + assertThat(builder.getPredicate(new MutablePropertyValues(), DEFAULT_BINDINGS, ClassTypeInformation.OBJECT), + is((Predicate) new BooleanBuilder())); } /** @@ -67,10 +84,31 @@ public class QuerydslPredicateBuilderUnitTests { @Test public void resolveArgumentShouldCreateSingleStringParameterPredicateCorrectly() throws Exception { - Predicate predicate = builder.getPredicate( - new MutablePropertyValues(Collections.singletonMap("firstname", new String[] { "rand" })), - new QuerydslBindingContext(ClassTypeInformation.from(User.class), null, null)); + Predicate predicate = builder.getPredicate(new MutablePropertyValues(singletonMap("firstname", "Oliver")), + DEFAULT_BINDINGS, ClassTypeInformation.from(User.class)); - assertThat(predicate, Is. is(QUser.user.firstname.eq("rand"))); + assertThat(predicate, is((Predicate) QUser.user.firstname.eq("Oliver"))); + + List result = CollQueryFactory.from(QUser.user, Users.USERS).where(predicate).list(QUser.user); + + assertThat(result, hasSize(1)); + assertThat(result, hasItem(Users.OLIVER)); + } + + /** + * @see DATACMNS-669 + */ + @Test + public void resolveArgumentShouldCreateNestedStringParameterPredicateCorrectly() throws Exception { + + Predicate predicate = builder.getPredicate(new MutablePropertyValues(singletonMap("address.city", "Linz")), + DEFAULT_BINDINGS, ClassTypeInformation.from(User.class)); + + assertThat(predicate, is((Predicate) QUser.user.address.city.eq("Linz"))); + + List result = CollQueryFactory.from(QUser.user, Users.USERS).where(predicate).list(QUser.user); + + assertThat(result, hasSize(1)); + assertThat(result, hasItem(Users.CHRISTOPH)); } }