diff --git a/src/main/java/org/springframework/data/web/config/SpringDataWebConfiguration.java b/src/main/java/org/springframework/data/web/config/SpringDataWebConfiguration.java index 644304c00..c7c153046 100644 --- a/src/main/java/org/springframework/data/web/config/SpringDataWebConfiguration.java +++ b/src/main/java/org/springframework/data/web/config/SpringDataWebConfiguration.java @@ -26,10 +26,12 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.ConversionService; import org.springframework.data.geo.format.DistanceFormatter; import org.springframework.data.geo.format.PointFormatter; +import org.springframework.data.querydsl.QueryDslUtils; import org.springframework.data.repository.support.DomainClassConverter; import org.springframework.data.web.PageableHandlerMethodArgumentResolver; import org.springframework.data.web.ProxyingHandlerMethodArgumentResolver; import org.springframework.data.web.SortHandlerMethodArgumentResolver; +import org.springframework.data.web.querydsl.QuerydslPredicateArgumentResolver; import org.springframework.format.FormatterRegistry; import org.springframework.format.support.FormattingConversionService; import org.springframework.web.method.support.HandlerMethodArgumentResolver; @@ -97,6 +99,10 @@ public class SpringDataWebConfiguration extends WebMvcConfigurerAdapter { argumentResolvers.add(sortResolver()); argumentResolvers.add(pageableResolver()); + if (QueryDslUtils.QUERY_DSL_PRESENT) { + argumentResolvers.add(new QuerydslPredicateArgumentResolver(conversionService.getObject())); + } + ProxyingHandlerMethodArgumentResolver resolver = new ProxyingHandlerMethodArgumentResolver( conversionService.getObject()); resolver.setBeanFactory(context); diff --git a/src/main/java/org/springframework/data/web/querydsl/QuerydslBinding.java b/src/main/java/org/springframework/data/web/querydsl/QuerydslBinding.java new file mode 100644 index 000000000..1b324bb59 --- /dev/null +++ b/src/main/java/org/springframework/data/web/querydsl/QuerydslBinding.java @@ -0,0 +1,36 @@ +/* + * 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 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 + */ +public interface QuerydslBinding> { + + /** + * @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> 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> 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> 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> 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> binding; + + public PathAndBinding(Path path, QuerydslBinding> binding) { + + this.path = path; + this.binding = binding; + } + + public Path getPath() { + return path; + } + + public QuerydslBinding> 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 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> getPredicateBuilderForPath(PropertyPath dotPath, + QuerydslBindingContext context) { + + QuerydslBinding> 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 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"))); + } +}