> {
+
+ /**
+ * @param path {@link Path} to the property. Must not be {@literal null}.
+ * @param value
+ * @return
+ */
+ Predicate bind(T path, Object 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
new file mode 100644
index 000000000..5a182377d
--- /dev/null
+++ b/src/main/java/org/springframework/data/web/querydsl/QuerydslBindingContext.java
@@ -0,0 +1,124 @@
+/*
+ * 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();
+ }
+}
diff --git a/src/main/java/org/springframework/data/web/querydsl/QuerydslBindings.java b/src/main/java/org/springframework/data/web/querydsl/QuerydslBindings.java
new file mode 100644
index 000000000..f0bbb3c71
--- /dev/null
+++ b/src/main/java/org/springframework/data/web/querydsl/QuerydslBindings.java
@@ -0,0 +1,248 @@
+/*
+ * 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 java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.data.mapping.PropertyPath;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+import com.mysema.query.types.Path;
+
+/**
+ * {@link QuerydslBindings} allows definition of path specific {@link QuerydslBinding}. *
+ *
+ *
+ *
+ * new QuerydslBindings() {
+ * {
+ * bind(QUser.user.address.city, (path, value) -> path.like(value.toString()));
+ * bind(new StringPath("address.city"), (path, value) -> path.like(value.toString()));
+ * }
+ * }
+ *
+ *
+ *
+ * @author Christoph Strobl
+ * @since 1.11
+ */
+public class QuerydslBindings {
+
+ private final Map pathSpecs;
+ private final Map, PathAndBinding> typeSpecs;
+ private Set whiteList;
+ private final Set blackList;
+
+ public QuerydslBindings() {
+
+ this.pathSpecs = new LinkedHashMap();
+ this.typeSpecs = new LinkedHashMap, PathAndBinding>();
+ this.whiteList = new HashSet();
+ this.blackList = new HashSet();
+ }
+
+ /**
+ * @param path
+ * @param builder
+ */
+ 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;
+ }
+
+ /**
+ * @param type
+ * @param builder
+ */
+ protected QuerydslBindings bind(Class type, QuerydslBinding extends Path> builder) {
+
+ Assert.notNull(type, "Cannot bind to null type!");
+ this.typeSpecs.put(type, new PathAndBinding(null, builder));
+ return this;
+ }
+
+ /**
+ * @param path
+ * @param builder
+ */
+ protected > QuerydslBindings bind(T path, QuerydslBinding builder) {
+
+ Assert.notNull(path, "Cannot bind to null path!");
+ this.pathSpecs.put(extractPropertyPath(path), new PathAndBinding(path, builder));
+ return this;
+ }
+
+ /**
+ * 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}.
+ *
+ * @param properties
+ */
+ protected QuerydslBindings excluding(String... properties) {
+ this.blackList.addAll(Arrays.asList(properties));
+ return this;
+ }
+
+ /**
+ * Include properties for binding.
+ * Include the property considered a binding candidate.
+ *
+ * @param properties
+ */
+ protected QuerydslBindings including(String... properties) {
+ this.whiteList.addAll(Arrays.asList(properties));
+ return this;
+ }
+
+ /**
+ * Checks if a given {@link PropertyPath} should be visible for binding values.
+ *
+ * @param path
+ * @return
+ */
+ public boolean isPathVisible(PropertyPath path) {
+
+ List segments = Arrays.asList(path.toDotPath().split("\\."));
+
+ for (int i = 1; i <= segments.size(); i++) {
+
+ if (!isPathVisible(StringUtils.collectionToDelimitedString(segments.subList(0, i), "."))) {
+
+ // check if full path is on whitelist if though partial one is not
+ if (!whiteList.isEmpty()) {
+ return whiteList.contains(path.toDotPath());
+ }
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ 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;
+ }
+
+ /**
+ * 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.
+ *
+ * @param path must not 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) {
+
+ if (path == null) {
+ return "";
+ }
+
+ if (path.getMetadata().getParent() != null && !path.getMetadata().getParent().getMetadata().isRoot()) {
+ return extractPropertyPath(path.getMetadata().getParent()) + "." + path.getMetadata().getName();
+ }
+
+ return path.getMetadata().getName();
+
+ }
+
+ /**
+ * @author Christoph Strobl
+ * @since 1.11
+ */
+ private static class PathAndBinding {
+
+ private final Path> path;
+ private final QuerydslBinding extends Path>> binding;
+
+ public PathAndBinding(Path> path, QuerydslBinding extends Path>> binding) {
+
+ this.path = path;
+ this.binding = binding;
+ }
+
+ public Path> getPath() {
+ return path;
+ }
+
+ public QuerydslBinding extends Path>> getBinding() {
+ return binding;
+ }
+
+ }
+}
diff --git a/src/main/java/org/springframework/data/web/querydsl/QuerydslDefaultBinding.java b/src/main/java/org/springframework/data/web/querydsl/QuerydslDefaultBinding.java
new file mode 100644
index 000000000..631f7a219
--- /dev/null
+++ b/src/main/java/org/springframework/data/web/querydsl/QuerydslDefaultBinding.java
@@ -0,0 +1,68 @@
+/*
+ * 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 java.util.Collection;
+
+import com.mysema.query.types.Path;
+import com.mysema.query.types.Predicate;
+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:
+ *
+ * {@literal null} as {@link SimpleExpression#isNull()}.
+ * {@link java.lang.Object} as {@link SimpleExpression#eq()} on simple properties.
+ * {@link java.lang.Object} as {@link SimpleExpression#contains()} on collection properties.
+ * {@link java.util.Collection} as {@link SimpleExpression#in()} on simple properties.
+ *
+ *
+ * @author Christoph Strobl
+ * @since 1.11
+ */
+class QuerydslDefaultBinding implements QuerydslBinding> {
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.web.querydsl.QueryDslPredicateBuilder#buildPredicate(org.springframework.data.mapping.PropertyPath, java.lang.Object)
+ */
+ @Override
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public Predicate bind(Path> path, Object source) {
+
+ if (source == null && path instanceof SimpleExpression) {
+ return ((SimpleExpression) path).isNull();
+ }
+
+ if (path instanceof CollectionPathBase) {
+ return ((CollectionPathBase) path).contains(source);
+ }
+
+ if (path instanceof SimpleExpression) {
+
+ if (source instanceof Collection) {
+ return ((SimpleExpression) path).in((Collection) source);
+ }
+
+ return ((SimpleExpression) path).eq(source);
+ }
+
+ 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/QuerydslPredicate.java b/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicate.java
new file mode 100644
index 000000000..fa0bf2ff9
--- /dev/null
+++ b/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicate.java
@@ -0,0 +1,47 @@
+/*
+ * 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * {@link java.lang.annotation.Annotation} to specify aspects of {@link com.mysema.query.types.Predicate} used in Spring
+ * MVC {@link org.springframework.web.servlet.mvc.Controller}.
+ *
+ * @author Christoph Strobl
+ * @since 1.11
+ */
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface QuerydslPredicate {
+
+ /**
+ * Root type to be used for {@link com.mysema.query.types.Path}
+ *
+ * @return
+ */
+ Class> root() default Object.class;
+
+ /**
+ * Configuration class providing options on a per field base.
+ *
+ * @return
+ */
+ Class extends QuerydslBindings> bindings() default QuerydslBindings.class;
+}
diff --git a/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolver.java b/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolver.java
new file mode 100644
index 000000000..ac28b0b3f
--- /dev/null
+++ b/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolver.java
@@ -0,0 +1,103 @@
+/*
+ * 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.beans.BeanUtils;
+import org.springframework.beans.MutablePropertyValues;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.support.DefaultConversionService;
+import org.springframework.data.util.ClassTypeInformation;
+import org.springframework.data.util.TypeInformation;
+import org.springframework.web.bind.support.WebDataBinderFactory;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.method.support.ModelAndViewContainer;
+
+import com.mysema.query.types.Predicate;
+
+/**
+ * {@link HandlerMethodArgumentResolver} to allow injection of {@link com.mysema.query.types.Predicate} into Spring MVC
+ * controller methods.
+ *
+ * @author Christoph Strobl
+ * @since 1.11
+ */
+public class QuerydslPredicateArgumentResolver implements HandlerMethodArgumentResolver {
+
+ private final QuerydslPredicateBuilder predicateBuilder;
+ private final ConversionService conversionService;
+
+ /**
+ * @param conversionService Defaulted to {@link DefaultConversionService} if {@literal null}.
+ */
+ public QuerydslPredicateArgumentResolver(ConversionService conversionService) {
+
+ this.conversionService = conversionService == null ? new DefaultConversionService() : conversionService;
+ this.predicateBuilder = new QuerydslPredicateBuilder();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter)
+ */
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+
+ if (Predicate.class.equals(parameter.getParameterType())) {
+ return true;
+ }
+
+ 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()));
+ }
+
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @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,
+ 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);
+ }
+
+ private TypeInformation> extractTypeInfo(MethodParameter parameter) {
+
+ Class> type = parameter.getParameterAnnotation(QuerydslPredicate.class).root();
+
+ return type == Object.class ? ClassTypeInformation.fromReturnTypeOf(parameter.getMethod()) : ClassTypeInformation
+ .from(type);
+ }
+
+ private QuerydslBindings extractBindings(MethodParameter parameter) throws InstantiationException,
+ IllegalAccessException {
+
+ return BeanUtils.instantiateClass(parameter.getParameterAnnotation(QuerydslPredicate.class).bindings());
+ }
+}
diff --git a/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateBuilder.java b/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateBuilder.java
new file mode 100644
index 000000000..93507738f
--- /dev/null
+++ b/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateBuilder.java
@@ -0,0 +1,179 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.springframework.beans.PropertyValue;
+import org.springframework.beans.PropertyValues;
+import org.springframework.data.mapping.PropertyPath;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.ObjectUtils;
+
+import com.mysema.query.BooleanBuilder;
+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
+ * @since 1.11
+ */
+class QuerydslPredicateBuilder {
+
+ private final QuerydslBinding> defaultBinding;
+
+ public QuerydslPredicateBuilder() {
+ this.defaultBinding = new QuerydslDefaultBinding();
+ }
+
+ /**
+ * @param values
+ * @param context
+ * @return
+ */
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public Predicate getPredicate(PropertyValues values, QuerydslBindingContext context) {
+
+ Assert.notNull(context, "Context must not be null!");
+
+ if (values.isEmpty()) {
+ return new BooleanBuilder();
+ }
+
+ BooleanBuilder builder = new BooleanBuilder();
+
+ for (PropertyValue propertyValue : values.getPropertyValues()) {
+
+ PropertyPath propertyPath = PropertyPath.from(propertyValue.getName(), context.getTargetType());
+
+ if (context.isPathVisible(propertyPath)) {
+
+ Object value = convertToPropertyPathSpecificType(propertyValue.getValue(), propertyPath, context);
+ QuerydslBinding binding = getPredicateBuilderForPath(propertyPath, context);
+
+ builder.and(binding.bind(getPath(propertyPath, context), value));
+ }
+ }
+
+ return builder.getValue();
+ }
+
+ private QuerydslBinding extends Path>> getPredicateBuilderForPath(PropertyPath dotPath,
+ QuerydslBindingContext context) {
+
+ QuerydslBinding extends Path>> binding = context.getBindingForPath(dotPath);
+ return binding == null ? defaultBinding : binding;
+ }
+
+ private Path> getPath(PropertyPath propertyPath, QuerydslBindingContext context) {
+
+ Path> path = context.getPathForPropertyPath(propertyPath);
+ if (path != null) {
+ return path;
+ }
+
+ return new PropertyPathPathBuilder(context.getTargetType()).forProperty(propertyPath);
+ }
+
+ private Object convertToPropertyPathSpecificType(Object source, PropertyPath path, QuerydslBindingContext context) {
+
+ 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;
+ }
+ }
+
+ if (value instanceof Collection) {
+ return potentiallyConvertCollectionValues((Collection>) value, path.getType(), context);
+ }
+
+ return potentiallyConvertValue(value, path.getType(), context);
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ private Collection> potentiallyConvertCollectionValues(Collection> source, Class> targetType,
+ QuerydslBindingContext context) {
+
+ Collection target = new ArrayList(source.size());
+ for (Object value : source) {
+ target.add(potentiallyConvertValue(value, targetType, context));
+ }
+
+ return target;
+ }
+
+ private Object potentiallyConvertValue(Object source, Class> targetType, QuerydslBindingContext context) {
+
+ return context.getConversionService().canConvert(source.getClass(), targetType)
+ ? context.getConversionService().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/test/java/org/springframework/data/querydsl/Address.java b/src/test/java/org/springframework/data/querydsl/Address.java
new file mode 100644
index 000000000..38bb0a604
--- /dev/null
+++ b/src/test/java/org/springframework/data/querydsl/Address.java
@@ -0,0 +1,27 @@
+/*
+ * 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 com.mysema.query.annotations.QueryEntity;
+
+/**
+ * @author Christoph Strobl
+ */
+@QueryEntity
+public class Address {
+ String street;
+ String city;
+}
diff --git a/src/test/java/org/springframework/data/querydsl/User.java b/src/test/java/org/springframework/data/querydsl/User.java
index 09245a728..ba7b52a83 100644
--- a/src/test/java/org/springframework/data/querydsl/User.java
+++ b/src/test/java/org/springframework/data/querydsl/User.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2014 the original author or authors.
+ * Copyright 2011-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.
@@ -16,16 +16,23 @@
package org.springframework.data.querydsl;
import java.util.Date;
+import java.util.List;
import com.mysema.query.annotations.QueryEntity;
/**
* @author Oliver Gierke
* @author Thomas Darimont
+ * @author Christoph Strobl
*/
@QueryEntity
public class User {
+
String firstname;
String lastname;
Date dateOfBirth;
+
+ Address address;
+ List nickNames;
+ Long inceptionYear;
}
diff --git a/src/test/java/org/springframework/data/web/querydsl/QuerydslBindingsUnitTests.java b/src/test/java/org/springframework/data/web/querydsl/QuerydslBindingsUnitTests.java
new file mode 100644
index 000000000..ba0bdcc76
--- /dev/null
+++ b/src/test/java/org/springframework/data/web/querydsl/QuerydslBindingsUnitTests.java
@@ -0,0 +1,242 @@
+/*
+ * 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 static org.hamcrest.core.Is.*;
+import static org.hamcrest.core.IsNull.*;
+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.data.mapping.PropertyPath;
+import org.springframework.data.querydsl.Address;
+import org.springframework.data.querydsl.QAddress;
+import org.springframework.data.querydsl.User;
+import org.springframework.data.util.ClassTypeInformation;
+
+import com.mysema.query.types.Predicate;
+import com.mysema.query.types.path.StringPath;
+
+/**
+ * Unit tests for {@link QuerydslBindings}.
+ *
+ * @author Oliver Gierke
+ * @author Christoph Strobl
+ */
+public class QuerydslBindingsUnitTests {
+
+ private QuerydslPredicateBuilder builder;
+ private QuerydslBindings typeBasedBindings;
+ private QuerydslBindings pathBasedBindings;
+
+ @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());
+ }
+ });
+ }
+ };
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void getBindingForPathShouldThrowErrorWhenPathIsNull() {
+ pathBasedBindings.getBindingForPath(null);
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void getBindingForPathShouldReturnNullWhenNoSpecifcBindingAvailable() {
+ assertThat(pathBasedBindings.getBindingForPath(PropertyPath.from("lastname", User.class)), nullValue());
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void getBindingForPathShouldReturnSpeficicBindingWhenAvailable() {
+ assertThat(pathBasedBindings.getBindingForPath(PropertyPath.from("firstname", User.class)), notNullValue());
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void getBindingForPathShouldReturnSpeficicBindingForNestedElementsWhenAvailable() {
+ assertThat(pathBasedBindings.getBindingForPath(PropertyPath.from("address.street", User.class)), notNullValue());
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void getBindingForPathShouldReturnSpeficicBindingForTypes() {
+ assertThat(typeBasedBindings.getBindingForPath(PropertyPath.from("address.street", User.class)), notNullValue());
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void getBindingForPathShouldIgnoreSpeficicBindingForTypesWhenTypesDoNotMatch() {
+ assertThat(typeBasedBindings.getBindingForPath(PropertyPath.from("inceptionYear", User.class)), nullValue());
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void isPathVisibleShouldReturnTrueWhenNoRestrictionDefined() {
+ assertThat(typeBasedBindings.isPathVisible(PropertyPath.from("inceptionYear", User.class)), is(true));
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void isPathVisibleShouldReturnTrueWhenPathContainedInIncluding() {
+
+ assertThat(
+ new QuerydslBindings().including("inceptionYear").isPathVisible(PropertyPath.from("inceptionYear", User.class)),
+ is(true));
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void isPathVisibleShouldReturnFalseWhenPathNotContainedInIncluding() {
+
+ assertThat(
+ new QuerydslBindings().including("inceptionYear").isPathVisible(PropertyPath.from("firstname", User.class)),
+ is(false));
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void isPathVisibleShouldReturnFalseWhenPathContainedInExcluding() {
+
+ assertThat(
+ new QuerydslBindings().excluding("inceptionYear").isPathVisible(PropertyPath.from("inceptionYear", User.class)),
+ is(false));
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void isPathVisibleShouldReturnTrueWhenPathNotContainedInExcluding() {
+
+ assertThat(
+ new QuerydslBindings().excluding("inceptionYear").isPathVisible(PropertyPath.from("firstname", User.class)),
+ is(true));
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void isPathVisibleShouldReturnTrueWhenPathContainedInExcludingAndIncluding() {
+
+ assertThat(
+ new QuerydslBindings().excluding("inceptionYear").isPathVisible(PropertyPath.from("firstname", User.class)),
+ is(true));
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void isPathVisibleShouldReturnFalseWhenPartialPathContainedInExcluding() {
+
+ assertThat(
+ new QuerydslBindings().excluding("address").isPathVisible(PropertyPath.from("address.city", User.class)),
+ is(false));
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void isPathVisibleShouldReturnTrueWhenPartialPathContainedInExcludingButConcretePathIsIncluded() {
+
+ assertThat(
+ new QuerydslBindings().excluding("address").including("address.city")
+ .isPathVisible(PropertyPath.from("address.city", User.class)), is(true));
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void isPathVisibleShouldReturnFalseWhenPartialPathContainedInExcludingAndConcretePathToDifferentPropertyIsIncluded() {
+
+ assertThat(
+ new QuerydslBindings().excluding("address").including("address.city")
+ .isPathVisible(PropertyPath.from("address.street", User.class)), is(false));
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void usesTypeBasedBindingIfConfigured() {
+
+ MutablePropertyValues values = new MutablePropertyValues(Collections.singletonMap("city", "Dresden"));
+
+ QuerydslBindingContext context = new QuerydslBindingContext(ClassTypeInformation.from(Address.class),
+ this.typeBasedBindings, null);
+
+ assertThat(builder.getPredicate(values, context), is((Predicate) QAddress.address.city.contains("Dresden")));
+ }
+}
diff --git a/src/test/java/org/springframework/data/web/querydsl/QuerydslDefaultBindingUnitTests.java b/src/test/java/org/springframework/data/web/querydsl/QuerydslDefaultBindingUnitTests.java
new file mode 100644
index 000000000..c2473bb77
--- /dev/null
+++ b/src/test/java/org/springframework/data/web/querydsl/QuerydslDefaultBindingUnitTests.java
@@ -0,0 +1,107 @@
+/*
+ * 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 static org.hamcrest.core.Is.*;
+
+import java.util.Arrays;
+
+import org.hamcrest.Matcher;
+import org.hamcrest.core.Is;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.data.querydsl.QUser;
+
+import com.mysema.query.types.Expression;
+import com.mysema.query.types.Predicate;
+
+/**
+ * @author Christoph Strobl
+ */
+public class QuerydslDefaultBindingUnitTests {
+
+ QuerydslDefaultBinding builder;
+
+ @Before
+ public void setUp() {
+ builder = new QuerydslDefaultBinding();
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void shouldCreatePredicateCorrectlyWhenPropertyIsInRoot() {
+
+ Predicate predicate = builder.bind(QUser.user.firstname, "tam");
+
+ assertThat(predicate, is(QUser.user.firstname.eq("tam")));
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void shouldCreatePredicateCorrectlyWhenPropertyIsInNestedElement() {
+
+ Predicate predicate = builder.bind(QUser.user.address.city, "two rivers");
+
+ Assert.assertThat(predicate.toString(), is(QUser.user.address.city.eq("two rivers").toString()));
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void shouldCreatePredicateCorrectlyWhenValueIsNull() {
+
+ Predicate predicate = builder.bind(QUser.user.firstname, null);
+
+ assertThat(predicate, is(QUser.user.firstname.isNull()));
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void shouldCreatePredicateWithContainingWhenPropertyIsCollectionLikeAndValueIsObject() {
+
+ Predicate predicate = builder.bind(QUser.user.nickNames, "dragon reborn");
+
+ assertThat(predicate, is(QUser.user.nickNames.contains("dragon reborn")));
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void shouldCreatePredicateWithInWhenPropertyIsAnObjectAndValueIsACollection() {
+
+ Predicate predicate = builder.bind(QUser.user.firstname, Arrays.asList("dragon reborn", "shadowkiller"));
+
+ assertThat(predicate, is(QUser.user.firstname.in(Arrays.asList("dragon reborn", "shadowkiller"))));
+ }
+
+ /*
+ * just to satisfy generic type boundaries o_O
+ */
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ private void assertThat(Predicate predicate, Matcher extends Expression> matcher) {
+ Assert.assertThat((Expression) predicate, Is. is((Matcher) matcher));
+ }
+
+}
diff --git a/src/test/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolverUnitTests.java b/src/test/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolverUnitTests.java
new file mode 100644
index 000000000..5cbb9a0e0
--- /dev/null
+++ b/src/test/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolverUnitTests.java
@@ -0,0 +1,250 @@
+/*
+ * 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 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;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.querydsl.QUser;
+import org.springframework.data.querydsl.User;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.web.context.request.ServletWebRequest;
+
+import com.mysema.query.types.Predicate;
+import com.mysema.query.types.expr.BooleanExpression;
+import com.mysema.query.types.path.StringPath;
+
+/**
+ * @author Christoph Strobl
+ */
+public class QuerydslPredicateArgumentResolverUnitTests {
+
+ QuerydslPredicateArgumentResolver resolver;
+ MockHttpServletRequest request;
+
+ @Before
+ public void setUp() {
+
+ resolver = new QuerydslPredicateArgumentResolver(null);
+ request = new MockHttpServletRequest();
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void supportsParameterReturnsTrueWhenMethodParameterIsPredicateAndAnnotatedCorrectly() {
+ assertThat(resolver.supportsParameter(getMethodParameterFor("simpleFind", Predicate.class)), is(true));
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void supportsParameterReturnsTrueWhenMethodParameterIsPredicateButNotAnnotatedAsSuch() {
+ assertThat(resolver.supportsParameter(getMethodParameterFor("predicateWithoutAnnotation", Predicate.class)),
+ is(true));
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void supportsParameterShouldThrowExceptionWhenMethodParameterIsNoPredicateButAnnotatedAsSuch() {
+ resolver.supportsParameter(getMethodParameterFor("nonPredicateWithAnnotation", String.class));
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void supportsParameterReturnsFalseWhenMethodParameterIsNoPredicate() {
+ assertThat(resolver.supportsParameter(getMethodParameterFor("nonPredicateWithoutAnnotation", String.class)),
+ is(false));
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void resolveArgumentShouldCreateSingleStringParameterPredicateCorrectly() throws Exception {
+
+ request.addParameter("firstname", "rand");
+
+ Object predicate = (BooleanExpression) resolver.resolveArgument(
+ getMethodParameterFor("simpleFind", Predicate.class), null, new ServletWebRequest(request), null);
+
+ assertThat(predicate, Is. is(QUser.user.firstname.eq("rand")));
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void resolveArgumentShouldCreateMultipleParametersPredicateCorrectly() throws Exception {
+
+ request.addParameter("firstname", "rand");
+ request.addParameter("lastname", "al'thor");
+
+ Object 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"))));
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void resolveArgumentShouldCreateNestedObjectPredicateCorrectly() throws Exception {
+
+ request.addParameter("address.city", "two rivers");
+
+ Object predicate = resolver.resolveArgument(getMethodParameterFor("simpleFind", Predicate.class), null,
+ new ServletWebRequest(request), null);
+
+ assertThat(predicate.toString(), is(QUser.user.address.city.eq("two rivers").toString()));
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void resolveArgumentShouldResolveTypePropertyFromPageCorrectly() throws Exception {
+
+ request.addParameter("address.city", "tar valon");
+
+ Object 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()));
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void resolveArgumentShouldHonorCustomSpeficifcation() throws Exception {
+
+ request.addParameter("firstname", "egwene");
+ request.addParameter("lastname", "al'vere");
+
+ Object 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()));
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void shouldCreatePredicateForNonStringPropertyCorrectly() throws Exception {
+
+ request.addParameter("inceptionYear", "978");
+
+ Object predicate = (BooleanExpression) resolver.resolveArgument(
+ getMethodParameterFor("specificFind", Predicate.class), null, new ServletWebRequest(request), null);
+
+ assertThat(predicate, Is. is(QUser.user.inceptionYear.eq(978L)));
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void shouldCreatePredicateForNonStringListPropertyCorrectly() throws Exception {
+
+ request.addParameter("inceptionYear", new String[] { "978", "998" });
+
+ Object predicate = (BooleanExpression) resolver.resolveArgument(
+ getMethodParameterFor("specificFind", Predicate.class), null, new ServletWebRequest(request), null);
+
+ assertThat(predicate, Is. is(QUser.user.inceptionYear.in(978L, 998L)));
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void shouldExcludePorpertiesCorrectly() throws Exception {
+
+ request.addParameter("address.street", "downhill");
+ request.addParameter("inceptionYear", "973");
+
+ Object predicate = resolver.resolveArgument(getMethodParameterFor("specificFind", Predicate.class), null,
+ new ServletWebRequest(request), null);
+
+ assertThat(predicate.toString(), is(QUser.user.inceptionYear.eq(973L).toString()));
+ }
+
+ private MethodParameter getMethodParameterFor(String methodName, Class>... args) throws RuntimeException {
+
+ try {
+ return new MethodParameter(Sample.class.getMethod(methodName, args), 0);
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ static class SpecificBinding extends QuerydslBindings {
+
+ public SpecificBinding() {
+
+ bind("firstname", new QuerydslBinding() {
+
+ @Override
+ public Predicate bind(StringPath path, Object value) {
+ return path.eq(value.toString().toUpperCase());
+ }
+ });
+
+ bind(QUser.user.lastname, new QuerydslBinding() {
+
+ @Override
+ public Predicate bind(StringPath path, Object value) {
+ return path.toLowerCase().eq(value.toString());
+ }
+ });
+
+ excluding("address");
+ }
+ }
+
+ static interface Sample {
+
+ User predicateWithoutAnnotation(Predicate predicate);
+
+ User nonPredicateWithAnnotation(@QuerydslPredicate String predicate);
+
+ User nonPredicateWithoutAnnotation(String predicate);
+
+ User simpleFind(@QuerydslPredicate Predicate predicate);
+
+ Page pagedFind(@QuerydslPredicate Predicate predicate, Pageable page);
+
+ User specificFind(@QuerydslPredicate(bindings = SpecificBinding.class) Predicate predicate);
+ }
+
+}
diff --git a/src/test/java/org/springframework/data/web/querydsl/QuerydslPredicateBuilderUnitTests.java b/src/test/java/org/springframework/data/web/querydsl/QuerydslPredicateBuilderUnitTests.java
new file mode 100644
index 000000000..19ef24907
--- /dev/null
+++ b/src/test/java/org/springframework/data/web/querydsl/QuerydslPredicateBuilderUnitTests.java
@@ -0,0 +1,76 @@
+/*
+ * 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 static org.hamcrest.core.Is.*;
+import static org.junit.Assert.*;
+
+import java.util.Collections;
+
+import org.hamcrest.core.Is;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.beans.MutablePropertyValues;
+import org.springframework.data.querydsl.QUser;
+import org.springframework.data.querydsl.User;
+import org.springframework.data.util.ClassTypeInformation;
+
+import com.mysema.query.BooleanBuilder;
+import com.mysema.query.types.Predicate;
+
+/**
+ * @author Christoph Strobl
+ */
+public class QuerydslPredicateBuilderUnitTests {
+
+ QuerydslPredicateBuilder builder;
+
+ @Before
+ public void setUp() {
+ builder = new QuerydslPredicateBuilder();
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void getPredicateShouldThrowErrorWhenBindingContextIsNull() {
+ builder.getPredicate(new MutablePropertyValues(), null);
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @Test
+ public void getPredicateShouldReturnEmptyPredicateWhenPropertiesAreEmpty() {
+
+ assertThat(builder.getPredicate(new MutablePropertyValues(), new QuerydslBindingContext(
+ ClassTypeInformation.OBJECT, null, null)), is((Predicate) new BooleanBuilder()));
+ }
+
+ /**
+ * @see DATACMNS-669
+ */
+ @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));
+
+ assertThat(predicate, Is. is(QUser.user.firstname.eq("rand")));
+ }
+}