Fix paging.

This commit is contained in:
Jon Brisbin
2013-03-12 13:37:07 -05:00
committed by Jon Brisbin
parent 34adf44c1f
commit e0c34e1be6
4 changed files with 224 additions and 138 deletions

View File

@@ -7,8 +7,10 @@ import java.lang.reflect.Method;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -21,11 +23,17 @@ import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.domain.Page;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.model.BeanWrapper;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.config.ResourceMapping;
import org.springframework.data.rest.repository.BaseUriAwareResource;
import org.springframework.data.rest.repository.PagingAndSorting;
import org.springframework.data.rest.repository.PersistentEntityResource;
import org.springframework.data.rest.repository.RepositoryConstraintViolationException;
import org.springframework.data.rest.repository.invoke.MethodParameterConversionService;
import org.springframework.data.rest.repository.support.ResourceMappingUtils;
@@ -36,7 +44,9 @@ import org.springframework.data.rest.webmvc.support.ValidationExceptionHandler;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkBuilder;
import org.springframework.hateoas.PagedResources;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.Resources;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@@ -46,6 +56,7 @@ import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.util.UriComponentsBuilder;
/**
* @author Jon Brisbin
@@ -243,6 +254,56 @@ public class AbstractRepositoryRestController implements ApplicationContextAware
return new ResponseEntity<Resource<?>>(resource, hdrs, status);
}
protected void addQueryParameters(HttpServletRequest request,
UriComponentsBuilder builder) {
for(Enumeration<String> names = request.getParameterNames(); names.hasMoreElements(); ) {
String name = names.nextElement();
String value = request.getParameter(name);
if(name.equals(config.getPageParamName()) || name.equals(config.getLimitParamName())) {
continue;
}
builder.queryParam(name, value);
}
}
protected Link searchLink(RepositoryRestRequest repoRequest,
int pageIncrement,
String method,
String rel) {
PagingAndSorting pageSort = repoRequest.getPagingAndSorting();
UriComponentsBuilder ucb = UriComponentsBuilder.fromUri(
entityLinks.linkFor(repoRequest.getPersistentEntity().getType())
.slash("search")
.slash(method)
.toUri()
);
ucb.queryParam(config.getPageParamName(), Math.max(pageSort.getPageNumber() + pageIncrement, 1))
.queryParam(config.getLimitParamName(), pageSort.getPageSize());
addQueryParameters(repoRequest.getRequest(), ucb);
return new Link(ucb.build().toString(), rel);
}
protected Link entitiesPageLink(RepositoryRestRequest repoRequest,
int pageIncrement,
String rel) {
PagingAndSorting pageSort = repoRequest.getPagingAndSorting();
UriComponentsBuilder ucb = UriComponentsBuilder.fromUri(
entityLinks.linkFor(repoRequest.getPersistentEntity().getType())
.toUri()
);
if(null != repoRequest.getRequest().getParameter(config.getPageParamName())) {
ucb.queryParam(config.getPageParamName(), Math.max(pageSort.getPageNumber() + pageIncrement, 1))
.queryParam(config.getLimitParamName(), pageSort.getPageSize());
}
addQueryParameters(repoRequest.getRequest(), ucb);
return new Link(ucb.build().toString(), rel);
}
protected List<Link> queryMethodLinks(URI baseUri, Class<?> domainType) {
List<Link> links = new ArrayList<Link>();
RepositoryInformation repoInfo = repositories.getRepositoryInformationFor(domainType);
@@ -269,4 +330,89 @@ public class AbstractRepositoryRestController implements ApplicationContextAware
return new Link(selfLink.getHref(), rel);
}
@SuppressWarnings({"unchecked"})
protected ResourceSupport resultToResourceSupport(RepositoryRestRequest repoRequest,
Object result,
List<Link> links,
Link prevLink,
Link nextLink) {
ResourceSupport resources;
if(result instanceof Page) {
Page page = (Page)result;
PagedResources.PageMetadata pageMeta = pageMetadata(page);
if(page.hasPreviousPage() && null != prevLink) {
links.add(prevLink);
}
if(page.hasNextPage() && null != nextLink) {
links.add(nextLink);
}
if(page.hasContent()) {
resources = entitiesToResource(repoRequest, page);
} else {
resources = new PagedResources(Collections.emptyList(), pageMeta);
}
} else if(result instanceof Iterable) {
resources = entitiesToResource(repoRequest, (Iterable)result);
} else if(null == result) {
resources = new Resources(EMPTY_RESOURCE_LIST);
} else {
PersistentEntityResource per = PersistentEntityResource.wrap(repoRequest.getPersistentEntity(),
result,
repoRequest.getBaseUri());
BeanWrapper wrapper = BeanWrapper.create(result, conversionService);
Link selfLink = entityLinks.linkForSingleResource(result.getClass(),
wrapper.getProperty(repoRequest.getPersistentEntity()
.getIdProperty()))
.withSelfRel();
per.add(selfLink);
resources = per;
}
resources.add(links);
return resources;
}
@SuppressWarnings({"unchecked"})
protected ResourceSupport entitiesToResource(RepositoryRestRequest repoRequest, Page page) {
PagedResources.PageMetadata pageMeta = pageMetadata(page);
Resources<Object> resource = (Resources<Object>)entitiesToResource(repoRequest, page.getContent());
return new PagedResources<Object>(resource.getContent(), pageMeta, resource.getLinks());
}
@SuppressWarnings({"unchecked"})
protected ResourceSupport entitiesToResource(RepositoryRestRequest repoRequest, Iterable entities) {
List<Resource<?>> resources = new ArrayList<Resource<?>>();
for(Object obj : entities) {
if(null == obj) {
resources.add(null);
continue;
}
PersistentEntity persistentEntity = repositories.getPersistentEntity(obj.getClass());
if(null == persistentEntity) {
resources.add(new BaseUriAwareResource<Object>(obj)
.setBaseUri(repoRequest.getBaseUri()));
continue;
}
BeanWrapper wrapper = BeanWrapper.create(obj, conversionService);
PersistentEntityResource per = PersistentEntityResource.wrap(persistentEntity, obj, repoRequest.getBaseUri());
Link selfLink = entityLinks.linkForSingleResource(persistentEntity.getType(),
wrapper.getProperty(persistentEntity.getIdProperty()))
.withSelfRel();
per.add(selfLink);
resources.add(per);
}
return new Resources(resources);
}
protected PagedResources.PageMetadata pageMetadata(Page page) {
return new PagedResources.PageMetadata(
page.getNumberOfElements(),
page.getNumber() + 1,
page.getTotalElements(),
page.getTotalPages()
);
}
}

View File

@@ -1,15 +1,15 @@
package org.springframework.data.rest.webmvc;
import static org.springframework.data.rest.core.util.UriUtils.*;
import java.io.Serializable;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.model.BeanWrapper;
@@ -17,7 +17,7 @@ import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.config.ResourceMapping;
import org.springframework.data.rest.repository.PageableResources;
import org.springframework.data.rest.repository.PagingAndSorting;
import org.springframework.data.rest.repository.PersistentEntityResource;
import org.springframework.data.rest.repository.context.AfterCreateEvent;
import org.springframework.data.rest.repository.context.AfterDeleteEvent;
@@ -31,7 +31,9 @@ import org.springframework.data.rest.repository.json.PersistentEntityToJsonSchem
import org.springframework.data.rest.repository.support.DomainObjectMerger;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.PagedResources;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.Resources;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@@ -87,9 +89,8 @@ public class RepositoryEntityController extends AbstractRepositoryRestController
}
)
@ResponseBody
public Resources<Resource<?>> listEntities(final RepositoryRestRequest repoRequest)
public ResourceSupport listEntities(final RepositoryRestRequest repoRequest)
throws ResourceNotFoundException {
List<Resource<?>> resources = new ArrayList<Resource<?>>();
List<Link> links = new ArrayList<Link>();
Iterable<?> results;
@@ -100,9 +101,10 @@ public class RepositoryEntityController extends AbstractRepositoryRestController
boolean hasPagingParams = (null != repoRequest.getRequest().getParameter(config.getPageParamName()));
boolean hasSortParams = (null != repoRequest.getRequest().getParameter(config.getSortParamName()));
if(repoMethodInvoker.hasFindAllPageable() && hasPagingParams) {
results = repoMethodInvoker.findAll(new PageRequest(repoRequest.getPagingAndSorting().getPageNumber(),
repoRequest.getPagingAndSorting().getPageSize(),
repoRequest.getPagingAndSorting().getSort()));
PagingAndSorting pageSort = repoRequest.getPagingAndSorting();
results = repoMethodInvoker.findAll(new PageRequest(pageSort.getPageNumber(),
pageSort.getPageSize(),
pageSort.getSort()));
} else if(repoMethodInvoker.hasFindAllSorted() && hasSortParams) {
results = repoMethodInvoker.findAll(repoRequest.getPagingAndSorting().getSort());
} else if(repoMethodInvoker.hasFindAll()) {
@@ -111,37 +113,25 @@ public class RepositoryEntityController extends AbstractRepositoryRestController
throw new ResourceNotFoundException();
}
for(Object o : results) {
BeanWrapper wrapper = BeanWrapper.create(o, conversionService);
Link selfLink = entityLinks.linkForSingleResource(repoRequest.getPersistentEntity().getType(),
wrapper.getProperty(repoRequest.getPersistentEntity()
.getIdProperty()))
.withSelfRel();
resources.add(new PersistentEntityResource<Object>(repoRequest.getPersistentEntity(),
o,
selfLink)
.setBaseUri(repoRequest.getBaseUri()));
}
ResourceMapping repoMapping = repoRequest.getRepositoryResourceMapping();
if(!repoMethodInvoker.getQueryMethods().isEmpty()) {
ResourceMapping repoMapping = repoRequest.getRepositoryResourceMapping();
links.add(new Link(buildUri(repoRequest.getBaseUri(), repoMapping.getPath(), "search").toString(),
repoMapping.getRel() + ".search"));
links.add(entityLinks.linkForSingleResource(repoRequest.getPersistentEntity().getType(), "search")
.withRel(repoMapping.getRel() + ".search"));
}
if(hasPagingParams || hasSortParams) {
PageRequest pr = new PageRequest(repoRequest.getPagingAndSorting().getPageNumber() + 1,
repoRequest.getPagingAndSorting().getPageSize(),
repoRequest.getPagingAndSorting().getSort()) {
@Override public int getOffset() {
return super.getOffset() - repoRequest.getPagingAndSorting().getPageSize();
}
};
return new PageableResources<Resource<?>>(resources, pr, links);
} else {
return new Resources<Resource<?>>(resources, links);
PagingAndSorting pageSort = repoRequest.getPagingAndSorting();
Link prevLink = null;
Link nextLink = null;
if(results instanceof Page) {
if(((Page)results).hasPreviousPage() && pageSort.getPageNumber() > 0) {
prevLink = entitiesPageLink(repoRequest, 0, "page.previous");
}
if(((Page)results).hasNextPage()) {
nextLink = entitiesPageLink(repoRequest, 1, "page.next");
}
}
return resultToResourceSupport(repoRequest, results, links, prevLink, nextLink);
}
@SuppressWarnings({"unchecked"})
@@ -153,22 +143,22 @@ public class RepositoryEntityController extends AbstractRepositoryRestController
}
)
@ResponseBody
public Resources<Resource<?>> listEntitiesCompact(RepositoryRestRequest repoRequest)
public ResourceSupport listEntitiesCompact(final RepositoryRestRequest repoRequest)
throws ResourceNotFoundException {
Resources<Resource<?>> resources = listEntities(repoRequest);
ResourceSupport resources = listEntities(repoRequest);
List<Link> links = new ArrayList<Link>(resources.getLinks());
for(Resource<?> resource : resources.getContent()) {
PersistentEntityResource<?> persistentEntityResource = (PersistentEntityResource<?>)resource;
links.add(resourceLink(repoRequest, persistentEntityResource));
if(resources instanceof Resources) {
for(Resource<?> resource : ((Resources<Resource<?>>)resources).getContent()) {
PersistentEntityResource<?> persistentEntityResource = (PersistentEntityResource<?>)resource;
links.add(resourceLink(repoRequest, persistentEntityResource));
}
}
boolean hasPagingParams = (null != repoRequest.getRequest().getParameter(config.getPageParamName()));
boolean hasSortParams = (null != repoRequest.getRequest().getParameter(config.getSortParamName()));
if(hasPagingParams || hasSortParams) {
return new PageableResources<Resource<?>>(EMPTY_RESOURCE_LIST, repoRequest.getPagingAndSorting(), links);
if(resources instanceof PagedResources) {
return new PagedResources(Collections.emptyList(), ((PagedResources)resources).getMetadata(), links);
} else {
return new Resources<Resource<?>>(EMPTY_RESOURCE_LIST, links);
return new Resources(Collections.emptyList(), links);
}
}

View File

@@ -18,24 +18,24 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl
*/
public class RepositoryRestHandlerAdapter extends ResourceProcessorInvokingHandlerAdapter {
@Autowired
private List<HandlerMethodArgumentResolver> argumentResolvers;
@Autowired
private List<HandlerMethodArgumentResolver> argumentResolvers;
@Override public void afterPropertiesSet() {
setCustomArgumentResolvers(argumentResolvers);
super.afterPropertiesSet();
}
@Override public void afterPropertiesSet() {
setCustomArgumentResolvers(argumentResolvers);
super.afterPropertiesSet();
}
@Override public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
@Override public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
@Override protected boolean supportsInternal(HandlerMethod handlerMethod) {
Class<?> controllerType = handlerMethod.getBeanType();
return (RepositoryController.class.isAssignableFrom(controllerType)
|| RepositoryEntityController.class.isAssignableFrom(controllerType)
|| RepositoryPropertyReferenceController.class.isAssignableFrom(controllerType)
|| RepositorySearchController.class.isAssignableFrom(controllerType));
}
@Override protected boolean supportsInternal(HandlerMethod handlerMethod) {
Class<?> controllerType = handlerMethod.getBeanType();
return (RepositoryController.class.isAssignableFrom(controllerType)
|| RepositoryEntityController.class.isAssignableFrom(controllerType)
|| RepositoryPropertyReferenceController.class.isAssignableFrom(controllerType)
|| RepositorySearchController.class.isAssignableFrom(controllerType));
}
}

View File

@@ -12,19 +12,18 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.model.BeanWrapper;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.config.ResourceMapping;
import org.springframework.data.rest.repository.BaseUriAwareResource;
import org.springframework.data.rest.repository.PersistentEntityResource;
import org.springframework.data.rest.repository.PagingAndSorting;
import org.springframework.data.rest.repository.invoke.RepositoryMethod;
import org.springframework.data.rest.repository.invoke.RepositoryMethodInvoker;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.Resources;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -75,8 +74,9 @@ public class RepositorySearchController extends AbstractRepositoryRestController
}
)
@ResponseBody
public Resource<?> query(RepositoryRestRequest repoRequest,
@PathVariable String method)
public ResourceSupport query(final RepositoryRestRequest repoRequest,
@PathVariable String repository,
@PathVariable String method)
throws ResourceNotFoundException {
RepositoryMethodInvoker repoMethodInvoker = repoRequest.getRepositoryMethodInvoker();
if(repoMethodInvoker.getQueryMethods().isEmpty()) {
@@ -99,17 +99,18 @@ public class RepositorySearchController extends AbstractRepositoryRestController
}
}
PagingAndSorting pageSort = repoRequest.getPagingAndSorting();
List<MethodParameter> methodParams = repoMethod.getParameters();
Object[] paramValues = new Object[methodParams.size()];
if(!methodParams.isEmpty()) {
for(int i = 0; i < paramValues.length; i++) {
MethodParameter param = methodParams.get(i);
if(Pageable.class.isAssignableFrom(param.getParameterType())) {
paramValues[i] = new PageRequest(repoRequest.getPagingAndSorting().getPageNumber(),
repoRequest.getPagingAndSorting().getPageSize(),
repoRequest.getPagingAndSorting().getSort());
paramValues[i] = new PageRequest(pageSort.getPageNumber(),
pageSort.getPageSize(),
pageSort.getSort());
} else if(Sort.class.isAssignableFrom(param.getParameterType())) {
paramValues[i] = repoRequest.getPagingAndSorting().getSort();
paramValues[i] = pageSort.getSort();
} else {
String paramName = repoMethod.getParameterNames().get(i);
String[] queryParamVals = repoRequest.getRequest().getParameterValues(paramName);
@@ -129,42 +130,18 @@ public class RepositorySearchController extends AbstractRepositoryRestController
}
}
BaseUriAwareResource resources;
List<Link> links = new ArrayList<Link>();
Object result = repoMethodInvoker.invokeQueryMethod(repoMethod, paramValues);
Link prevLink = null;
Link nextLink = null;
if(result instanceof Page) {
Page page = (Page)result;
if(page.hasPreviousPage()) {
repoRequest.addPrevLink(page, links);
if(((Page)result).hasPreviousPage() && pageSort.getPageNumber() > 0) {
prevLink = searchLink(repoRequest, 0, method, "page.previous");
}
if(page.hasNextPage()) {
repoRequest.addNextLink(page, links);
if(((Page)result).hasNextPage()) {
nextLink = searchLink(repoRequest, 1, method, "page.next");
}
if(page.hasContent()) {
resources = entitiesToResource(repoRequest, page.getContent());
} else {
resources = new BaseUriAwareResource(EMPTY_RESOURCE_LIST);
}
} else if(result instanceof Iterable) {
resources = entitiesToResource(repoRequest, (Iterable)result);
} else if(null == result) {
resources = new BaseUriAwareResource(EMPTY_RESOURCE_LIST);
} else {
PersistentEntityResource per = PersistentEntityResource.wrap(repoRequest.getPersistentEntity(),
result,
repoRequest.getBaseUri());
BeanWrapper wrapper = BeanWrapper.create(result, conversionService);
Link selfLink = entityLinks.linkForSingleResource(result.getClass(),
wrapper.getProperty(repoRequest.getPersistentEntity()
.getIdProperty()))
.withSelfRel();
per.add(selfLink);
resources = per;
}
resources.setBaseUri(repoRequest.getBaseUri())
.add(links);
return resources;
return resultToResourceSupport(repoRequest, result, new ArrayList<Link>(), prevLink, nextLink);
}
@RequestMapping(
@@ -175,56 +152,29 @@ public class RepositorySearchController extends AbstractRepositoryRestController
}
)
@ResponseBody
public Resource<?> queryCompact(RepositoryRestRequest repoRequest,
@PathVariable String method)
public ResourceSupport queryCompact(RepositoryRestRequest repoRequest,
@PathVariable String repository,
@PathVariable String method)
throws ResourceNotFoundException {
List<Link> links = new ArrayList<Link>();
Resource<?> resource = query(repoRequest, method);
ResourceSupport resource = query(repoRequest, repository, method);
links.addAll(resource.getLinks());
if(resource.getContent() instanceof Iterable) {
Iterable iter = (Iterable)resource.getContent();
for(Object obj : iter) {
if(resource instanceof Resources && ((Resources)resource).getContent() != null) {
for(Object obj : ((Resources)resource).getContent()) {
if(null != obj && obj instanceof Resource) {
Resource res = (Resource)obj;
links.add(resourceLink(repoRequest, res));
}
}
} else if(resource.getContent() instanceof Resource) {
Resource res = (Resource)resource.getContent();
} else if(resource instanceof Resource) {
Resource res = (Resource)resource;
links.add(resourceLink(repoRequest, res));
}
return new Resource<Object>(EMPTY_RESOURCE_LIST, links);
}
@SuppressWarnings({"unchecked"})
private BaseUriAwareResource entitiesToResource(RepositoryRestRequest repoRequest, Iterable entities) {
List<Resource<?>> resources = new ArrayList<Resource<?>>();
for(Object obj : entities) {
if(null == obj) {
resources.add(null);
break;
}
PersistentEntity persistentEntity = repositories.getPersistentEntity(obj.getClass());
if(null == persistentEntity) {
resources.add(new BaseUriAwareResource<Object>(obj)
.setBaseUri(repoRequest.getBaseUri()));
continue;
}
BeanWrapper wrapper = BeanWrapper.create(obj, conversionService);
PersistentEntityResource per = PersistentEntityResource.wrap(persistentEntity, obj, repoRequest.getBaseUri());
Link selfLink = entityLinks.linkForSingleResource(persistentEntity.getType(),
wrapper.getProperty(persistentEntity.getIdProperty()))
.withSelfRel();
per.add(selfLink);
resources.add(per);
}
return new BaseUriAwareResource(resources)
.setBaseUri(repoRequest.getBaseUri());
}
}