Skip to content

Commit e093aea

Browse files
committed
DATACMNS-117 - Added PageableHandlerArgumentResolver.
Added PageableHandlerArgumentResolver to supersede the now deprecated PageableArgumentResolver. The latter still stays available for Spring 3.0.x based deployments. Updated reference documentation to mention the newly introduced type as well as possible configuration options.
1 parent 0bbf0ae commit e093aea

File tree

4 files changed

+547
-6
lines changed

4 files changed

+547
-6
lines changed

src/docbkx/repositories.xml

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,19 +1005,36 @@ public class UserController {
10051005

10061006
<para>The bottom line is that the controller should not have to handle
10071007
the functionality of extracting pagination information from the request.
1008-
So Spring includes a <classname>PageableArgumentResolver</classname>
1009-
that will do the work for you.</para>
1008+
So Spring Data ships with a
1009+
<classname>PageableHandlerArgumentResolver</classname> that will do the
1010+
work for you. The Spring MVC JavaConfig support exposes a
1011+
<classname>WebMvcConfigurationSupport</classname> helper class to
1012+
customize the configuration as follows:</para>
1013+
1014+
<programlisting language="xml">@Configuration
1015+
public class WebConfig extends WebMvcConfigurationSupport {
1016+
1017+
@Override
1018+
public void configureMessageConverters(List&lt;HttpMessageConverter&lt;?&gt;&gt; converters) {
1019+
converters.add(new PageableHandlerArgumentResolver());
1020+
}
1021+
}</programlisting>
1022+
1023+
<para>If you're stuck with XML configuration you can register the
1024+
resolver as follows:</para>
10101025

1011-
<programlisting language="xml">&lt;bean class="….web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"&gt;
1026+
<programlisting language="xml">&lt;bean class="….web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"&gt;
10121027
&lt;property name="customArgumentResolvers"&gt;
10131028
&lt;list&gt;
1014-
&lt;bean class="org.springframework.data.web.PageableArgumentResolver" /&gt;
1029+
&lt;bean class="org.springframework.data.web.PageableHandlerArgumentResolver" /&gt;
10151030
&lt;/list&gt;
10161031
&lt;/property&gt;
10171032
&lt;/bean&gt;</programlisting>
10181033

1019-
<para>This configuration allows you to simplify controllers down to
1020-
something like this:</para>
1034+
<para>When using Spring 3.0.x versions use the
1035+
<classname>PageableArgumentResolver</classname> instead. Once you've
1036+
configured the resolver with Spring MVC it allows you to simplify
1037+
controllers down to something like this:</para>
10211038

10221039
<programlisting lang="" language="java">@Controller
10231040
@RequestMapping("/users")

src/main/java/org/springframework/data/web/PageableArgumentResolver.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,10 @@
4242
* methods. Request properties to be parsed can be configured. Default configuration uses request properties beginning
4343
* with {@link #DEFAULT_PREFIX}{@link #DEFAULT_SEPARATOR}.
4444
*
45+
* @deprecated use {@link PageableWebHandlerArgumentResolver} instead.
4546
* @author Oliver Gierke
4647
*/
48+
@Deprecated
4749
public class PageableArgumentResolver implements WebArgumentResolver {
4850

4951
private static final Pageable DEFAULT_PAGE_REQUEST = new PageRequest(0, 10);
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
/*
2+
* Copyright 2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.web;
17+
18+
import java.beans.PropertyEditorSupport;
19+
import java.lang.annotation.Annotation;
20+
import java.lang.reflect.Method;
21+
import java.util.HashSet;
22+
import java.util.Set;
23+
24+
import javax.servlet.ServletRequest;
25+
26+
import org.springframework.beans.PropertyValue;
27+
import org.springframework.beans.PropertyValues;
28+
import org.springframework.beans.factory.annotation.Qualifier;
29+
import org.springframework.core.MethodParameter;
30+
import org.springframework.data.domain.PageRequest;
31+
import org.springframework.data.domain.Pageable;
32+
import org.springframework.data.domain.Sort;
33+
import org.springframework.data.domain.Sort.Direction;
34+
import org.springframework.validation.DataBinder;
35+
import org.springframework.web.bind.ServletRequestDataBinder;
36+
import org.springframework.web.bind.ServletRequestParameterPropertyValues;
37+
import org.springframework.web.bind.support.WebDataBinderFactory;
38+
import org.springframework.web.context.request.NativeWebRequest;
39+
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
40+
import org.springframework.web.method.support.ModelAndViewContainer;
41+
42+
/**
43+
* Extracts paging information from web requests and thus allows injecting {@link Pageable} instances into controller
44+
* methods. Request properties to be parsed can be configured. Default configuration uses request properties beginning
45+
* with {@link #DEFAULT_PREFIX}{@link #DEFAULT_SEPARATOR}.
46+
*
47+
* @since 1.6
48+
* @author Oliver Gierke
49+
*/
50+
public class PageableHandlerArgumentResolver implements HandlerMethodArgumentResolver {
51+
52+
private static final Pageable DEFAULT_PAGE_REQUEST = new PageRequest(0, 10);
53+
private static final String DEFAULT_PREFIX = "page";
54+
private static final String DEFAULT_SEPARATOR = ".";
55+
56+
private Pageable fallbackPagable = DEFAULT_PAGE_REQUEST;
57+
private String prefix = DEFAULT_PREFIX;
58+
private String separator = DEFAULT_SEPARATOR;
59+
60+
/**
61+
* Setter to configure a fallback instance of {@link Pageable} that is being used to back missing parameters. Defaults
62+
* to {@link #DEFAULT_PAGE_REQUEST}.
63+
*
64+
* @param fallbackPagable the fallbackPagable to set
65+
*/
66+
public void setFallbackPagable(Pageable fallbackPagable) {
67+
this.fallbackPagable = null == fallbackPagable ? DEFAULT_PAGE_REQUEST : fallbackPagable;
68+
}
69+
70+
/**
71+
* Setter to configure the prefix of request parameters to be used to retrieve paging information. Defaults to
72+
* {@link #DEFAULT_PREFIX}.
73+
*
74+
* @param prefix the prefix to set
75+
*/
76+
public void setPrefix(String prefix) {
77+
this.prefix = null == prefix ? DEFAULT_PREFIX : prefix;
78+
}
79+
80+
/**
81+
* Setter to configure the separator between prefix and actual property value. Defaults to {@link #DEFAULT_SEPARATOR}.
82+
*
83+
* @param separator the separator to set. Will default to {@link #DEFAULT_SEPEARATOR} if set to {@literal null}.
84+
*/
85+
public void setSeparator(String separator) {
86+
this.separator = null == separator ? DEFAULT_SEPARATOR : separator;
87+
}
88+
89+
/*
90+
* (non-Javadoc)
91+
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter)
92+
*/
93+
public boolean supportsParameter(MethodParameter parameter) {
94+
return Pageable.class.equals(parameter.getParameterType());
95+
}
96+
97+
/*
98+
* (non-Javadoc)
99+
* @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)
100+
*/
101+
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer mavContainer,
102+
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
103+
104+
assertPageableUniqueness(methodParameter);
105+
106+
Pageable request = getDefaultFromAnnotationOrFallback(methodParameter);
107+
ServletRequest servletRequest = (ServletRequest) webRequest.getNativeRequest();
108+
PropertyValues propertyValues = new ServletRequestParameterPropertyValues(servletRequest,
109+
getPrefix(methodParameter), separator);
110+
111+
DataBinder binder = new ServletRequestDataBinder(request);
112+
113+
binder.initDirectFieldAccess();
114+
binder.registerCustomEditor(Sort.class, new SortPropertyEditor("sort.dir", propertyValues));
115+
binder.bind(propertyValues);
116+
117+
if (request.getPageNumber() > 0) {
118+
request = new PageRequest(request.getPageNumber() - 1, request.getPageSize(), request.getSort());
119+
}
120+
121+
return request;
122+
}
123+
124+
private Pageable getDefaultFromAnnotationOrFallback(MethodParameter methodParameter) {
125+
126+
// search for PageableDefaults annotation
127+
for (Annotation annotation : methodParameter.getParameterAnnotations()) {
128+
if (annotation instanceof PageableDefaults) {
129+
return getDefaultPageRequestFrom((PageableDefaults) annotation);
130+
}
131+
}
132+
133+
// Construct request with fallback request to ensure sensible
134+
// default values. Create fresh copy as Spring will manipulate the
135+
// instance under the covers
136+
return new PageRequest(fallbackPagable.getPageNumber(), fallbackPagable.getPageSize(), fallbackPagable.getSort());
137+
}
138+
139+
private static Pageable getDefaultPageRequestFrom(PageableDefaults defaults) {
140+
141+
// +1 is because we substract 1 later
142+
int defaultPageNumber = defaults.pageNumber() + 1;
143+
int defaultPageSize = defaults.value();
144+
145+
if (defaults.sort().length == 0) {
146+
return new PageRequest(defaultPageNumber, defaultPageSize);
147+
}
148+
149+
return new PageRequest(defaultPageNumber, defaultPageSize, defaults.sortDir(), defaults.sort());
150+
}
151+
152+
/**
153+
* Resolves the prefix to use to bind properties from. Will prepend a possible {@link Qualifier} if available or
154+
* return the configured prefix otherwise.
155+
*
156+
* @param parameter
157+
* @return
158+
*/
159+
private String getPrefix(MethodParameter parameter) {
160+
161+
for (Annotation annotation : parameter.getParameterAnnotations()) {
162+
if (annotation instanceof Qualifier) {
163+
return new StringBuilder(((Qualifier) annotation).value()).append("_").append(prefix).toString();
164+
}
165+
}
166+
167+
return prefix;
168+
}
169+
170+
/**
171+
* Asserts uniqueness of all {@link Pageable} parameters of the method of the given {@link MethodParameter}.
172+
*
173+
* @param parameter
174+
*/
175+
private void assertPageableUniqueness(MethodParameter parameter) {
176+
177+
Method method = parameter.getMethod();
178+
179+
if (containsMoreThanOnePageableParameter(method)) {
180+
Annotation[][] annotations = method.getParameterAnnotations();
181+
assertQualifiersFor(method.getParameterTypes(), annotations);
182+
}
183+
}
184+
185+
/**
186+
* Returns whether the given {@link Method} has more than one {@link Pageable} parameter.
187+
*
188+
* @param method
189+
* @return
190+
*/
191+
private boolean containsMoreThanOnePageableParameter(Method method) {
192+
193+
boolean pageableFound = false;
194+
195+
for (Class<?> type : method.getParameterTypes()) {
196+
197+
if (pageableFound && type.equals(Pageable.class)) {
198+
return true;
199+
}
200+
201+
if (type.equals(Pageable.class)) {
202+
pageableFound = true;
203+
}
204+
}
205+
206+
return false;
207+
}
208+
209+
/**
210+
* Asserts that every {@link Pageable} parameter of the given parameters carries an {@link Qualifier} annotation to
211+
* distinguish them from each other.
212+
*
213+
* @param parameterTypes
214+
* @param annotations
215+
*/
216+
private void assertQualifiersFor(Class<?>[] parameterTypes, Annotation[][] annotations) {
217+
218+
Set<String> values = new HashSet<String>();
219+
220+
for (int i = 0; i < annotations.length; i++) {
221+
222+
if (Pageable.class.equals(parameterTypes[i])) {
223+
224+
Qualifier qualifier = findAnnotation(annotations[i]);
225+
226+
if (null == qualifier) {
227+
throw new IllegalStateException(
228+
"Ambiguous Pageable arguments in handler method. If you use multiple parameters of type Pageable you need to qualify them with @Qualifier");
229+
}
230+
231+
if (values.contains(qualifier.value())) {
232+
throw new IllegalStateException("Values of the user Qualifiers must be unique!");
233+
}
234+
235+
values.add(qualifier.value());
236+
}
237+
}
238+
}
239+
240+
/**
241+
* Returns a {@link Qualifier} annotation from the given array of {@link Annotation}s. Returns {@literal null} if the
242+
* array does not contain a {@link Qualifier} annotation.
243+
*
244+
* @param annotations
245+
* @return
246+
*/
247+
private Qualifier findAnnotation(Annotation[] annotations) {
248+
249+
for (Annotation annotation : annotations) {
250+
if (annotation instanceof Qualifier) {
251+
return (Qualifier) annotation;
252+
}
253+
}
254+
255+
return null;
256+
}
257+
258+
/**
259+
* {@link java.beans.PropertyEditor} to create {@link Sort} instances from textual representations. The implementation
260+
* interprets the string as a comma separated list where the first entry is the sort direction ( {@code asc},
261+
* {@code desc}) followed by the properties to sort by.
262+
*
263+
* @author Oliver Gierke
264+
*/
265+
private static class SortPropertyEditor extends PropertyEditorSupport {
266+
267+
private final String orderProperty;
268+
private final PropertyValues values;
269+
270+
/**
271+
* Creates a new {@link SortPropertyEditor}.
272+
*
273+
* @param orderProperty
274+
* @param values
275+
*/
276+
public SortPropertyEditor(String orderProperty, PropertyValues values) {
277+
278+
this.orderProperty = orderProperty;
279+
this.values = values;
280+
}
281+
282+
/*
283+
* (non-Javadoc)
284+
* @see java.beans.PropertyEditorSupport#setAsText(java.lang.String)
285+
*/
286+
@Override
287+
public void setAsText(String text) {
288+
289+
PropertyValue rawOrder = values.getPropertyValue(orderProperty);
290+
Direction order = null == rawOrder ? Direction.ASC : Direction.fromString(rawOrder.getValue().toString());
291+
292+
setValue(new Sort(order, text));
293+
}
294+
}
295+
}

0 commit comments

Comments
 (0)