Browse Source
Added HandlerMethodArgumentResolver implementations for Pageable and Sort exposing a new default set of properties (page, size, sort) to resolve pagination and sorting information from the request. To mimic the legacy behavior we expose a (deprecated) LEGACY constant in PageableHandlerMethodArgumentResolver. Clients should move to the new properties structure ASAP. Added unit tests to verify old and new defaulting behavior. Introduced new annotations @SortDefault (with @SortDefaults wrapper annotation) and @PageableDefault (superseding the legacy @PageableDefaults). The new annotations have more speaking attribute names and the HMAR implementations transparently alias the generic value attribute into a more semantic one (size). The HMAR implementations implement Spring HATEOAS' UriComponentsContributor to be able to turn Pageable / Sort instances back into URIs created through the MethodLinkBuilderFactory API in Spring HATEOAS. Extracted common API between legacy PageableArgumentResolver and PageableHandlerMethodArgumentResolver into common helper class. Upgraded to Spring Hateoas 0.5.0.BUILD-SNAPSHOT.pull/25/head
16 changed files with 1619 additions and 438 deletions
@ -0,0 +1,73 @@
@@ -0,0 +1,73 @@
|
||||
/* |
||||
* Copyright 2013 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; |
||||
|
||||
import java.lang.annotation.Documented; |
||||
import java.lang.annotation.ElementType; |
||||
import java.lang.annotation.Retention; |
||||
import java.lang.annotation.RetentionPolicy; |
||||
import java.lang.annotation.Target; |
||||
|
||||
import org.springframework.data.domain.Sort.Direction; |
||||
import org.springframework.data.web.SortDefault.SortDefaults; |
||||
|
||||
/** |
||||
* Annotation to set defaults when injecting a {@link org.springframework.data.domain.Pageable} into a controller |
||||
* method. Instead of configuring {@link #sort()} and {@link #direction()} you can also use {@link SortDefault} or |
||||
* {@link SortDefaults}. |
||||
* |
||||
* @since 1.6 |
||||
* @author Oliver Gierke |
||||
*/ |
||||
@Documented |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@Target(ElementType.PARAMETER) |
||||
public @interface PageableDefault { |
||||
|
||||
/** |
||||
* Alias for {@link #size()}. Prefer to use the {@link #size()} method as it makes the annotation declaration more |
||||
* expressive and you'll probably want to configure the {@link #page()} anyway. |
||||
* |
||||
* @return |
||||
*/ |
||||
int value() default 10; |
||||
|
||||
/** |
||||
* The default-size the injected {@link org.springframework.data.domain.Pageable} should get if no corresponding |
||||
* parameter defined in request (default is 10). |
||||
*/ |
||||
int size() default 10; |
||||
|
||||
/** |
||||
* The default-pagenumber the injected {@link org.springframework.data.domain.Pageable} should get if no corresponding |
||||
* parameter defined in request (default is 0). |
||||
*/ |
||||
int page() default 0; |
||||
|
||||
/** |
||||
* The properties to sort by by default. If unset, no sorting will be applied at all. |
||||
* |
||||
* @return |
||||
*/ |
||||
String[] sort() default {}; |
||||
|
||||
/** |
||||
* The direction to sort by. Defaults to {@link Direction#ASC}. |
||||
* |
||||
* @return |
||||
*/ |
||||
Direction direction() default Direction.ASC; |
||||
} |
||||
@ -1,295 +0,0 @@
@@ -1,295 +0,0 @@
|
||||
/* |
||||
* Copyright 2013 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; |
||||
|
||||
import java.beans.PropertyEditorSupport; |
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.Method; |
||||
import java.util.HashSet; |
||||
import java.util.Set; |
||||
|
||||
import javax.servlet.ServletRequest; |
||||
|
||||
import org.springframework.beans.PropertyValue; |
||||
import org.springframework.beans.PropertyValues; |
||||
import org.springframework.beans.factory.annotation.Qualifier; |
||||
import org.springframework.core.MethodParameter; |
||||
import org.springframework.data.domain.PageRequest; |
||||
import org.springframework.data.domain.Pageable; |
||||
import org.springframework.data.domain.Sort; |
||||
import org.springframework.data.domain.Sort.Direction; |
||||
import org.springframework.validation.DataBinder; |
||||
import org.springframework.web.bind.ServletRequestDataBinder; |
||||
import org.springframework.web.bind.ServletRequestParameterPropertyValues; |
||||
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; |
||||
|
||||
/** |
||||
* Extracts paging information from web requests and thus allows injecting {@link Pageable} instances into controller |
||||
* methods. Request properties to be parsed can be configured. Default configuration uses request properties beginning |
||||
* with {@link #DEFAULT_PREFIX}{@link #DEFAULT_SEPARATOR}. |
||||
* |
||||
* @since 1.6 |
||||
* @author Oliver Gierke |
||||
*/ |
||||
public class PageableHandlerArgumentResolver implements HandlerMethodArgumentResolver { |
||||
|
||||
private static final Pageable DEFAULT_PAGE_REQUEST = new PageRequest(0, 10); |
||||
private static final String DEFAULT_PREFIX = "page"; |
||||
private static final String DEFAULT_SEPARATOR = "."; |
||||
|
||||
private Pageable fallbackPagable = DEFAULT_PAGE_REQUEST; |
||||
private String prefix = DEFAULT_PREFIX; |
||||
private String separator = DEFAULT_SEPARATOR; |
||||
|
||||
/** |
||||
* Setter to configure a fallback instance of {@link Pageable} that is being used to back missing parameters. Defaults |
||||
* to {@link #DEFAULT_PAGE_REQUEST}. |
||||
* |
||||
* @param fallbackPagable the fallbackPagable to set |
||||
*/ |
||||
public void setFallbackPagable(Pageable fallbackPagable) { |
||||
this.fallbackPagable = null == fallbackPagable ? DEFAULT_PAGE_REQUEST : fallbackPagable; |
||||
} |
||||
|
||||
/** |
||||
* Setter to configure the prefix of request parameters to be used to retrieve paging information. Defaults to |
||||
* {@link #DEFAULT_PREFIX}. |
||||
* |
||||
* @param prefix the prefix to set |
||||
*/ |
||||
public void setPrefix(String prefix) { |
||||
this.prefix = null == prefix ? DEFAULT_PREFIX : prefix; |
||||
} |
||||
|
||||
/** |
||||
* Setter to configure the separator between prefix and actual property value. Defaults to {@link #DEFAULT_SEPARATOR}. |
||||
* |
||||
* @param separator the separator to set. Will default to {@link #DEFAULT_SEPEARATOR} if set to {@literal null}. |
||||
*/ |
||||
public void setSeparator(String separator) { |
||||
this.separator = null == separator ? DEFAULT_SEPARATOR : separator; |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter) |
||||
*/ |
||||
public boolean supportsParameter(MethodParameter parameter) { |
||||
return Pageable.class.equals(parameter.getParameterType()); |
||||
} |
||||
|
||||
/* |
||||
* (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) |
||||
*/ |
||||
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer mavContainer, |
||||
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { |
||||
|
||||
assertPageableUniqueness(methodParameter); |
||||
|
||||
Pageable request = getDefaultFromAnnotationOrFallback(methodParameter); |
||||
ServletRequest servletRequest = (ServletRequest) webRequest.getNativeRequest(); |
||||
PropertyValues propertyValues = new ServletRequestParameterPropertyValues(servletRequest, |
||||
getPrefix(methodParameter), separator); |
||||
|
||||
DataBinder binder = new ServletRequestDataBinder(request); |
||||
|
||||
binder.initDirectFieldAccess(); |
||||
binder.registerCustomEditor(Sort.class, new SortPropertyEditor("sort.dir", propertyValues)); |
||||
binder.bind(propertyValues); |
||||
|
||||
if (request.getPageNumber() > 0) { |
||||
request = new PageRequest(request.getPageNumber() - 1, request.getPageSize(), request.getSort()); |
||||
} |
||||
|
||||
return request; |
||||
} |
||||
|
||||
private Pageable getDefaultFromAnnotationOrFallback(MethodParameter methodParameter) { |
||||
|
||||
// search for PageableDefaults annotation
|
||||
for (Annotation annotation : methodParameter.getParameterAnnotations()) { |
||||
if (annotation instanceof PageableDefaults) { |
||||
return getDefaultPageRequestFrom((PageableDefaults) annotation); |
||||
} |
||||
} |
||||
|
||||
// Construct request with fallback request to ensure sensible
|
||||
// default values. Create fresh copy as Spring will manipulate the
|
||||
// instance under the covers
|
||||
return new PageRequest(fallbackPagable.getPageNumber(), fallbackPagable.getPageSize(), fallbackPagable.getSort()); |
||||
} |
||||
|
||||
private static Pageable getDefaultPageRequestFrom(PageableDefaults defaults) { |
||||
|
||||
// +1 is because we substract 1 later
|
||||
int defaultPageNumber = defaults.pageNumber() + 1; |
||||
int defaultPageSize = defaults.value(); |
||||
|
||||
if (defaults.sort().length == 0) { |
||||
return new PageRequest(defaultPageNumber, defaultPageSize); |
||||
} |
||||
|
||||
return new PageRequest(defaultPageNumber, defaultPageSize, defaults.sortDir(), defaults.sort()); |
||||
} |
||||
|
||||
/** |
||||
* Resolves the prefix to use to bind properties from. Will prepend a possible {@link Qualifier} if available or |
||||
* return the configured prefix otherwise. |
||||
* |
||||
* @param parameter |
||||
* @return |
||||
*/ |
||||
private String getPrefix(MethodParameter parameter) { |
||||
|
||||
for (Annotation annotation : parameter.getParameterAnnotations()) { |
||||
if (annotation instanceof Qualifier) { |
||||
return new StringBuilder(((Qualifier) annotation).value()).append("_").append(prefix).toString(); |
||||
} |
||||
} |
||||
|
||||
return prefix; |
||||
} |
||||
|
||||
/** |
||||
* Asserts uniqueness of all {@link Pageable} parameters of the method of the given {@link MethodParameter}. |
||||
* |
||||
* @param parameter |
||||
*/ |
||||
private void assertPageableUniqueness(MethodParameter parameter) { |
||||
|
||||
Method method = parameter.getMethod(); |
||||
|
||||
if (containsMoreThanOnePageableParameter(method)) { |
||||
Annotation[][] annotations = method.getParameterAnnotations(); |
||||
assertQualifiersFor(method.getParameterTypes(), annotations); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Returns whether the given {@link Method} has more than one {@link Pageable} parameter. |
||||
* |
||||
* @param method |
||||
* @return |
||||
*/ |
||||
private boolean containsMoreThanOnePageableParameter(Method method) { |
||||
|
||||
boolean pageableFound = false; |
||||
|
||||
for (Class<?> type : method.getParameterTypes()) { |
||||
|
||||
if (pageableFound && type.equals(Pageable.class)) { |
||||
return true; |
||||
} |
||||
|
||||
if (type.equals(Pageable.class)) { |
||||
pageableFound = true; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* Asserts that every {@link Pageable} parameter of the given parameters carries an {@link Qualifier} annotation to |
||||
* distinguish them from each other. |
||||
* |
||||
* @param parameterTypes |
||||
* @param annotations |
||||
*/ |
||||
private void assertQualifiersFor(Class<?>[] parameterTypes, Annotation[][] annotations) { |
||||
|
||||
Set<String> values = new HashSet<String>(); |
||||
|
||||
for (int i = 0; i < annotations.length; i++) { |
||||
|
||||
if (Pageable.class.equals(parameterTypes[i])) { |
||||
|
||||
Qualifier qualifier = findAnnotation(annotations[i]); |
||||
|
||||
if (null == qualifier) { |
||||
throw new IllegalStateException( |
||||
"Ambiguous Pageable arguments in handler method. If you use multiple parameters of type Pageable you need to qualify them with @Qualifier"); |
||||
} |
||||
|
||||
if (values.contains(qualifier.value())) { |
||||
throw new IllegalStateException("Values of the user Qualifiers must be unique!"); |
||||
} |
||||
|
||||
values.add(qualifier.value()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Returns a {@link Qualifier} annotation from the given array of {@link Annotation}s. Returns {@literal null} if the |
||||
* array does not contain a {@link Qualifier} annotation. |
||||
* |
||||
* @param annotations |
||||
* @return |
||||
*/ |
||||
private Qualifier findAnnotation(Annotation[] annotations) { |
||||
|
||||
for (Annotation annotation : annotations) { |
||||
if (annotation instanceof Qualifier) { |
||||
return (Qualifier) annotation; |
||||
} |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
/** |
||||
* {@link java.beans.PropertyEditor} to create {@link Sort} instances from textual representations. The implementation |
||||
* interprets the string as a comma separated list where the first entry is the sort direction ( {@code asc}, |
||||
* {@code desc}) followed by the properties to sort by. |
||||
* |
||||
* @author Oliver Gierke |
||||
*/ |
||||
private static class SortPropertyEditor extends PropertyEditorSupport { |
||||
|
||||
private final String orderProperty; |
||||
private final PropertyValues values; |
||||
|
||||
/** |
||||
* Creates a new {@link SortPropertyEditor}. |
||||
* |
||||
* @param orderProperty |
||||
* @param values |
||||
*/ |
||||
public SortPropertyEditor(String orderProperty, PropertyValues values) { |
||||
|
||||
this.orderProperty = orderProperty; |
||||
this.values = values; |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see java.beans.PropertyEditorSupport#setAsText(java.lang.String) |
||||
*/ |
||||
@Override |
||||
public void setAsText(String text) { |
||||
|
||||
PropertyValue rawOrder = values.getPropertyValue(orderProperty); |
||||
Direction order = null == rawOrder ? Direction.ASC : Direction.fromString(rawOrder.getValue().toString()); |
||||
|
||||
setValue(new Sort(order, text)); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,255 @@
@@ -0,0 +1,255 @@
|
||||
/* |
||||
* Copyright 2013 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; |
||||
|
||||
import static org.springframework.data.web.SpringDataAnnotationUtils.*; |
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier; |
||||
import org.springframework.core.MethodParameter; |
||||
import org.springframework.data.domain.PageRequest; |
||||
import org.springframework.data.domain.Pageable; |
||||
import org.springframework.data.domain.Sort; |
||||
import org.springframework.hateoas.mvc.UriComponentsContributor; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.StringUtils; |
||||
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 org.springframework.web.util.UriComponentsBuilder; |
||||
|
||||
/** |
||||
* Extracts paging information from web requests and thus allows injecting {@link Pageable} instances into controller |
||||
* methods. Request properties to be parsed can be configured. Default configuration uses request properties beginning |
||||
* with {@link #PAGE_PROPERTY}{@link #DEFAULT_SEPARATOR}. |
||||
* |
||||
* @since 1.6 |
||||
* @author Oliver Gierke |
||||
*/ |
||||
@SuppressWarnings("deprecation") |
||||
public class PageableHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver, UriComponentsContributor { |
||||
|
||||
/** |
||||
* A {@link PageableHandlerMethodArgumentResolver} preconfigured to the setup of {@link PageableArgumentResolver}. Use |
||||
* that if you need to stick to the former request parameters an 1-indexed behavior. This will be removed in the next |
||||
* major version (1.7). So consider migrating to the new way of exposing request parameters. |
||||
*/ |
||||
@Deprecated |
||||
public static final PageableHandlerMethodArgumentResolver LEGACY; |
||||
|
||||
static { |
||||
LEGACY = new PageableHandlerMethodArgumentResolver(); |
||||
LEGACY.pageProperty = "page.page"; |
||||
LEGACY.sizeProperty = "page.size"; |
||||
LEGACY.fallbackPageable = new PageRequest(1, 10); |
||||
LEGACY.oneIndexedParameters = true; |
||||
LEGACY.sortResolver.setLegacyMode(true); |
||||
LEGACY.sortResolver.setSortParameter("page.sort"); |
||||
} |
||||
|
||||
private static final Pageable DEFAULT_PAGE_REQUEST = new PageRequest(0, 20); |
||||
private static final String DEFAULT_PAGE_PROPERTY = "page"; |
||||
private static final String DEFAULT_SIZE_PROPERTY = "size"; |
||||
private static final String DEFAULT_PREFIX = ""; |
||||
private static final String DEFAULT_QUALIFIER_SEPARATOR = "_"; |
||||
|
||||
private Pageable fallbackPageable = DEFAULT_PAGE_REQUEST; |
||||
private SortHandlerMethodArgumentResolver sortResolver = new SortHandlerMethodArgumentResolver(); |
||||
private String pageProperty = DEFAULT_PAGE_PROPERTY; |
||||
private String sizeProperty = DEFAULT_SIZE_PROPERTY; |
||||
private String prefix = DEFAULT_PREFIX; |
||||
private String qualifierSeparator = DEFAULT_QUALIFIER_SEPARATOR; |
||||
private boolean oneIndexedParameters = false; |
||||
|
||||
/** |
||||
* Configures the {@link Pageable} to be used as fallback in case no {@link PageableDefault} or |
||||
* {@link PageableDefaults} (the latter only supported in legacy mode) can be found at the method parameter to be |
||||
* resolved. |
||||
* <p> |
||||
* If you set this to {@literal null}, be aware that you controller methods will get {@literal null} handed into them |
||||
* in case no {@link Pageable} data can be found in the request. |
||||
* |
||||
* @param fallbackPageable the {@link Pageable} to be used as general fallback. |
||||
*/ |
||||
public void setFallbackPageable(Pageable fallbackPageable) { |
||||
this.fallbackPageable = fallbackPageable; |
||||
} |
||||
|
||||
/** |
||||
* Configures the parameter name to be used to find the page number in the request. Defaults to {@code page}. |
||||
* |
||||
* @param pageProperty the parameter name to be used, must not be {@literal null} or empty. |
||||
*/ |
||||
public void setPageProperty(String pageProperty) { |
||||
|
||||
Assert.hasText(pageProperty, "Page parameter name must not be null or empty!"); |
||||
this.pageProperty = pageProperty; |
||||
} |
||||
|
||||
/** |
||||
* Configures the parameter name to be used to find the page size in the request. Defaults to {@code size}. |
||||
* |
||||
* @param sizeProperty the parameter name to be used, must not be {@literal null} or empty. |
||||
*/ |
||||
public void setSizeProperty(String sizeProperty) { |
||||
|
||||
Assert.hasText(sizeProperty, "Size parameter name must not be null or empty!"); |
||||
this.sizeProperty = sizeProperty; |
||||
} |
||||
|
||||
/** |
||||
* Configures a general prefix to be prepended to the page number and page size parameters. Useful to namespace the |
||||
* property names used in case they are clashing with ones used by your application. By default, no prefix is used. |
||||
* |
||||
* @param prefix the prefix to be used or {@literal null} to reset to the default. |
||||
*/ |
||||
public void setPrefix(String prefix) { |
||||
this.prefix = prefix == null ? DEFAULT_PREFIX : prefix; |
||||
} |
||||
|
||||
/** |
||||
* The separator to be used between the qualifier and the actual page number and size properties. Defaults to |
||||
* {@code _}. So a qualifier of {@code foo} will result in a page number parameter of {@code foo_page}. |
||||
* |
||||
* @param qualifierSeparator the qualifierSeparator to be used or {@literal null} to reset to the default. |
||||
*/ |
||||
public void setQualifierSeparator(String qualifierSeparator) { |
||||
this.qualifierSeparator = qualifierSeparator == null ? DEFAULT_QUALIFIER_SEPARATOR : qualifierSeparator; |
||||
} |
||||
|
||||
/** |
||||
* Configure the {@link SortHandlerMethodArgumentResolver} to be used with the |
||||
* {@link PageableHandlerMethodArgumentResolver}. |
||||
* |
||||
* @param sortResolver the {@link SortHandlerMethodArgumentResolver} to be used ot {@literal null} to reset it to the |
||||
* default one. |
||||
*/ |
||||
public void setSortResolver(SortHandlerMethodArgumentResolver sortResolver) { |
||||
this.sortResolver = sortResolver == null ? new SortHandlerMethodArgumentResolver() : sortResolver; |
||||
} |
||||
|
||||
/** |
||||
* Configures whether to expose and assume 1-based page number indexes in the request parameters. Defaults to |
||||
* {@literal false}, meaning a page number of 0 in the request equals the first page. If this is set to |
||||
* {@literal true}, a page number of 1 in the request will be considered the first page. |
||||
* |
||||
* @param oneIndexedParameters the oneIndexedParameters to set |
||||
*/ |
||||
public void setOneIndexedParameters(boolean oneIndexedParameters) { |
||||
this.oneIndexedParameters = oneIndexedParameters; |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter) |
||||
*/ |
||||
public boolean supportsParameter(MethodParameter parameter) { |
||||
return Pageable.class.equals(parameter.getParameterType()); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.hateoas.mvc.UriComponentsContributor#enhance(org.springframework.web.util.UriComponentsBuilder, org.springframework.core.MethodParameter, java.lang.Object) |
||||
*/ |
||||
public void enhance(UriComponentsBuilder builder, MethodParameter parameter, Object value) { |
||||
|
||||
if (!(value instanceof Pageable)) { |
||||
return; |
||||
} |
||||
|
||||
Pageable pageable = (Pageable) value; |
||||
|
||||
String pagePropertyName = getParameterNameToUse(pageProperty, parameter); |
||||
String propertyToLookup = getParameterNameToUse(sizeProperty, parameter); |
||||
|
||||
int pageNumber = pageable.getPageNumber(); |
||||
|
||||
builder.queryParam(pagePropertyName, oneIndexedParameters ? pageNumber + 1 : pageNumber); |
||||
builder.queryParam(propertyToLookup, pageable.getPageSize()); |
||||
|
||||
sortResolver.enhance(builder, parameter, pageable.getSort()); |
||||
} |
||||
|
||||
/* |
||||
* (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) |
||||
*/ |
||||
public Pageable resolveArgument(MethodParameter methodParameter, ModelAndViewContainer mavContainer, |
||||
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { |
||||
|
||||
assertPageableUniqueness(methodParameter); |
||||
|
||||
Pageable defaultOrFallback = getDefaultFromAnnotationOrFallback(methodParameter); |
||||
|
||||
String pageString = webRequest.getParameter(getParameterNameToUse(pageProperty, methodParameter)); |
||||
String pageSizeString = webRequest.getParameter(getParameterNameToUse(sizeProperty, methodParameter)); |
||||
|
||||
int page = StringUtils.hasText(pageString) ? Integer.parseInt(pageString) - (oneIndexedParameters ? 1 : 0) |
||||
: defaultOrFallback.getPageNumber(); |
||||
int pageSize = StringUtils.hasText(pageSizeString) ? Integer.parseInt(pageSizeString) : defaultOrFallback |
||||
.getPageSize(); |
||||
|
||||
Sort sort = sortResolver.resolveArgument(methodParameter, mavContainer, webRequest, binderFactory); |
||||
return new PageRequest(page, pageSize, sort == null ? defaultOrFallback.getSort() : sort); |
||||
} |
||||
|
||||
/** |
||||
* Returns the name of the request parameter to find the {@link Pageable} information in. Inspects the given |
||||
* {@link MethodParameter} for {@link Qualifier} present and prefixes the given source parameter name with it. |
||||
* |
||||
* @param source the basic parameter name. |
||||
* @param parameter the {@link MethodParameter} potentially qualified. |
||||
* @return |
||||
*/ |
||||
private String getParameterNameToUse(String source, MethodParameter parameter) { |
||||
|
||||
StringBuilder builder = new StringBuilder(prefix); |
||||
|
||||
if (parameter.hasParameterAnnotation(Qualifier.class)) { |
||||
builder.append(parameter.getParameterAnnotation(Qualifier.class).value()); |
||||
builder.append(qualifierSeparator); |
||||
} |
||||
|
||||
return builder.append(source).toString(); |
||||
} |
||||
|
||||
private Pageable getDefaultFromAnnotationOrFallback(MethodParameter methodParameter) { |
||||
|
||||
if (sortResolver.legacyMode && methodParameter.hasParameterAnnotation(PageableDefaults.class)) { |
||||
Pageable pageable = PageableArgumentResolver.getDefaultPageRequestFrom(methodParameter |
||||
.getParameterAnnotation(PageableDefaults.class)); |
||||
return new PageRequest(pageable.getPageNumber() - 1, pageable.getPageSize(), pageable.getSort()); |
||||
} |
||||
|
||||
if (methodParameter.hasParameterAnnotation(PageableDefault.class)) { |
||||
return getDefaultPageRequestFrom(methodParameter.getParameterAnnotation(PageableDefault.class)); |
||||
} |
||||
|
||||
return fallbackPageable; |
||||
} |
||||
|
||||
private static Pageable getDefaultPageRequestFrom(PageableDefault defaults) { |
||||
|
||||
int defaultPageNumber = defaults.page(); |
||||
int defaultPageSize = getSpecificPropertyOrDefaultFromValue(defaults, "size"); |
||||
|
||||
if (defaults.sort().length == 0) { |
||||
return new PageRequest(defaultPageNumber, defaultPageSize); |
||||
} |
||||
|
||||
return new PageRequest(defaultPageNumber, defaultPageSize, defaults.direction(), defaults.sort()); |
||||
} |
||||
} |
||||
@ -0,0 +1,78 @@
@@ -0,0 +1,78 @@
|
||||
/* |
||||
* Copyright 2013 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; |
||||
|
||||
import java.lang.annotation.Documented; |
||||
import java.lang.annotation.ElementType; |
||||
import java.lang.annotation.Retention; |
||||
import java.lang.annotation.RetentionPolicy; |
||||
import java.lang.annotation.Target; |
||||
|
||||
import org.springframework.data.domain.Sort; |
||||
import org.springframework.data.domain.Sort.Direction; |
||||
|
||||
/** |
||||
* Annotation to define the default {@link Sort} options to be used when injecting a {@link Sort} instance into a |
||||
* controller handler method. |
||||
* |
||||
* @since 1.6 |
||||
* @author Oliver Gierke |
||||
*/ |
||||
@Documented |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@Target(ElementType.PARAMETER) |
||||
public @interface SortDefault { |
||||
|
||||
/** |
||||
* Alias for {@link #sort()} to make a declaration configuring fields only more concise. |
||||
* |
||||
* @return |
||||
*/ |
||||
String[] value() default {}; |
||||
|
||||
/** |
||||
* The properties to sort by by default. If unset, no sorting will be applied at all. |
||||
* |
||||
* @return |
||||
*/ |
||||
String[] sort() default {}; |
||||
|
||||
/** |
||||
* The direction to sort by. Defaults to {@link Direction#ASC}. |
||||
* |
||||
* @return |
||||
*/ |
||||
Direction direction() default Direction.ASC; |
||||
|
||||
/** |
||||
* Wrapper annotation to allow declaring multiple {@link SortDefault} annotations on a method parameter. |
||||
* |
||||
* @since 1.6 |
||||
* @author Oliver Gierke |
||||
*/ |
||||
@Documented |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@Target(ElementType.PARAMETER) |
||||
public @interface SortDefaults { |
||||
|
||||
/** |
||||
* The individual {@link SortDefault} declarations to be sorted by. |
||||
* |
||||
* @return |
||||
*/ |
||||
SortDefault[] value(); |
||||
} |
||||
} |
||||
@ -0,0 +1,406 @@
@@ -0,0 +1,406 @@
|
||||
/* |
||||
* Copyright 2013 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; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier; |
||||
import org.springframework.core.MethodParameter; |
||||
import org.springframework.data.domain.Sort; |
||||
import org.springframework.data.domain.Sort.Direction; |
||||
import org.springframework.data.domain.Sort.Order; |
||||
import org.springframework.data.web.SortDefault.SortDefaults; |
||||
import org.springframework.hateoas.mvc.UriComponentsContributor; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.StringUtils; |
||||
import org.springframework.web.bind.support.WebDataBinderFactory; |
||||
import org.springframework.web.context.request.NativeWebRequest; |
||||
import org.springframework.web.context.request.WebRequest; |
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver; |
||||
import org.springframework.web.method.support.ModelAndViewContainer; |
||||
import org.springframework.web.util.UriComponentsBuilder; |
||||
|
||||
/** |
||||
* {@link HandlerMethodArgumentResolver} to automatically create {@link Sort} instances from request parameters or |
||||
* {@link SortDefault} annotations. |
||||
* |
||||
* @since 1.6 |
||||
* @author Oliver Gierke |
||||
*/ |
||||
public class SortHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver, UriComponentsContributor { |
||||
|
||||
private static final String DEFAULT_PARAMETER = "sort"; |
||||
private static final String DEFAULT_PROPERTY_DELIMITER = ","; |
||||
private static final String DEFAULT_QUALIFIER_DELIMITER = "_"; |
||||
|
||||
private static final String SORT_DEFAULTS_NAME = SortDefaults.class.getSimpleName(); |
||||
private static final String SORT_DEFAULT_NAME = SortDefault.class.getSimpleName(); |
||||
|
||||
private String sortParameter = DEFAULT_PARAMETER; |
||||
private String propertyDelimiter = DEFAULT_PROPERTY_DELIMITER; |
||||
private String qualifierDelimiter = DEFAULT_QUALIFIER_DELIMITER; |
||||
|
||||
boolean legacyMode = false; |
||||
|
||||
/** |
||||
* Enables legacy mode parsing of the sorting parameter from the incoming request. Uses the sort property configured |
||||
* to lookup the fields to sort on and {@code $sortParameter.dir} for the direction. |
||||
* |
||||
* @param legacyMode whether to enable the legacy mode or not. |
||||
*/ |
||||
@Deprecated |
||||
void setLegacyMode(boolean legacyMode) { |
||||
this.legacyMode = legacyMode; |
||||
} |
||||
|
||||
/** |
||||
* Configure the request parameter to lookup sort information from. Defaults to {@code sort}. |
||||
* |
||||
* @param sortParameter must not be {@literal null} or empty. |
||||
*/ |
||||
public void setSortParameter(String parameter) { |
||||
|
||||
Assert.hasText(parameter); |
||||
this.sortParameter = parameter; |
||||
} |
||||
|
||||
/** |
||||
* Configures the delimiter used to separate property references and the direction to be sorted by. Defaults to |
||||
* {@code}, which means sort values look like this: {@code firstname,lastname,asc}. |
||||
* |
||||
* @param propertyDelimiter must not be {@literal null} or empty. |
||||
*/ |
||||
public void setPropertyDelimiter(String propertyDelimiter) { |
||||
|
||||
Assert.hasText(propertyDelimiter, "Property delimiter must not be null or empty!"); |
||||
this.propertyDelimiter = propertyDelimiter; |
||||
} |
||||
|
||||
/** |
||||
* Configures the delimiter used to separate the qualifier from the sort parameter. Defaults to {@code _}, so a |
||||
* qualified sort property would look like {@code qualifier_sort}. |
||||
* |
||||
* @param qualifierDelimiter the qualifier delimiter to be used or {@literal null} to reset to the default. |
||||
*/ |
||||
public void setQualifierDelimiter(String qualifierDelimiter) { |
||||
this.qualifierDelimiter = qualifierDelimiter == null ? DEFAULT_QUALIFIER_DELIMITER : qualifierDelimiter; |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter) |
||||
*/ |
||||
public boolean supportsParameter(MethodParameter parameter) { |
||||
return Sort.class.equals(parameter.getParameterType()); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.hateoas.mvc.UriComponentsContributor#enhance(org.springframework.web.util.UriComponentsBuilder, org.springframework.core.MethodParameter, java.lang.Object) |
||||
*/ |
||||
public void enhance(UriComponentsBuilder builder, MethodParameter parameter, Object value) { |
||||
|
||||
if (!(value instanceof Sort)) { |
||||
return; |
||||
} |
||||
|
||||
Sort sort = (Sort) value; |
||||
|
||||
if (legacyMode) { |
||||
|
||||
List<String> expressions = legacyFoldExpressions(sort); |
||||
Assert.isTrue(expressions.size() == 2, |
||||
String.format("Expected 2 sort expressions (fields, direction) but got %d!", expressions.size())); |
||||
builder.queryParam(getSortParameter(parameter), expressions.get(0)); |
||||
builder.queryParam(getLegacyDirectionParameter(parameter), expressions.get(1)); |
||||
|
||||
} else { |
||||
|
||||
for (String expression : foldIntoExpressions(sort)) { |
||||
builder.queryParam(getSortParameter(parameter), expression); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/* |
||||
* (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) |
||||
*/ |
||||
public Sort resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, |
||||
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { |
||||
|
||||
String[] directionParameter = webRequest.getParameterValues(getSortParameter(parameter)); |
||||
|
||||
if (directionParameter != null && directionParameter.length != 0) { |
||||
return legacyMode ? parseLegacyParameterIntoSort(webRequest, parameter) : parseParameterIntoSort( |
||||
directionParameter, propertyDelimiter); |
||||
} else { |
||||
return getDefaults(parameter); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Reads the default {@link Sort} to be used from the given {@link MethodParameter}. Rejects the parameter if both an |
||||
* {@link SortDefaults} and {@link SortDefault} annotation is found as we cannot build a reliable {@link Sort} |
||||
* instance then (property ordering). |
||||
* |
||||
* @param parameter will never be {@literal null}. |
||||
* @return the default {@link Sort} instance derived from the parameter annotations or {@literal null}. |
||||
*/ |
||||
private Sort getDefaults(MethodParameter parameter) { |
||||
|
||||
SortDefaults annotatedDefaults = parameter.getParameterAnnotation(SortDefaults.class); |
||||
Sort sort = null; |
||||
|
||||
if (annotatedDefaults != null) { |
||||
for (SortDefault annotatedDefault : annotatedDefaults.value()) { |
||||
sort = appendOrCreateSortTo(annotatedDefault, sort); |
||||
} |
||||
} |
||||
|
||||
SortDefault annotatedDefault = parameter.getParameterAnnotation(SortDefault.class); |
||||
|
||||
if (annotatedDefault == null) { |
||||
return sort; |
||||
} |
||||
|
||||
if (sort != null && annotatedDefault != null) { |
||||
throw new IllegalArgumentException(String.format( |
||||
"Cannot use both @%s and @%s on parameter %s! Move %s into %s to define sorting order!", SORT_DEFAULTS_NAME, |
||||
SORT_DEFAULT_NAME, parameter.toString(), SORT_DEFAULT_NAME, SORT_DEFAULTS_NAME)); |
||||
} |
||||
|
||||
return appendOrCreateSortTo(annotatedDefault, sort); |
||||
} |
||||
|
||||
/** |
||||
* Creates a new {@link Sort} instance from the given {@link SortDefault} or appends it to the given {@link Sort} |
||||
* instance if it's not {@literal null}. |
||||
* |
||||
* @param sortDefault |
||||
* @param sortOrNull |
||||
* @return |
||||
*/ |
||||
private Sort appendOrCreateSortTo(SortDefault sortDefault, Sort sortOrNull) { |
||||
|
||||
String[] fields = SpringDataAnnotationUtils.getSpecificPropertyOrDefaultFromValue(sortDefault, "sort"); |
||||
|
||||
if (fields.length == 0) { |
||||
return null; |
||||
} |
||||
|
||||
Sort sort = new Sort(sortDefault.direction(), fields); |
||||
return sortOrNull == null ? sort : sortOrNull.and(sort); |
||||
} |
||||
|
||||
/** |
||||
* Returns the sort parameter to be looked up from the request. Potentially applies qualifiers to it. |
||||
* |
||||
* @param parameter will never be {@literal null}. |
||||
* @return |
||||
*/ |
||||
private String getSortParameter(MethodParameter parameter) { |
||||
|
||||
StringBuilder builder = new StringBuilder(); |
||||
|
||||
if (parameter.hasParameterAnnotation(Qualifier.class)) { |
||||
builder.append(parameter.getParameterAnnotation(Qualifier.class).value()).append(qualifierDelimiter); |
||||
} |
||||
|
||||
return builder.append(sortParameter).toString(); |
||||
} |
||||
|
||||
/** |
||||
* Creates a {@link Sort} instance from the given request expecting the {@link Direction} being encoded in a parameter |
||||
* with an appended {@code .dir}. |
||||
* |
||||
* @param request must not be {@literal null}. |
||||
* @param parameter must not be {@literal null}. |
||||
* @return |
||||
*/ |
||||
private Sort parseLegacyParameterIntoSort(WebRequest request, MethodParameter parameter) { |
||||
|
||||
String property = getSortParameter(parameter); |
||||
String fields = request.getParameter(property); |
||||
String directions = request.getParameter(getLegacyDirectionParameter(parameter)); |
||||
|
||||
return new Sort(Direction.fromStringOrNull(directions), fields.split(",")); |
||||
} |
||||
|
||||
private String getLegacyDirectionParameter(MethodParameter parameter) { |
||||
return getSortParameter(parameter) + ".dir"; |
||||
} |
||||
|
||||
/** |
||||
* Parses the given sort expressions into a {@link Sort} instance. The implementation expects the sources to be a |
||||
* concatenation of Strings using the given delimiter. If the last element can be parsed into a {@link Direction} it's |
||||
* considered a {@link Direction} and a simple property otherwise. |
||||
* |
||||
* @param source will never be {@literal null}. |
||||
* @param delimiter the delimiter to be used to split up the source elements, will never be {@literal null}. |
||||
* @return |
||||
*/ |
||||
Sort parseParameterIntoSort(String[] source, String delimiter) { |
||||
|
||||
List<Order> allOrders = new ArrayList<Sort.Order>(); |
||||
|
||||
for (String part : source) { |
||||
|
||||
if (part == null) { |
||||
continue; |
||||
} |
||||
|
||||
String[] elements = part.split(delimiter); |
||||
Direction direction = Direction.fromStringOrNull(elements[elements.length - 1]); |
||||
|
||||
for (int i = 0; i < elements.length; i++) { |
||||
|
||||
if (i == elements.length - 1 && direction != null) { |
||||
continue; |
||||
} |
||||
|
||||
allOrders.add(new Order(direction, elements[i])); |
||||
} |
||||
} |
||||
|
||||
return allOrders.isEmpty() ? null : new Sort(allOrders); |
||||
} |
||||
|
||||
/** |
||||
* Folds the given {@link Sort} instance into a {@link List} of sort expressions, accumulating {@link Order} instances |
||||
* of the same direction into a single expression if they are in order. |
||||
* |
||||
* @param sort must not be {@literal null}. |
||||
* @return |
||||
*/ |
||||
private List<String> foldIntoExpressions(Sort sort) { |
||||
|
||||
List<String> expressions = new ArrayList<String>(); |
||||
ExpressionBuilder builder = null; |
||||
|
||||
for (Order order : sort) { |
||||
|
||||
Direction direction = order.getDirection(); |
||||
|
||||
if (builder == null) { |
||||
builder = new ExpressionBuilder(direction); |
||||
} else if (!builder.hasSameDirectionAs(order)) { |
||||
builder.dumpExpressionIfPresentInto(expressions); |
||||
builder = new ExpressionBuilder(direction); |
||||
} |
||||
|
||||
builder.add(order.getProperty()); |
||||
} |
||||
|
||||
return builder.dumpExpressionIfPresentInto(expressions); |
||||
} |
||||
|
||||
/** |
||||
* Folds the given {@link Sort} instance into two expressions. The first being the property list, the second being the |
||||
* direction. |
||||
* |
||||
* @throws IllegalArgumentException if a {@link Sort} with multiple {@link Direction}s has been handed in. |
||||
* @param sort must not be {@literal null}. |
||||
* @return |
||||
*/ |
||||
private List<String> legacyFoldExpressions(Sort sort) { |
||||
|
||||
List<String> expressions = new ArrayList<String>(); |
||||
ExpressionBuilder builder = null; |
||||
|
||||
for (Order order : sort) { |
||||
|
||||
Direction direction = order.getDirection(); |
||||
|
||||
if (builder == null) { |
||||
builder = new ExpressionBuilder(direction); |
||||
} else if (!builder.hasSameDirectionAs(order)) { |
||||
throw new IllegalArgumentException(String.format( |
||||
"%s in legacy configuration only supports a single direction to sort by!", getClass().getSimpleName())); |
||||
} |
||||
|
||||
builder.add(order.getProperty()); |
||||
} |
||||
|
||||
return builder.dumpExpressionIfPresentInto(expressions); |
||||
} |
||||
|
||||
/** |
||||
* Helper to easily build request parameter expressions for {@link Sort} instances. |
||||
* |
||||
* @author Oliver Gierke |
||||
*/ |
||||
class ExpressionBuilder { |
||||
|
||||
private final List<String> elements = new ArrayList<String>(); |
||||
private final Direction direction; |
||||
|
||||
/** |
||||
* Sets up a new {@link ExpressionBuilder} for properties to be sorted in the given {@link Direction}. |
||||
* |
||||
* @param direction must not be {@literal null}. |
||||
*/ |
||||
public ExpressionBuilder(Direction direction) { |
||||
|
||||
Assert.notNull(direction, "Direction must not be null!"); |
||||
this.direction = direction; |
||||
} |
||||
|
||||
/** |
||||
* Returns whether the given {@link Order} has the same direction as the current {@link ExpressionBuilder}. |
||||
* |
||||
* @param order must not be {@literal null}. |
||||
* @return |
||||
*/ |
||||
public boolean hasSameDirectionAs(Order order) { |
||||
return this.direction == order.getDirection(); |
||||
} |
||||
|
||||
/** |
||||
* Adds the given property to the expression to be built. |
||||
* |
||||
* @param property |
||||
*/ |
||||
public void add(String property) { |
||||
this.elements.add(property); |
||||
} |
||||
|
||||
/** |
||||
* Dumps the expression currently in build into the given {@link List} of {@link String}s. Will only dump it in case |
||||
* there are properties piled up currently. |
||||
* |
||||
* @param expressions |
||||
* @return |
||||
*/ |
||||
public List<String> dumpExpressionIfPresentInto(List<String> expressions) { |
||||
|
||||
if (elements.isEmpty()) { |
||||
return expressions; |
||||
} |
||||
|
||||
if (legacyMode) { |
||||
expressions.add(StringUtils.collectionToDelimitedString(elements, propertyDelimiter)); |
||||
expressions.add(direction.name().toLowerCase()); |
||||
} else { |
||||
elements.add(direction.name().toLowerCase()); |
||||
expressions.add(StringUtils.collectionToDelimitedString(elements, propertyDelimiter)); |
||||
} |
||||
|
||||
return expressions; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,142 @@
@@ -0,0 +1,142 @@
|
||||
/* |
||||
* Copyright 2013 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; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.Method; |
||||
import java.util.HashSet; |
||||
import java.util.Set; |
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier; |
||||
import org.springframework.core.MethodParameter; |
||||
import org.springframework.core.annotation.AnnotationUtils; |
||||
import org.springframework.data.domain.Pageable; |
||||
import org.springframework.util.ObjectUtils; |
||||
|
||||
/** |
||||
* Helper class to ease sharing code between legacy {@link PageableArgumentResolver} and |
||||
* {@link PageableHandlerMethodArgumentResolver}. |
||||
* |
||||
* @author Oliver Gierke |
||||
*/ |
||||
class SpringDataAnnotationUtils { |
||||
|
||||
/** |
||||
* Asserts uniqueness of all {@link Pageable} parameters of the method of the given {@link MethodParameter}. |
||||
* |
||||
* @param parameter must not be {@literal null}. |
||||
*/ |
||||
public static void assertPageableUniqueness(MethodParameter parameter) { |
||||
|
||||
Method method = parameter.getMethod(); |
||||
|
||||
if (containsMoreThanOnePageableParameter(method)) { |
||||
Annotation[][] annotations = method.getParameterAnnotations(); |
||||
assertQualifiersFor(method.getParameterTypes(), annotations); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Returns whether the given {@link Method} has more than one {@link Pageable} parameter. |
||||
* |
||||
* @param method must not be {@literal null}. |
||||
* @return |
||||
*/ |
||||
private static boolean containsMoreThanOnePageableParameter(Method method) { |
||||
|
||||
boolean pageableFound = false; |
||||
|
||||
for (Class<?> type : method.getParameterTypes()) { |
||||
|
||||
if (pageableFound && type.equals(Pageable.class)) { |
||||
return true; |
||||
} |
||||
|
||||
if (type.equals(Pageable.class)) { |
||||
pageableFound = true; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* Returns the value of the given specific property of the given annotation. If the value of that property is the |
||||
* properties default, we fall back to the value of the {@code value} attribute. |
||||
* |
||||
* @param annotation must not be {@literal null}. |
||||
* @param property must not be {@literal null} or empty. |
||||
* @return |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
public static <T> T getSpecificPropertyOrDefaultFromValue(Annotation annotation, String property) { |
||||
|
||||
Object propertyDefaultValue = AnnotationUtils.getDefaultValue(annotation, property); |
||||
Object propertyValue = AnnotationUtils.getValue(annotation, property); |
||||
|
||||
return (T) (ObjectUtils.nullSafeEquals(propertyDefaultValue, propertyValue) ? AnnotationUtils.getValue(annotation) |
||||
: propertyValue); |
||||
} |
||||
|
||||
/** |
||||
* Asserts that every {@link Pageable} parameter of the given parameters carries an {@link Qualifier} annotation to |
||||
* distinguish them from each other. |
||||
* |
||||
* @param parameterTypes must not be {@literal null}. |
||||
* @param annotations must not be {@literal null}. |
||||
*/ |
||||
public static void assertQualifiersFor(Class<?>[] parameterTypes, Annotation[][] annotations) { |
||||
|
||||
Set<String> values = new HashSet<String>(); |
||||
|
||||
for (int i = 0; i < annotations.length; i++) { |
||||
|
||||
if (Pageable.class.equals(parameterTypes[i])) { |
||||
|
||||
Qualifier qualifier = findAnnotation(annotations[i]); |
||||
|
||||
if (null == qualifier) { |
||||
throw new IllegalStateException( |
||||
"Ambiguous Pageable arguments in handler method. If you use multiple parameters of type Pageable you need to qualify them with @Qualifier"); |
||||
} |
||||
|
||||
if (values.contains(qualifier.value())) { |
||||
throw new IllegalStateException("Values of the user Qualifiers must be unique!"); |
||||
} |
||||
|
||||
values.add(qualifier.value()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Returns a {@link Qualifier} annotation from the given array of {@link Annotation}s. Returns {@literal null} if the |
||||
* array does not contain a {@link Qualifier} annotation. |
||||
* |
||||
* @param annotations must not be {@literal null}. |
||||
* @return |
||||
*/ |
||||
public static Qualifier findAnnotation(Annotation[] annotations) { |
||||
|
||||
for (Annotation annotation : annotations) { |
||||
if (annotation instanceof Qualifier) { |
||||
return (Qualifier) annotation; |
||||
} |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
} |
||||
@ -0,0 +1,155 @@
@@ -0,0 +1,155 @@
|
||||
/* |
||||
* Copyright 2013 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; |
||||
|
||||
import static org.hamcrest.CoreMatchers.*; |
||||
import static org.junit.Assert.*; |
||||
import static org.springframework.data.web.SortDefaultUnitTest.*; |
||||
|
||||
import org.junit.Rule; |
||||
import org.junit.Test; |
||||
import org.junit.rules.ExpectedException; |
||||
import org.springframework.core.MethodParameter; |
||||
import org.springframework.data.domain.PageRequest; |
||||
import org.springframework.data.domain.Pageable; |
||||
import org.springframework.data.domain.Sort; |
||||
import org.springframework.test.util.ReflectionTestUtils; |
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver; |
||||
import org.springframework.web.util.UriComponentsBuilder; |
||||
|
||||
/** |
||||
* Base test class to test supporting of a {@link HandlerMethodArgumentResolver} implementation defaulting |
||||
* {@link Pageable} method parameters. Expects the {@link HandlerMethodArgumentResolver} to be tested returned from |
||||
* {@link #getResolver()} and expects methods to be present in the controller class returned from |
||||
* {@link #getControllerClass()}. For sample usage see {@link PageableHandlerMethodArgumentResolver}. |
||||
* |
||||
* @since 1.6 |
||||
* @author Oliver Gierke |
||||
*/ |
||||
public abstract class PageableDefaultUnitTest { |
||||
|
||||
static final int PAGE_SIZE = 47; |
||||
static final int PAGE_NUMBER = 23; |
||||
|
||||
static final PageRequest REFERENCE_WITHOUT_SORT = new PageRequest(PAGE_NUMBER, PAGE_SIZE); |
||||
static final PageRequest REFERENCE_WITH_SORT = new PageRequest(PAGE_NUMBER, PAGE_SIZE, SORT); |
||||
static final PageRequest REFERENCE_WITH_SORT_FIELDS = new PageRequest(PAGE_NUMBER, PAGE_SIZE, new Sort(SORT_FIELDS)); |
||||
|
||||
@Rule |
||||
public ExpectedException exception = ExpectedException.none(); |
||||
|
||||
@Test |
||||
public void supportsPageable() { |
||||
assertThat(getResolver().supportsParameter(getParameterOfMethod("supportedMethod")), is(true)); |
||||
} |
||||
|
||||
@Test |
||||
public void doesNotSupportNonPageable() { |
||||
|
||||
MethodParameter parameter = TestUtils.getParameterOfMethod(getControllerClass(), "unsupportedMethod", String.class); |
||||
assertThat(getResolver().supportsParameter(parameter), is(false)); |
||||
} |
||||
|
||||
@Test |
||||
public void returnsDefaultIfNoRequestParametersAndNoDefault() throws Exception { |
||||
assertSupportedAndResult(getParameterOfMethod("supportedMethod"), |
||||
(Pageable) ReflectionTestUtils.getField(getResolver(), "fallbackPageable")); |
||||
} |
||||
|
||||
@Test |
||||
public void simpleDefault() throws Exception { |
||||
assertSupportedAndResult(getParameterOfMethod("simpleDefault"), REFERENCE_WITHOUT_SORT); |
||||
} |
||||
|
||||
@Test |
||||
public void simpleDefaultWithSort() throws Exception { |
||||
assertSupportedAndResult(getParameterOfMethod("simpleDefaultWithSort"), REFERENCE_WITH_SORT_FIELDS); |
||||
} |
||||
|
||||
@Test |
||||
public void simpleDefaultWithSortAndDirection() throws Exception { |
||||
assertSupportedAndResult(getParameterOfMethod("simpleDefaultWithSortAndDirection"), REFERENCE_WITH_SORT); |
||||
} |
||||
|
||||
@Test |
||||
public void simpleDefaultWithExternalSort() throws Exception { |
||||
assertSupportedAndResult(getParameterOfMethod("simpleDefaultWithExternalSort"), REFERENCE_WITH_SORT); |
||||
} |
||||
|
||||
@Test |
||||
public void simpleDefaultWithContaineredExternalSort() throws Exception { |
||||
assertSupportedAndResult(getParameterOfMethod("simpleDefaultWithContaineredExternalSort"), REFERENCE_WITH_SORT); |
||||
} |
||||
|
||||
@Test |
||||
public void rejectsInvalidQulifiers() throws Exception { |
||||
|
||||
MethodParameter parameter = TestUtils.getParameterOfMethod(getControllerClass(), "invalidQualifiers", |
||||
Pageable.class, Pageable.class); |
||||
|
||||
HandlerMethodArgumentResolver resolver = getResolver(); |
||||
assertThat(resolver.supportsParameter(parameter), is(true)); |
||||
|
||||
exception.expect(IllegalStateException.class); |
||||
exception.expectMessage("unique"); |
||||
|
||||
resolver.resolveArgument(parameter, null, TestUtils.getWebRequest(), null); |
||||
} |
||||
|
||||
@Test |
||||
public void rejectsNoQualifiers() throws Exception { |
||||
|
||||
MethodParameter parameter = TestUtils.getParameterOfMethod(getControllerClass(), "noQualifiers", Pageable.class, |
||||
Pageable.class); |
||||
|
||||
HandlerMethodArgumentResolver resolver = getResolver(); |
||||
assertThat(resolver.supportsParameter(parameter), is(true)); |
||||
|
||||
exception.expect(IllegalStateException.class); |
||||
exception.expectMessage("Ambiguous"); |
||||
|
||||
resolver.resolveArgument(parameter, null, TestUtils.getWebRequest(), null); |
||||
} |
||||
|
||||
private void assertSupportedAndResult(MethodParameter parameter, Pageable pageable) throws Exception { |
||||
|
||||
HandlerMethodArgumentResolver resolver = getResolver(); |
||||
assertThat(resolver.supportsParameter(parameter), is(true)); |
||||
assertThat(resolver.resolveArgument(parameter, null, TestUtils.getWebRequest(), null), is((Object) pageable)); |
||||
} |
||||
|
||||
protected void assertUriStringFor(Pageable pageable, String expected) { |
||||
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.fromPath("/"); |
||||
MethodParameter parameter = getParameterOfMethod("supportedMethod"); |
||||
|
||||
getResolver().enhance(builder, parameter, pageable); |
||||
|
||||
assertThat(builder.build().toUriString(), endsWith(expected)); |
||||
} |
||||
|
||||
protected abstract PageableHandlerMethodArgumentResolver getResolver(); |
||||
|
||||
protected abstract Class<?> getControllerClass(); |
||||
|
||||
protected MethodParameter getParameterOfMethod(String name) { |
||||
return getParameterOfMethod(getControllerClass(), name); |
||||
} |
||||
|
||||
private static MethodParameter getParameterOfMethod(Class<?> controller, String name) { |
||||
return TestUtils.getParameterOfMethod(controller, name, Pageable.class); |
||||
} |
||||
} |
||||
@ -0,0 +1,76 @@
@@ -0,0 +1,76 @@
|
||||
/* |
||||
* Copyright 2013 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; |
||||
|
||||
import org.junit.Test; |
||||
import org.springframework.beans.factory.annotation.Qualifier; |
||||
import org.springframework.data.domain.Pageable; |
||||
import org.springframework.data.domain.Sort.Direction; |
||||
import org.springframework.data.web.SortDefault.SortDefaults; |
||||
|
||||
/** |
||||
* Unit tests for {@link PageableHandlerMethodArgumentResolver}. Pulls in defaulting tests from |
||||
* {@link PageableDefaultUnitTest}. |
||||
* |
||||
* @author Oliver Gierke |
||||
*/ |
||||
public class PageableHandlerMethodArgumentResolverUnitTest extends PageableDefaultUnitTest { |
||||
|
||||
@Test |
||||
public void buildsUpRequestParameters() { |
||||
|
||||
String basicString = String.format("page=%d&size=%d", PAGE_NUMBER, PAGE_SIZE); |
||||
|
||||
assertUriStringFor(REFERENCE_WITHOUT_SORT, basicString); |
||||
assertUriStringFor(REFERENCE_WITH_SORT, basicString + "&sort=firstname,lastname,desc"); |
||||
assertUriStringFor(REFERENCE_WITH_SORT_FIELDS, basicString + "&sort=firstname,lastname,asc"); |
||||
} |
||||
|
||||
@Override |
||||
protected PageableHandlerMethodArgumentResolver getResolver() { |
||||
return new PageableHandlerMethodArgumentResolver(); |
||||
} |
||||
|
||||
@Override |
||||
protected Class<?> getControllerClass() { |
||||
return Sample.class; |
||||
} |
||||
|
||||
interface Sample { |
||||
|
||||
void supportedMethod(Pageable pageable); |
||||
|
||||
void unsupportedMethod(String string); |
||||
|
||||
void simpleDefault(@PageableDefault(size = PAGE_SIZE, page = PAGE_NUMBER) Pageable pageable); |
||||
|
||||
void simpleDefaultWithSort( |
||||
@PageableDefault(size = PAGE_SIZE, page = PAGE_NUMBER, sort = { "firstname", "lastname" }) Pageable pageable); |
||||
|
||||
void simpleDefaultWithSortAndDirection(@PageableDefault(size = PAGE_SIZE, page = PAGE_NUMBER, sort = { "firstname", |
||||
"lastname" }, direction = Direction.DESC) Pageable pageable); |
||||
|
||||
void simpleDefaultWithExternalSort(@PageableDefault(size = PAGE_SIZE, page = PAGE_NUMBER)//
|
||||
@SortDefault(sort = { "firstname", "lastname" }, direction = Direction.DESC) Pageable pageable); |
||||
|
||||
void simpleDefaultWithContaineredExternalSort(@PageableDefault(size = PAGE_SIZE, page = PAGE_NUMBER)//
|
||||
@SortDefaults(@SortDefault(sort = { "firstname", "lastname" }, direction = Direction.DESC)) Pageable pageable); |
||||
|
||||
void invalidQualifiers(@Qualifier("foo") Pageable first, @Qualifier("foo") Pageable second); |
||||
|
||||
void noQualifiers(Pageable first, Pageable second); |
||||
} |
||||
} |
||||
@ -0,0 +1,143 @@
@@ -0,0 +1,143 @@
|
||||
/* |
||||
* Copyright 2013 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; |
||||
|
||||
import static org.hamcrest.CoreMatchers.*; |
||||
import static org.junit.Assert.*; |
||||
import static org.springframework.data.domain.Sort.Direction.*; |
||||
|
||||
import org.junit.Rule; |
||||
import org.junit.Test; |
||||
import org.junit.rules.ExpectedException; |
||||
import org.springframework.core.MethodParameter; |
||||
import org.springframework.data.domain.Sort; |
||||
import org.springframework.data.domain.Sort.Direction; |
||||
import org.springframework.data.domain.Sort.Order; |
||||
import org.springframework.data.web.SortDefault.SortDefaults; |
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver; |
||||
|
||||
/** |
||||
* Unit tests for {@link SortDefault}. |
||||
* |
||||
* @since 1.6 |
||||
* @author Oliver Gierke |
||||
*/ |
||||
public abstract class SortDefaultUnitTest { |
||||
|
||||
static final String SORT_0 = "username"; |
||||
static final String SORT_1 = "username,asc"; |
||||
static final String[] SORT_2 = new String[] { "username,ASC", "lastname,firstname,DESC" }; |
||||
static final String SORT_3 = "firstname,lastname"; |
||||
|
||||
static final String[] SORT_FIELDS = new String[] { "firstname", "lastname" }; |
||||
static final Direction SORT_DIRECTION = Direction.DESC; |
||||
|
||||
static final Sort SORT = new Sort(SORT_DIRECTION, SORT_FIELDS); |
||||
|
||||
@Rule |
||||
public ExpectedException exception = ExpectedException.none(); |
||||
|
||||
@Test |
||||
public void parsesSimpleSortStringCorrectly() { |
||||
|
||||
assertSortStringParsedInto(new Sort(new Order("username")), SORT_1); |
||||
assertSortStringParsedInto(new Sort(new Order(ASC, "username")), SORT_1); |
||||
assertSortStringParsedInto(new Sort(new Order(ASC, "username"), //
|
||||
new Order(DESC, "lastname"), new Order(DESC, "firstname")), SORT_2); |
||||
assertSortStringParsedInto(new Sort("firstname", "lastname"), SORT_3); |
||||
} |
||||
|
||||
private static void assertSortStringParsedInto(Sort expected, String... source) { |
||||
|
||||
SortHandlerMethodArgumentResolver resolver = new SortHandlerMethodArgumentResolver(); |
||||
Sort sort = resolver.parseParameterIntoSort(source, ","); |
||||
|
||||
assertThat(sort, is(expected)); |
||||
} |
||||
|
||||
@Test |
||||
public void supportsSortParameter() { |
||||
assertThat(getResolver().supportsParameter(getParameterOfMethod("supportedMethod")), is(true)); |
||||
} |
||||
|
||||
@Test |
||||
public void returnsNullForNoDefault() throws Exception { |
||||
assertSupportedAndResolvedTo(getParameterOfMethod("supportedMethod"), null); |
||||
} |
||||
|
||||
@Test |
||||
public void discoversSimpleDefault() throws Exception { |
||||
assertSupportedAndResolvedTo(getParameterOfMethod("simpleDefault"), new Sort(Direction.ASC, SORT_FIELDS)); |
||||
} |
||||
|
||||
@Test |
||||
public void discoversSimpleDefaultWithDirection() throws Exception { |
||||
assertSupportedAndResolvedTo(getParameterOfMethod("simpleDefaultWithDirection"), SORT); |
||||
} |
||||
|
||||
@Test |
||||
public void rejectsNonSortParameter() { |
||||
|
||||
MethodParameter parameter = TestUtils.getParameterOfMethod(getControllerClass(), "unsupportedMethod", String.class); |
||||
assertThat(getResolver().supportsParameter(parameter), is(false)); |
||||
} |
||||
|
||||
@Test |
||||
public void rejectsDoubleAnnotatedMethod() throws Exception { |
||||
|
||||
MethodParameter parameter = getParameterOfMethod("invalid"); |
||||
|
||||
HandlerMethodArgumentResolver resolver = new SortHandlerMethodArgumentResolver(); |
||||
assertThat(resolver.supportsParameter(parameter), is(true)); |
||||
|
||||
exception.expect(IllegalArgumentException.class); |
||||
exception.expectMessage(SortDefault.class.getSimpleName()); |
||||
exception.expectMessage(SortDefaults.class.getSimpleName()); |
||||
exception.expectMessage(parameter.toString()); |
||||
|
||||
resolver.resolveArgument(parameter, null, TestUtils.getWebRequest(), null); |
||||
} |
||||
|
||||
@Test |
||||
public void discoversContaineredDefault() throws Exception { |
||||
|
||||
MethodParameter parameter = getParameterOfMethod("containeredDefault"); |
||||
Sort reference = new Sort("foo", "bar"); |
||||
|
||||
assertSupportedAndResolvedTo(parameter, reference); |
||||
} |
||||
|
||||
protected HandlerMethodArgumentResolver getResolver() { |
||||
return new SortHandlerMethodArgumentResolver(); |
||||
} |
||||
|
||||
protected abstract Class<?> getControllerClass(); |
||||
|
||||
private void assertSupportedAndResolvedTo(MethodParameter parameter, Sort sort) throws Exception { |
||||
|
||||
HandlerMethodArgumentResolver resolver = getResolver(); |
||||
assertThat(resolver.supportsParameter(parameter), is(true)); |
||||
assertThat(resolver.resolveArgument(parameter, null, TestUtils.getWebRequest(), null), is((Object) sort)); |
||||
} |
||||
|
||||
protected MethodParameter getParameterOfMethod(String name) { |
||||
return getParameterOfMethod(getControllerClass(), name); |
||||
} |
||||
|
||||
private static MethodParameter getParameterOfMethod(Class<?> controller, String name) { |
||||
return TestUtils.getParameterOfMethod(controller, name, Sort.class); |
||||
} |
||||
} |
||||
@ -0,0 +1,164 @@
@@ -0,0 +1,164 @@
|
||||
/* |
||||
* Copyright 2013 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; |
||||
|
||||
import static org.hamcrest.CoreMatchers.*; |
||||
import static org.junit.Assert.*; |
||||
import static org.springframework.data.domain.Sort.Direction.*; |
||||
|
||||
import org.junit.Test; |
||||
import org.springframework.beans.factory.annotation.Qualifier; |
||||
import org.springframework.core.MethodParameter; |
||||
import org.springframework.data.domain.Sort; |
||||
import org.springframework.data.domain.Sort.Direction; |
||||
import org.springframework.data.domain.Sort.Order; |
||||
import org.springframework.data.web.SortDefault.SortDefaults; |
||||
import org.springframework.mock.web.MockHttpServletRequest; |
||||
import org.springframework.util.StringUtils; |
||||
import org.springframework.web.context.request.NativeWebRequest; |
||||
import org.springframework.web.context.request.ServletWebRequest; |
||||
import org.springframework.web.util.UriComponentsBuilder; |
||||
|
||||
/** |
||||
* Unit tests for {@link SortHandlerMethodArgumentResolver}. |
||||
* |
||||
* @since 1.6 |
||||
* @author Oliver Gierke |
||||
*/ |
||||
public class SortHandlerArgumentResolverUnitTests extends SortDefaultUnitTest { |
||||
|
||||
static final String SORT_0 = "username"; |
||||
static final String SORT_1 = "username,asc"; |
||||
static final String[] SORT_2 = new String[] { "username,ASC", "lastname,firstname,DESC" }; |
||||
static final String SORT_3 = "firstname,lastname"; |
||||
|
||||
@Test |
||||
public void discoversSimpleSortFromRequest() { |
||||
|
||||
MethodParameter parameter = getParameterOfMethod("simpleDefault"); |
||||
Sort reference = new Sort("bar", "foo"); |
||||
NativeWebRequest request = getRequestWithSort(reference); |
||||
|
||||
assertSupportedAndResolvedTo(request, parameter, reference); |
||||
} |
||||
|
||||
@Test |
||||
public void discoversComplexSortFromRequest() { |
||||
|
||||
MethodParameter parameter = getParameterOfMethod("simpleDefault"); |
||||
Sort reference = new Sort("bar", "foo").and(new Sort("fizz", "buzz")); |
||||
|
||||
assertSupportedAndResolvedTo(getRequestWithSort(reference), parameter, reference); |
||||
} |
||||
|
||||
@Test |
||||
public void discoversQualifiedSortFromRequest() { |
||||
|
||||
MethodParameter parameter = getParameterOfMethod("qualifiedSort"); |
||||
Sort reference = new Sort("bar", "foo"); |
||||
|
||||
assertSupportedAndResolvedTo(getRequestWithSort(reference, "qual"), parameter, reference); |
||||
} |
||||
|
||||
@Test |
||||
public void returnsNullForSortParameterSetToNothing() throws Exception { |
||||
|
||||
MethodParameter parameter = getParameterOfMethod("supportedMethod"); |
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||
request.addParameter("sort", (String) null); |
||||
|
||||
SortHandlerMethodArgumentResolver resolver = new SortHandlerMethodArgumentResolver(); |
||||
Sort result = resolver.resolveArgument(parameter, null, new ServletWebRequest(request), null); |
||||
assertThat(result, is(nullValue())); |
||||
} |
||||
|
||||
@Test |
||||
public void buildsUpRequestParameters() { |
||||
assertUriStringFor(SORT, "sort=firstname,lastname,desc"); |
||||
assertUriStringFor(new Sort(ASC, "foo").and(new Sort(DESC, "bar").and(new Sort(ASC, "foobar"))), |
||||
"sort=foo,asc&sort=bar,desc&sort=foobar,asc"); |
||||
assertUriStringFor(new Sort(ASC, "foo").and(new Sort(ASC, "bar").and(new Sort(DESC, "foobar"))), |
||||
"sort=foo,bar,asc&sort=foobar,desc"); |
||||
} |
||||
|
||||
private void assertUriStringFor(Sort sort, String expected) { |
||||
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.fromPath("/"); |
||||
MethodParameter parameter = getParameterOfMethod("supportedMethod"); |
||||
|
||||
new SortHandlerMethodArgumentResolver().enhance(builder, parameter, sort); |
||||
|
||||
assertThat(builder.build().toUriString(), endsWith(expected)); |
||||
} |
||||
|
||||
private static void assertSupportedAndResolvedTo(NativeWebRequest request, MethodParameter parameter, Sort sort) { |
||||
|
||||
SortHandlerMethodArgumentResolver resolver = new SortHandlerMethodArgumentResolver(); |
||||
assertThat(resolver.supportsParameter(parameter), is(true)); |
||||
|
||||
try { |
||||
assertThat(resolver.resolveArgument(parameter, null, request, null), is(sort)); |
||||
} catch (Exception e) { |
||||
throw new RuntimeException(e); |
||||
} |
||||
} |
||||
|
||||
private static NativeWebRequest getRequestWithSort(Sort sort) { |
||||
|
||||
return getRequestWithSort(sort, null); |
||||
} |
||||
|
||||
private static NativeWebRequest getRequestWithSort(Sort sort, String qualifier) { |
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||
|
||||
if (sort == null) { |
||||
return new ServletWebRequest(request); |
||||
} |
||||
|
||||
for (Order order : sort) { |
||||
|
||||
String prefix = StringUtils.hasText(qualifier) ? qualifier + "_" : ""; |
||||
request.addParameter(prefix + "sort", String.format("%s,%s", order.getProperty(), order.getDirection().name())); |
||||
} |
||||
|
||||
return new ServletWebRequest(request); |
||||
} |
||||
|
||||
@Override |
||||
protected Class<?> getControllerClass() { |
||||
return Controller.class; |
||||
} |
||||
|
||||
interface Controller { |
||||
|
||||
void supportedMethod(Sort sort); |
||||
|
||||
void unsupportedMethod(String string); |
||||
|
||||
void qualifiedSort(@Qualifier("qual") Sort sort); |
||||
|
||||
void simpleDefault(@SortDefault({ "firstname", "lastname" }) Sort sort); |
||||
|
||||
void simpleDefaultWithDirection( |
||||
@SortDefault(sort = { "firstname", "lastname" }, direction = Direction.DESC) Sort sort); |
||||
|
||||
void containeredDefault(@SortDefaults(@SortDefault({ "foo", "bar" })) Sort sort); |
||||
|
||||
void invalid(@SortDefaults(@SortDefault({ "foo", "bar" })) @SortDefault({ "bar", "foo" }) Sort sort); |
||||
} |
||||
} |
||||
@ -0,0 +1,51 @@
@@ -0,0 +1,51 @@
|
||||
/* |
||||
* Copyright 2013 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; |
||||
|
||||
import java.lang.reflect.Method; |
||||
|
||||
import org.springframework.core.MethodParameter; |
||||
import org.springframework.mock.web.MockHttpServletRequest; |
||||
import org.springframework.web.context.request.NativeWebRequest; |
||||
import org.springframework.web.context.request.ServletWebRequest; |
||||
|
||||
/** |
||||
* General test utilities. |
||||
* |
||||
* @since 1.6 |
||||
* @author Oliver Gierke |
||||
*/ |
||||
class TestUtils { |
||||
|
||||
public static NativeWebRequest getWebRequest() { |
||||
return new ServletWebRequest(new MockHttpServletRequest()); |
||||
} |
||||
|
||||
public static MethodParameter getParameterOfMethod(Class<?> controller, String name, Class<?>... argumentTypes) { |
||||
|
||||
Method method = getMethod(controller, name, argumentTypes); |
||||
return new MethodParameter(method, 0); |
||||
} |
||||
|
||||
public static Method getMethod(Class<?> controller, String name, Class<?>... argumentTypes) { |
||||
|
||||
try { |
||||
return controller.getMethod(name, argumentTypes); |
||||
} catch (Exception e) { |
||||
throw new RuntimeException(e); |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue