DATACMNS-669 - Polishing.

QuerydslBindings now uses a builder style pattern to define custom bindings. This allows to define the same binding for multiple properties of the same type in one configuration call.

Introduced MultiValueBinding to be able to register a binding for multiple source values (i.e. if multiple values are provided for the same parameter). The previously named QuerydslBinding is now a SingleValueBinding which gets adapted to by extracting the first element of the values provided considered as collection.

Slightly refined the generics signature in QuerydslBinding for better type derivation in Lambdas. Removed the need for QuerydslBindingContext by moving a few properties around. The ConversionService instance is now piped into the QuerydslPredicateBuilder directly.

Added a bit of sample data to the Users test domain type to be able to execute the generated Predicates in tests to validate their correctness.

Original pull request: #132.
This commit is contained in:
Oliver Gierke
2015-07-15 09:24:52 +02:00
parent e645501174
commit 5db3def79b
14 changed files with 652 additions and 487 deletions

View File

@@ -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}. <br />
*
* @author Christoph Strobl
* @since 1.11
* @author Oliver Gierke
*/
public interface QuerydslBinding<T extends Path<?>> {
public interface MultiValueBinding<T extends Path<? extends S>, 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<? extends S> value);
}

View File

@@ -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<? extends Path<?>> 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();
}
}

View File

@@ -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}.
*
* <pre>
* <code>
* 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()));
* }
* }
* </code>
* </pre>
*
* @author Christoph Strobl
* @author Oliver Gierke
* @since 1.11
*/
public class QuerydslBindings {
private final Map<String, PathAndBinding> pathSpecs;
private final Map<Class<?>, PathAndBinding> typeSpecs;
private Set<String> whiteList;
private final Map<String, PathAndBinding<?, ?>> pathSpecs;
private final Map<Class<?>, PathAndBinding<?, ?>> typeSpecs;
private final Set<String> whiteList;
private final Set<String> blackList;
private boolean excludeUnlistedProperties;
/**
* Creates a new {@link QuerydslBindings} instance.
*/
public QuerydslBindings() {
this.pathSpecs = new LinkedHashMap<String, PathAndBinding>();
this.typeSpecs = new LinkedHashMap<Class<?>, PathAndBinding>();
this.pathSpecs = new LinkedHashMap<String, PathAndBinding<?, ?>>();
this.typeSpecs = new LinkedHashMap<Class<?>, PathAndBinding<?, ?>>();
this.whiteList = new HashSet<String>();
this.blackList = new HashSet<String>();
}
/**
* @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<? extends Path<?>> builder) {
Assert.hasText(path, "Cannot bind to null/empty path");
this.pathSpecs.put(path, new PathAndBinding(null, builder));
return this;
public final <T extends Path<S>, S> PathBinder<T, S> bind(T... paths) {
return new PathBinder<T, S>(paths);
}
/**
* @param type
* @param builder
* Defines a binding for the given
*
* @param paths
* @return
*/
protected <T> QuerydslBindings bind(Class<T> type, QuerydslBinding<? extends Path<T>> 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 <T> TypeBinder<T> bind(Class<T> type) {
return new TypeBinder<T>(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 <T extends Path<?>> QuerydslBindings bind(T path, QuerydslBinding<T> 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. <br />
* 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. <br />
* 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<String> 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")
<S extends Path<T>, T> MultiValueBinding<S, T> getBindingForPath(PropertyPath path) {
Assert.notNull(path, "PropertyPath must not be null!");
PathAndBinding<S, T> pathAndBinding = (PathAndBinding<S, T>) pathSpecs.get(path.toDotPath());
if (pathAndBinding != null) {
return pathAndBinding.getBinding();
}
pathAndBinding = (PathAndBinding<S, T>) 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<? extends Path<?>> 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<P extends Path<? extends T>, T> {
private final List<P> 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<P, T> binding) {
Assert.notNull(binding, "Binding must not be null!");
multi(new MultiValueBindingAdapter<P, T>(binding));
}
public void multi(MultiValueBinding<P, T> binding) {
Assert.notNull(binding, "Binding must not be null!");
for (P path : paths) {
QuerydslBindings.this.pathSpecs.put(toDotPath(path), new PathAndBinding<P, T>(path, binding));
}
}
}
protected final class TypeBinder<T> {
private final Class<T> type;
public TypeBinder(Class<T> type) {
this.type = type;
}
public <P extends Path<T>> void single(SingleValueBinding<P, T> binding) {
Assert.notNull(binding, "Binding must not be null!");
multi(new MultiValueBindingAdapter<P, T>(binding));
}
public <P extends Path<T>> void multi(MultiValueBinding<P, T> binding) {
Assert.notNull(binding, "Binding must not be null!");
QuerydslBindings.this.typeSpecs.put(type, new PathAndBinding<P, T>(null, binding));
}
}
protected final class PropertyBinder {
private final List<String> paths;
private PropertyBinder(List<String> 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<S extends Path<? extends T>, T> {
private final Path<?> path;
private final QuerydslBinding<? extends Path<?>> binding;
private final MultiValueBinding<S, T> binding;
public PathAndBinding(Path<?> path, QuerydslBinding<? extends Path<?>> binding) {
public PathAndBinding(S path, MultiValueBinding<S, T> binding) {
this.path = path;
this.binding = binding;
@@ -240,9 +368,32 @@ public class QuerydslBindings {
return path;
}
public QuerydslBinding<? extends Path<?>> getBinding() {
public MultiValueBinding<S, T> getBinding() {
return binding;
}
}
/**
* @author Oliver Gierke
*/
static class MultiValueBindingAdapter<T extends Path<? extends S>, S> implements MultiValueBinding<T, S> {
private final SingleValueBinding<T, S> delegate;
/**
* @param delegate
*/
public MultiValueBindingAdapter(SingleValueBinding<T, S> 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<? extends S> value) {
return delegate.bind(path, value.iterator().next());
}
}
}

View File

@@ -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:
* <ul>
* <li><i>{@literal null}</i> as {@link SimpleExpression#isNull()}.</li>
* <li><i>{@link java.lang.Object}</i> as {@link SimpleExpression#eq()} on simple properties.</li>
@@ -32,9 +33,10 @@ import com.mysema.query.types.path.CollectionPathBase;
* </ul>
*
* @author Christoph Strobl
* @author Oliver Gierke
* @since 1.11
*/
class QuerydslDefaultBinding implements QuerydslBinding<Path<?>> {
class QuerydslDefaultBinding implements MultiValueBinding<Path<? extends Object>, Object> {
/*
* (non-Javadoc)
@@ -42,27 +44,28 @@ class QuerydslDefaultBinding implements QuerydslBinding<Path<?>> {
*/
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public Predicate bind(Path<?> path, Object source) {
public Predicate bind(Path<?> path, Collection<? extends Object> 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()));
}
}

View File

@@ -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<? extends QuerydslBindings> bindingsType = parameter.getParameterAnnotation(QuerydslPredicate.class)
.bindings();
return beanFactory != null ? beanFactory.createBean(bindingsType) : BeanUtils.instantiateClass(bindingsType);
}
}

View File

@@ -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<PropertyPath, Path<?>> 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<PropertyPath, Path<?>>();
}
/**
@@ -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<Object> value = convertToPropertyPathSpecificType(propertyValue.getValue(), propertyPath);
builder.and(invokeBinding(propertyPath, bindings, value));
}
}
return builder.getValue();
}
private QuerydslBinding<? extends Path<?>> getPredicateBuilderForPath(PropertyPath dotPath,
QuerydslBindingContext context) {
@SuppressWarnings({ "unchecked", "rawtypes" })
private Predicate invokeBinding(PropertyPath dotPath, QuerydslBindings bindings, Collection<Object> value) {
QuerydslBinding<? extends Path<?>> 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<Object> 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<Object> potentiallyConvertCollectionValues(Collection<?> source, Class<?> elementType) {
Collection<Object> target = new ArrayList<Object>(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;
}
}
}

View File

@@ -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<T extends Path<? extends S>, 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);
}