DATAREST-467 - RepositoryEntityLinks now exposes methods to create links to search resources.
RepositoryEntityLinks now exposes methods to obtain links to all search resources and individual ones, including overloads to pre-expand Pageable and Sort parameters potentially contained in the method signature. Search links now also contain a projection template variable if the type returned by the query method backing the search resource has projections registered.
This commit is contained in:
@@ -16,6 +16,9 @@
|
||||
package org.springframework.data.rest.core.mapping;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A {@link ResourceMapping} that is backed by a {@link Method}.
|
||||
@@ -44,4 +47,13 @@ public interface MethodResourceMapping extends ResourceMapping {
|
||||
* @return
|
||||
*/
|
||||
boolean isSortableResource();
|
||||
|
||||
/**
|
||||
* Returns the domain type that the query method returns. This will inspect wrapper types ({@link Collection}s,
|
||||
* {@link Map}s, {@link Optional}s etc.) for their elemtn or value types.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
* @since 2.3
|
||||
*/
|
||||
Class<?> getReturnedDomainType();
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.data.rest.core.Path;
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
@@ -48,6 +49,7 @@ class RepositoryMethodResourceMapping implements MethodResourceMapping {
|
||||
private final Method method;
|
||||
private final boolean paging;
|
||||
private final boolean sorting;
|
||||
private final RepositoryMetadata metadata;
|
||||
|
||||
private final List<ParameterMetadata> parameterMetadata;
|
||||
|
||||
@@ -57,7 +59,7 @@ class RepositoryMethodResourceMapping implements MethodResourceMapping {
|
||||
* @param method must not be {@literal null}.
|
||||
* @param resourceMapping must not be {@literal null}.
|
||||
*/
|
||||
public RepositoryMethodResourceMapping(Method method, ResourceMapping resourceMapping) {
|
||||
public RepositoryMethodResourceMapping(Method method, ResourceMapping resourceMapping, RepositoryMetadata metadata) {
|
||||
|
||||
Assert.notNull(method, "Method must not be null!");
|
||||
Assert.notNull(resourceMapping, "ResourceMapping must not be null!");
|
||||
@@ -76,6 +78,7 @@ class RepositoryMethodResourceMapping implements MethodResourceMapping {
|
||||
|
||||
this.paging = parameterTypes.contains(Pageable.class);
|
||||
this.sorting = parameterTypes.contains(Sort.class);
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
private static final List<ParameterMetadata> discoverParameterMetadata(Method method, String baseRel) {
|
||||
@@ -162,4 +165,13 @@ class RepositoryMethodResourceMapping implements MethodResourceMapping {
|
||||
public ResourceDescription getDescription() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.MethodResourceMapping#getProjectionSourceType()
|
||||
*/
|
||||
@Override
|
||||
public Class<?> getReturnedDomainType() {
|
||||
return metadata.getReturnedDomainClass(method);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ public class RepositoryResourceMappings implements ResourceMappings {
|
||||
if (resourceMapping.isExported()) {
|
||||
for (Method queryMethod : repositoryInformation.getQueryMethods()) {
|
||||
RepositoryMethodResourceMapping methodMapping = new RepositoryMethodResourceMapping(queryMethod,
|
||||
resourceMapping);
|
||||
resourceMapping, repositoryInformation);
|
||||
if (methodMapping.isExported()) {
|
||||
mappings.add(methodMapping);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2014 the original author or authors.
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,9 +17,11 @@ package org.springframework.data.rest.core.mapping;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.data.rest.core.Path;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -77,6 +79,46 @@ public class SearchResourceMappings implements Iterable<MethodResourceMapping>,
|
||||
return mapping == null ? null : mapping.getMethod();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mappings for all exported query methods.
|
||||
*
|
||||
* @return
|
||||
* @since 2.3
|
||||
*/
|
||||
public Iterable<MethodResourceMapping> getExportedMappings() {
|
||||
|
||||
Set<MethodResourceMapping> result = new HashSet<MethodResourceMapping>(mappings.values().size());
|
||||
|
||||
for (MethodResourceMapping mapping : this) {
|
||||
if (mapping.isExported()) {
|
||||
result.add(mapping);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link MappingResourceMetadata} for the given relation name.
|
||||
*
|
||||
* @param rel must not be {@literal null} or empty.
|
||||
* @return
|
||||
* @since 2.3
|
||||
*/
|
||||
public MethodResourceMapping getExportedMethodMappingForRel(String rel) {
|
||||
|
||||
Assert.hasText(rel, "Rel must not be null or empty!");
|
||||
|
||||
for (MethodResourceMapping mapping : this) {
|
||||
|
||||
if (mapping.isExported() && mapping.getRel().endsWith(rel)) {
|
||||
return mapping;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ResourceMapping#getPath()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2014 the original author or authors.
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -45,7 +45,7 @@ public class RepositoryMethodResourceMappingUnitTests {
|
||||
public void defaultsMappingToMethodName() throws Exception {
|
||||
|
||||
Method method = PersonRepository.class.getMethod("findByLastname", String.class);
|
||||
ResourceMapping mapping = new RepositoryMethodResourceMapping(method, resourceMapping);
|
||||
ResourceMapping mapping = getMappingFor(method);
|
||||
|
||||
assertThat(mapping.getPath(), is(new Path("findByLastname")));
|
||||
}
|
||||
@@ -54,7 +54,7 @@ public class RepositoryMethodResourceMappingUnitTests {
|
||||
public void usesConfiguredNameWithLeadingSlash() throws Exception {
|
||||
|
||||
Method method = PersonRepository.class.getMethod("findByFirstname", String.class);
|
||||
ResourceMapping mapping = new RepositoryMethodResourceMapping(method, resourceMapping);
|
||||
ResourceMapping mapping = getMappingFor(method);
|
||||
|
||||
assertThat(mapping.getPath(), is(new Path("bar")));
|
||||
}
|
||||
@@ -66,7 +66,7 @@ public class RepositoryMethodResourceMappingUnitTests {
|
||||
public void doesNotDiscoverAnyParametersIfNotAnnotated() throws Exception {
|
||||
|
||||
Method method = PersonRepository.class.getMethod("findByLastname", String.class);
|
||||
MethodResourceMapping mapping = new RepositoryMethodResourceMapping(method, resourceMapping);
|
||||
MethodResourceMapping mapping = getMappingFor(method);
|
||||
|
||||
assertThat(mapping.getParametersMetadata().getParameterNames(), is(emptyIterable()));
|
||||
}
|
||||
@@ -78,7 +78,7 @@ public class RepositoryMethodResourceMappingUnitTests {
|
||||
public void resolvesParameterNamesIfNotAnnotated() throws Exception {
|
||||
|
||||
Method method = PersonRepository.class.getMethod("findByFirstname", String.class);
|
||||
MethodResourceMapping mapping = new RepositoryMethodResourceMapping(method, resourceMapping);
|
||||
MethodResourceMapping mapping = getMappingFor(method);
|
||||
|
||||
assertThat(mapping.getParametersMetadata().getParameterNames(), hasSize(1));
|
||||
assertThat(mapping.getParametersMetadata().getParameterNames(), hasItem("firstname"));
|
||||
@@ -91,7 +91,7 @@ public class RepositoryMethodResourceMappingUnitTests {
|
||||
public void considersPagingFinderAPagingResource() throws Exception {
|
||||
|
||||
Method method = PersonRepository.class.getMethod("findByEmailAddress", String.class, Pageable.class);
|
||||
MethodResourceMapping mapping = new RepositoryMethodResourceMapping(method, resourceMapping);
|
||||
MethodResourceMapping mapping = getMappingFor(method);
|
||||
|
||||
assertThat(mapping.isPagingResource(), is(true));
|
||||
}
|
||||
@@ -100,7 +100,7 @@ public class RepositoryMethodResourceMappingUnitTests {
|
||||
public void usesMethodNameAsRelFallbackEvenIfPathIsConfigured() throws Exception {
|
||||
|
||||
Method method = PersonRepository.class.getMethod("findByEmailAddress", String.class, Pageable.class);
|
||||
MethodResourceMapping mapping = new RepositoryMethodResourceMapping(method, resourceMapping);
|
||||
MethodResourceMapping mapping = getMappingFor(method);
|
||||
|
||||
assertThat(mapping.getRel(), is("findByEmailAddress"));
|
||||
}
|
||||
@@ -112,16 +112,33 @@ public class RepositoryMethodResourceMappingUnitTests {
|
||||
public void considersResourceSortableIfSortParameterIsPresent() throws Exception {
|
||||
|
||||
Method method = PersonRepository.class.getMethod("findByEmailAddress", String.class, Sort.class);
|
||||
RepositoryMethodResourceMapping mapping = new RepositoryMethodResourceMapping(method, resourceMapping);
|
||||
RepositoryMethodResourceMapping mapping = getMappingFor(method);
|
||||
|
||||
assertThat(mapping.isSortableResource(), is(true));
|
||||
|
||||
method = PersonRepository.class.getMethod("findByEmailAddress", String.class, Pageable.class);
|
||||
mapping = new RepositoryMethodResourceMapping(method, resourceMapping);
|
||||
mapping = getMappingFor(method);
|
||||
|
||||
assertThat(mapping.isSortableResource(), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-467
|
||||
*/
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
public void returnsDomainTypeAsProjectionSourceType() throws Exception {
|
||||
|
||||
Method method = PersonRepository.class.getMethod("findByLastname", String.class);
|
||||
MethodResourceMapping mapping = getMappingFor(method);
|
||||
|
||||
assertThat(mapping.getReturnedDomainType(), is(equalTo((Class) Person.class)));
|
||||
}
|
||||
|
||||
private RepositoryMethodResourceMapping getMappingFor(Method method) {
|
||||
return new RepositoryMethodResourceMapping(method, resourceMapping, metadata);
|
||||
}
|
||||
|
||||
static class Person {}
|
||||
|
||||
interface PersonRepository extends Repository<Person, Long> {
|
||||
@@ -138,5 +155,7 @@ public class RepositoryMethodResourceMappingUnitTests {
|
||||
Page<Person> findByEmailAddress(String email, Pageable pageable);
|
||||
|
||||
Page<Person> findByEmailAddress(String email, Sort pageable);
|
||||
|
||||
int countByLastname(String lastname);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,24 +26,18 @@ import java.util.Map;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.repository.support.RepositoryInvoker;
|
||||
import org.springframework.data.rest.core.mapping.MethodResourceMapping;
|
||||
import org.springframework.data.rest.core.mapping.ParameterMetadata;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMappings;
|
||||
import org.springframework.data.rest.core.mapping.SearchResourceMappings;
|
||||
import org.springframework.data.rest.webmvc.support.DefaultedPageable;
|
||||
import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks;
|
||||
import org.springframework.data.web.HateoasSortHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.web.PagedResourcesAssembler;
|
||||
import org.springframework.hateoas.EntityLinks;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.LinkBuilder;
|
||||
import org.springframework.hateoas.Links;
|
||||
import org.springframework.hateoas.Resource;
|
||||
import org.springframework.hateoas.ResourceSupport;
|
||||
import org.springframework.hateoas.Resources;
|
||||
import org.springframework.hateoas.TemplateVariable;
|
||||
import org.springframework.hateoas.TemplateVariable.VariableType;
|
||||
import org.springframework.hateoas.TemplateVariables;
|
||||
import org.springframework.hateoas.UriTemplate;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
@@ -56,7 +50,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* Controller to lookup and execute searches on a given repository.
|
||||
@@ -70,7 +63,7 @@ class RepositorySearchController extends AbstractRepositoryRestController {
|
||||
private static final String SEARCH = "/search";
|
||||
private static final String BASE_MAPPING = "/{repository}" + SEARCH;
|
||||
|
||||
private final EntityLinks entityLinks;
|
||||
private final RepositoryEntityLinks entityLinks;
|
||||
private final ResourceMappings mappings;
|
||||
private final PagedResourcesAssembler<Object> assembler;
|
||||
private final HateoasSortHandlerMethodArgumentResolver sortResolver;
|
||||
@@ -84,7 +77,7 @@ class RepositorySearchController extends AbstractRepositoryRestController {
|
||||
* @param mappings must not be {@literal null}.
|
||||
*/
|
||||
@Autowired
|
||||
public RepositorySearchController(PagedResourcesAssembler<Object> assembler, EntityLinks entityLinks,
|
||||
public RepositorySearchController(PagedResourcesAssembler<Object> assembler, RepositoryEntityLinks entityLinks,
|
||||
ResourceMappings mappings, HateoasSortHandlerMethodArgumentResolver sortResolver) {
|
||||
|
||||
super(assembler);
|
||||
@@ -144,7 +137,7 @@ class RepositorySearchController extends AbstractRepositoryRestController {
|
||||
|
||||
verifySearchesExposed(resourceInformation);
|
||||
|
||||
Links queryMethodLinks = getSearchLinks(resourceInformation.getDomainType());
|
||||
Links queryMethodLinks = entityLinks.linksToSearchResources(resourceInformation.getDomainType());
|
||||
|
||||
if (queryMethodLinks.isEmpty()) {
|
||||
throw new ResourceNotFoundException();
|
||||
@@ -291,50 +284,6 @@ class RepositorySearchController extends AbstractRepositoryRestController {
|
||||
return resultToResources(result, assembler, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link Links} to the individual searches exposed.
|
||||
*
|
||||
* @param domainType the domain type we want to obtain the search links for.
|
||||
* @return
|
||||
*/
|
||||
private Links getSearchLinks(Class<?> domainType) {
|
||||
|
||||
List<Link> links = new ArrayList<Link>();
|
||||
|
||||
SearchResourceMappings searchMappings = mappings.getSearchResourceMappings(domainType);
|
||||
LinkBuilder builder = entityLinks.linkFor(domainType).slash(searchMappings.getPath());
|
||||
|
||||
for (MethodResourceMapping mapping : searchMappings) {
|
||||
|
||||
if (!mapping.isExported()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
TemplateVariables variables = new TemplateVariables();
|
||||
|
||||
for (ParameterMetadata metadata : mapping.getParametersMetadata()) {
|
||||
variables = variables.concat(new TemplateVariable(metadata.getName(), VariableType.REQUEST_PARAM));
|
||||
}
|
||||
|
||||
String href = builder.slash(mapping.getPath()).toString().concat(variables.toString());
|
||||
|
||||
Link link = new Link(href, mapping.getRel());
|
||||
|
||||
if (mapping.isPagingResource()) {
|
||||
link = assembler.appendPaginationParameterTemplates(link);
|
||||
} else if (mapping.isSortableResource()) {
|
||||
|
||||
TemplateVariables sortVariable = sortResolver.getSortTemplateVariables(null, UriComponentsBuilder
|
||||
.fromUriString(link.expand().getHref()).build());
|
||||
link = new Link(new UriTemplate(link.getHref()).with(sortVariable), link.getRel());
|
||||
}
|
||||
|
||||
links.add(link);
|
||||
}
|
||||
|
||||
return new Links(links);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the given {@link RootResourceInformation} has searches exposed.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.webmvc.config;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.rest.webmvc.support.PagingAndSortingTemplateVariables;
|
||||
import org.springframework.data.web.HateoasPageableHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.web.HateoasSortHandlerMethodArgumentResolver;
|
||||
import org.springframework.hateoas.TemplateVariables;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* {@link PagingAndSortingTemplateVariables} implementation to delegate to the HATEOAS-enabled
|
||||
* {@link HandlerMethodArgumentResolver}s for {@link Pageable} and {@link Sort}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 2.3
|
||||
*/
|
||||
class ArgumentResolverPagingAndSortingTemplateVariables implements PagingAndSortingTemplateVariables {
|
||||
|
||||
private static final Set<Class<?>> SUPPORTED_TYPES = Collections.unmodifiableSet(new HashSet<Class<?>>(Arrays
|
||||
.<Class<?>> asList(Pageable.class, Sort.class)));
|
||||
|
||||
private final HateoasPageableHandlerMethodArgumentResolver pagingResolver;
|
||||
private final HateoasSortHandlerMethodArgumentResolver sortResolver;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ArgumentResolverPagingAndSortingTemplateVariables} using the given
|
||||
* {@link HateoasPageableHandlerMethodArgumentResolver} and {@link HateoasSortHandlerMethodArgumentResolver}.
|
||||
*
|
||||
* @param pagingResolver must not be {@literal null}.
|
||||
* @param sortResolver must not be {@literal null}.
|
||||
*/
|
||||
public ArgumentResolverPagingAndSortingTemplateVariables(HateoasPageableHandlerMethodArgumentResolver pagingResolver,
|
||||
HateoasSortHandlerMethodArgumentResolver sortResolver) {
|
||||
|
||||
Assert.notNull(pagingResolver, "HateoasPageableHandlerMethodArgumentResolver must not be null!");
|
||||
Assert.notNull(sortResolver, "HateoasSortHandlerMethodArgumentResolver must not be null!");
|
||||
|
||||
this.pagingResolver = pagingResolver;
|
||||
this.sortResolver = sortResolver;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.webmvc.support.PagingAndSortingTemplateVariables#getPaginationTemplateVariables(org.springframework.core.MethodParameter, org.springframework.web.util.UriComponents)
|
||||
*/
|
||||
@Override
|
||||
public TemplateVariables getPaginationTemplateVariables(MethodParameter parameter, UriComponents components) {
|
||||
return pagingResolver.getPaginationTemplateVariables(parameter, components);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.webmvc.support.PagingAndSortingTemplateVariables#getSortTemplateVariables(org.springframework.core.MethodParameter, org.springframework.web.util.UriComponents)
|
||||
*/
|
||||
@Override
|
||||
public TemplateVariables getSortTemplateVariables(MethodParameter parameter, UriComponents template) {
|
||||
return sortResolver.getSortTemplateVariables(parameter, template);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.mvc.UriComponentsContributor#enhance(org.springframework.web.util.UriComponentsBuilder, org.springframework.core.MethodParameter, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public void enhance(UriComponentsBuilder builder, MethodParameter parameter, Object value) {
|
||||
|
||||
if (value instanceof Pageable) {
|
||||
pagingResolver.enhance(builder, parameter, value);
|
||||
} else if (value instanceof Sort) {
|
||||
sortResolver.enhance(builder, parameter, value);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.mvc.UriComponentsContributor#supportsParameter(org.springframework.core.MethodParameter)
|
||||
*/
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return SUPPORTED_TYPES.contains(parameter.getParameterType());
|
||||
}
|
||||
}
|
||||
@@ -86,6 +86,7 @@ import org.springframework.data.rest.webmvc.support.DefaultedPageableHandlerMeth
|
||||
import org.springframework.data.rest.webmvc.support.ETagArgumentResolver;
|
||||
import org.springframework.data.rest.webmvc.support.HttpMethodHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.rest.webmvc.support.JpaHelper;
|
||||
import org.springframework.data.rest.webmvc.support.PagingAndSortingTemplateVariables;
|
||||
import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks;
|
||||
import org.springframework.data.util.AnnotatedTypeScanner;
|
||||
import org.springframework.data.web.HateoasPageableHandlerMethodArgumentResolver;
|
||||
@@ -303,7 +304,11 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
*/
|
||||
@Bean
|
||||
public RepositoryEntityLinks entityLinks() {
|
||||
return new RepositoryEntityLinks(repositories(), resourceMappings(), config(), pageableResolver(),
|
||||
|
||||
PagingAndSortingTemplateVariables templateVariables = new ArgumentResolverPagingAndSortingTemplateVariables(
|
||||
pageableResolver(), sortResolver());
|
||||
|
||||
return new RepositoryEntityLinks(repositories(), resourceMappings(), config(), templateVariables,
|
||||
backendIdConverterRegistry());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.webmvc.support;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.hateoas.TemplateVariables;
|
||||
import org.springframework.hateoas.mvc.UriComponentsContributor;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
|
||||
/**
|
||||
* Interface to abstract the access of {@link TemplateVariables} for pagination and sorting.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 2.3
|
||||
*/
|
||||
public interface PagingAndSortingTemplateVariables extends UriComponentsContributor {
|
||||
|
||||
/**
|
||||
* Returns the {@link TemplateVariables} for pagination.
|
||||
*
|
||||
* @param parameter can be {@literal null}.
|
||||
* @param components must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
TemplateVariables getPaginationTemplateVariables(MethodParameter parameter, UriComponents components);
|
||||
|
||||
/**
|
||||
* Returns the {@link TemplateVariables} for sorting.
|
||||
*
|
||||
* @param parameter can be {@literal null}.
|
||||
* @param components must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
TemplateVariables getSortTemplateVariables(MethodParameter parameter, UriComponents components);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,27 +18,36 @@ package org.springframework.data.rest.webmvc.support;
|
||||
import static org.springframework.hateoas.TemplateVariable.VariableType.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.config.ProjectionDefinitionConfiguration;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.core.mapping.MethodResourceMapping;
|
||||
import org.springframework.data.rest.core.mapping.ParameterMetadata;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMapping;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMappings;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMetadata;
|
||||
import org.springframework.data.rest.core.mapping.SearchResourceMappings;
|
||||
import org.springframework.data.rest.webmvc.BaseUri;
|
||||
import org.springframework.data.rest.webmvc.spi.BackendIdConverter;
|
||||
import org.springframework.data.rest.webmvc.spi.BackendIdConverter.DefaultIdConverter;
|
||||
import org.springframework.data.web.HateoasPageableHandlerMethodArgumentResolver;
|
||||
import org.springframework.hateoas.EntityLinks;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.LinkBuilder;
|
||||
import org.springframework.hateoas.Links;
|
||||
import org.springframework.hateoas.TemplateVariable;
|
||||
import org.springframework.hateoas.TemplateVariable.VariableType;
|
||||
import org.springframework.hateoas.TemplateVariables;
|
||||
import org.springframework.hateoas.UriTemplate;
|
||||
import org.springframework.hateoas.core.AbstractEntityLinks;
|
||||
import org.springframework.plugin.core.PluginRegistry;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
@@ -53,7 +62,7 @@ public class RepositoryEntityLinks extends AbstractEntityLinks {
|
||||
private final Repositories repositories;
|
||||
private final ResourceMappings mappings;
|
||||
private final RepositoryRestConfiguration config;
|
||||
private final HateoasPageableHandlerMethodArgumentResolver resolver;
|
||||
private final PagingAndSortingTemplateVariables templateVariables;
|
||||
private final PluginRegistry<BackendIdConverter, Class<?>> idConverters;
|
||||
|
||||
/**
|
||||
@@ -62,24 +71,24 @@ public class RepositoryEntityLinks extends AbstractEntityLinks {
|
||||
* @param repositories must not be {@literal null}.
|
||||
* @param mappings must not be {@literal null}.
|
||||
* @param config must not be {@literal null}.
|
||||
* @param resolver must not be {@literal null}.
|
||||
* @param pagingAndSortingTemplateVariables must not be {@literal null}.
|
||||
* @param idConverters must not be {@literal null}.
|
||||
*/
|
||||
@Autowired
|
||||
public RepositoryEntityLinks(Repositories repositories, ResourceMappings mappings,
|
||||
RepositoryRestConfiguration config, HateoasPageableHandlerMethodArgumentResolver resolver,
|
||||
RepositoryRestConfiguration config, PagingAndSortingTemplateVariables templateVariables,
|
||||
PluginRegistry<BackendIdConverter, Class<?>> idConverters) {
|
||||
|
||||
Assert.notNull(repositories, "Repositories must not be null!");
|
||||
Assert.notNull(mappings, "ResourceMappings must not be null!");
|
||||
Assert.notNull(config, "RepositoryRestConfiguration must not be null!");
|
||||
Assert.notNull(resolver, "HateoasPageableHandlerMethodArgumentResolver must not be null!");
|
||||
Assert.notNull(templateVariables, "PagingAndSortingTemplateVariables must not be null!");
|
||||
Assert.notNull(idConverters, "Id converter registry must not be null!");
|
||||
|
||||
this.repositories = repositories;
|
||||
this.mappings = mappings;
|
||||
this.config = config;
|
||||
this.resolver = resolver;
|
||||
this.templateVariables = templateVariables;
|
||||
this.idConverters = idConverters;
|
||||
}
|
||||
|
||||
@@ -112,33 +121,23 @@ public class RepositoryEntityLinks extends AbstractEntityLinks {
|
||||
return linkFor(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the link to to the paged colelction resource for the given type, pre-expanding the
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param pageable the pageable to can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public Link linkToPagedResource(Class<?> type, Pageable pageable) {
|
||||
|
||||
ResourceMetadata metadata = mappings.getMappingFor(type);
|
||||
TemplateVariables variables = new TemplateVariables();
|
||||
String href = linkFor(type).withSelfRel().getHref();
|
||||
String href = linkFor(type).toString();
|
||||
UriComponents components = prepareUri(href, metadata, pageable);
|
||||
|
||||
if (metadata.isPagingResource()) {
|
||||
TemplateVariables variables = getTemplateVariables(components, metadata, pageable).//
|
||||
concat(getProjectionVariable(type));
|
||||
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(href);
|
||||
|
||||
if (pageable != null) {
|
||||
resolver.enhance(builder, null, pageable);
|
||||
}
|
||||
|
||||
href = builder.build().toString();
|
||||
|
||||
variables = variables.concat(resolver.getPaginationTemplateVariables(null, builder.build()));
|
||||
}
|
||||
|
||||
ProjectionDefinitionConfiguration projectionConfiguration = config.projectionConfiguration();
|
||||
|
||||
if (projectionConfiguration.hasProjectionFor(type)) {
|
||||
variables = variables.concat(new TemplateVariable(projectionConfiguration.getParameterName(), REQUEST_PARAM));
|
||||
}
|
||||
|
||||
return variables.asList().isEmpty() ? linkFor(type).withRel(metadata.getRel()) : new Link(new UriTemplate(href,
|
||||
variables), metadata.getRel());
|
||||
return new Link(new UriTemplate(href, variables), metadata.getRel());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -163,16 +162,232 @@ public class RepositoryEntityLinks extends AbstractEntityLinks {
|
||||
String mappedId = idConverters.getPluginFor(type, DefaultIdConverter.INSTANCE).toRequestId((Serializable) id, type);
|
||||
|
||||
Link link = linkFor(type).slash(mappedId).withRel(metadata.getItemResourceRel());
|
||||
ProjectionDefinitionConfiguration projectionConfiguration = config.projectionConfiguration();
|
||||
return new Link(new UriTemplate(link.getHref(), getProjectionVariable(type)).toString(),
|
||||
metadata.getItemResourceRel());
|
||||
}
|
||||
|
||||
if (!projectionConfiguration.hasProjectionFor(type)) {
|
||||
return link;
|
||||
/**
|
||||
* Returns all links to search resource for the given type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
* @since 2.3
|
||||
*/
|
||||
public Links linksToSearchResources(Class<?> type) {
|
||||
return linksToSearchResources(type, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all link to search resources for the given type, pre-expanded with the given {@link Pageable} if
|
||||
* applicable.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param pageable can be {@literal null}.
|
||||
* @return
|
||||
* @since 2.3
|
||||
*/
|
||||
public Links linksToSearchResources(Class<?> type, Pageable pageable) {
|
||||
return linksToSearchResources(type, pageable, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all link to search resources for the given type, pre-expanded with the given {@link Sort} if applicable.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param sort can be {@literal null}.
|
||||
* @return
|
||||
* @since 2.3
|
||||
*/
|
||||
public Links linksToSearchResources(Class<?> type, Sort sort) {
|
||||
return linksToSearchResources(type, null, sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the link to the search resource with the given rel for a given type.
|
||||
*
|
||||
* @param domainType must not be {@literal null}.
|
||||
* @param rel must not be {@literal null} or empty.
|
||||
* @return
|
||||
* @since 2.3
|
||||
*/
|
||||
public Link linkToSearchResource(Class<?> domainType, String rel) {
|
||||
return getSearchResourceLinkFor(domainType, rel, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the link to the search resource with the given rel for a given type. Uses the given {@link Pageable} to
|
||||
* pre-expand potentially available template variables.
|
||||
*
|
||||
* @param domainType must not be {@literal null}.
|
||||
* @param rel must not be {@literal null} or empty.
|
||||
* @param pageable can be {@literal null}.
|
||||
* @return
|
||||
* @since 2.3
|
||||
*/
|
||||
public Link linkToSearchResource(Class<?> domainType, String rel, Pageable pageable) {
|
||||
return getSearchResourceLinkFor(domainType, rel, pageable, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the link to the search resource with the given rel for a given type. Uses the given {@link Sort} to
|
||||
* pre-expand potentially available template variables.
|
||||
*
|
||||
* @param domainType must not be {@literal null}.
|
||||
* @param rel must not be {@literal null} or empty.
|
||||
* @param sort can be {@literal null}.
|
||||
* @return
|
||||
* @since 2.3
|
||||
*/
|
||||
public Link linkToSearchResource(Class<?> domainType, String rel, Sort sort) {
|
||||
return getSearchResourceLinkFor(domainType, rel, null, sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all links to search resources of the given type. Pre-expands the template with the given {@link Pageable}
|
||||
* and {@link Sort} if applicable.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param pageable can be {@literal null}.
|
||||
* @param sort can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private Links linksToSearchResources(Class<?> type, Pageable pageable, Sort sort) {
|
||||
|
||||
List<Link> links = new ArrayList<Link>();
|
||||
|
||||
SearchResourceMappings searchMappings = mappings.getSearchResourceMappings(type);
|
||||
|
||||
for (MethodResourceMapping mapping : searchMappings.getExportedMappings()) {
|
||||
links.add(getSearchResourceLinkFor(type, mapping.getRel(), pageable, sort));
|
||||
}
|
||||
|
||||
String parameterName = projectionConfiguration.getParameterName();
|
||||
TemplateVariables templateVariables = new TemplateVariables(new TemplateVariable(parameterName, REQUEST_PARAM));
|
||||
UriTemplate template = new UriTemplate(link.getHref(), templateVariables);
|
||||
return new Links(links);
|
||||
}
|
||||
|
||||
return new Link(template.toString(), metadata.getItemResourceRel());
|
||||
/**
|
||||
* Returns the link pointing to the search resource with the given rel of the given type and pre-expands the
|
||||
* calculated URi tempalte with the given {@link Pageable} and {@link Sort}.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param rel must not be {@literal null} or empty.
|
||||
* @param pageable can be {@literal null}.
|
||||
* @param sort can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private Link getSearchResourceLinkFor(Class<?> type, String rel, Pageable pageable, Sort sort) {
|
||||
|
||||
Assert.notNull(type, "Domain type must not be null!");
|
||||
Assert.hasText(rel, "Relation name must not be null or empty!");
|
||||
|
||||
SearchResourceMappings searchMappings = mappings.getSearchResourceMappings(type);
|
||||
MethodResourceMapping mapping = searchMappings.getExportedMethodMappingForRel(rel);
|
||||
|
||||
if (mapping == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
LinkBuilder builder = linkFor(type).slash(mappings.getSearchResourceMappings(type).getPath());
|
||||
UriComponents uriComponents = prepareUri(builder.toString(), mapping, pageable, sort);
|
||||
|
||||
TemplateVariables variables = getParameterVariables(mapping).//
|
||||
concat(getTemplateVariables(uriComponents, mapping, pageable, sort)).//
|
||||
concat(getProjectionVariable(mapping.getReturnedDomainType()));
|
||||
|
||||
return new Link(new UriTemplate(builder.slash(mapping.getPath()).toString(), variables), mapping.getRel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link TemplateVariables} to be added for pagination for the given {@link UriComponentsBuilder} in case
|
||||
* the given {@link ResourceMapping} is a paging resource.
|
||||
*
|
||||
* @param components must not be {@literal null}.
|
||||
* @param mapping must not be {@literal null}.
|
||||
* @param pageable can be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
private TemplateVariables getTemplateVariables(UriComponents components, ResourceMapping mapping, Pageable pageable) {
|
||||
|
||||
if (mapping.isPagingResource()) {
|
||||
return templateVariables.getPaginationTemplateVariables(null, components);
|
||||
} else {
|
||||
return TemplateVariables.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all {@link TemplateVariables} that need to be added based on the given {@link UriComponents},
|
||||
* {@link MethodResourceMapping}, {@link Pageable} and {@link Sort}.
|
||||
*
|
||||
* @param components must not be {@literal null}.
|
||||
* @param mapping must not be {@literal null}.
|
||||
* @param pageable can be {@literal null}
|
||||
* @param sort can be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
private TemplateVariables getTemplateVariables(UriComponents components, MethodResourceMapping mapping,
|
||||
Pageable pageable, Sort sort) {
|
||||
|
||||
if (mapping.isSortableResource()) {
|
||||
return templateVariables.getSortTemplateVariables(null, components);
|
||||
} else {
|
||||
return getTemplateVariables(components, mapping, pageable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link TemplateVariables} for the projection parameter if projections are vonfigured for the given
|
||||
* type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
private TemplateVariables getProjectionVariable(Class<?> type) {
|
||||
|
||||
ProjectionDefinitionConfiguration projectionConfiguration = config.projectionConfiguration();
|
||||
|
||||
if (projectionConfiguration.hasProjectionFor(type)) {
|
||||
return new TemplateVariables(new TemplateVariable(projectionConfiguration.getParameterName(), REQUEST_PARAM));
|
||||
} else {
|
||||
return TemplateVariables.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link TemplateVariables} for all parameters of the given {@link MethodResourceMapping}.
|
||||
*
|
||||
* @param mapping must not be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
private TemplateVariables getParameterVariables(MethodResourceMapping mapping) {
|
||||
|
||||
List<TemplateVariable> variables = new ArrayList<TemplateVariable>();
|
||||
|
||||
for (ParameterMetadata metadata : mapping.getParametersMetadata()) {
|
||||
variables.add(new TemplateVariable(metadata.getName(), VariableType.REQUEST_PARAM));
|
||||
}
|
||||
|
||||
return new TemplateVariables(variables);
|
||||
}
|
||||
|
||||
private UriComponents prepareUri(String uri, MethodResourceMapping mapping, Pageable pageable, Sort sort) {
|
||||
|
||||
if (mapping.isSortableResource()) {
|
||||
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(uri);
|
||||
templateVariables.enhance(uriBuilder, null, sort);
|
||||
return uriBuilder.build();
|
||||
} else {
|
||||
return prepareUri(uri, mapping, pageable);
|
||||
}
|
||||
}
|
||||
|
||||
private UriComponents prepareUri(String uri, ResourceMapping mapping, Pageable pageable) {
|
||||
|
||||
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(uri);
|
||||
|
||||
if (mapping.isPagingResource()) {
|
||||
templateVariables.enhance(uriBuilder, null, pageable);
|
||||
}
|
||||
|
||||
return uriBuilder.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2014 the original author or authors.
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -65,12 +65,14 @@ public class RepositorySearchControllerIntegrationTests extends AbstractControll
|
||||
ResourceSupport resource = controller.listSearches(request);
|
||||
|
||||
ResourceTester tester = ResourceTester.of(resource);
|
||||
tester.assertNumberOfLinks(4);
|
||||
tester.assertHasLinkEndingWith("findFirstPersonByFirstName", "findFirstPersonByFirstName{?firstName}");
|
||||
tester.assertHasLinkEndingWith("firstname", "firstname{?firstName,page,size,sort}");
|
||||
tester.assertNumberOfLinks(5);
|
||||
tester.assertHasLinkEndingWith("findFirstPersonByFirstName", "findFirstPersonByFirstName{?firstname,projection}");
|
||||
tester.assertHasLinkEndingWith("firstname", "firstname{?firstname,page,size,sort,projection}");
|
||||
tester.assertHasLinkEndingWith("lastname", "lastname{?lastname,sort,projection}");
|
||||
tester.assertHasLinkEndingWith("findByCreatedUsingISO8601Date",
|
||||
"findByCreatedUsingISO8601Date{?date,page,size,sort}");
|
||||
tester.assertHasLinkEndingWith("findByCreatedGreaterThan", "findByCreatedGreaterThan{?date,page,size,sort}");
|
||||
"findByCreatedUsingISO8601Date{?date,page,size,sort,projection}");
|
||||
tester.assertHasLinkEndingWith("findByCreatedGreaterThan",
|
||||
"findByCreatedGreaterThan{?date,page,size,sort,projection}");
|
||||
}
|
||||
|
||||
@Test(expected = ResourceNotFoundException.class)
|
||||
@@ -86,7 +88,7 @@ public class RepositorySearchControllerIntegrationTests extends AbstractControll
|
||||
@Test
|
||||
public void executesSearchAgainstRepository() {
|
||||
|
||||
RequestParameters parameters = new RequestParameters("firstName", "John");
|
||||
RequestParameters parameters = new RequestParameters("firstname", "John");
|
||||
RootResourceInformation resourceInformation = getResourceInformation(Person.class);
|
||||
|
||||
ResponseEntity<Object> response = controller.executeSearch(resourceInformation, getRequest(parameters),
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.webmvc.config;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Matchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
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.rest.webmvc.support.PagingAndSortingTemplateVariables;
|
||||
import org.springframework.data.web.HateoasPageableHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.web.HateoasSortHandlerMethodArgumentResolver;
|
||||
import org.springframework.hateoas.mvc.UriComponentsContributor;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ArgumentResolverPagingAndSortingTemplateVariables}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class ArgumentResolverPagingAndSortingTemplateVariablesUnitTests {
|
||||
|
||||
@Mock HateoasPageableHandlerMethodArgumentResolver pageableResolver;
|
||||
@Mock HateoasSortHandlerMethodArgumentResolver sortResolver;
|
||||
@Mock UriComponentsBuilder builder;
|
||||
|
||||
/**
|
||||
* @see DATAREST-467
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullArgumentResolverForPageable() {
|
||||
new ArgumentResolverPagingAndSortingTemplateVariables(null, sortResolver);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-467
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullArgumentResolverForSort() {
|
||||
new ArgumentResolverPagingAndSortingTemplateVariables(pageableResolver, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-467
|
||||
*/
|
||||
@Test
|
||||
public void supportsPageableAndSortMethodParameters() {
|
||||
|
||||
PagingAndSortingTemplateVariables variables = new ArgumentResolverPagingAndSortingTemplateVariables(
|
||||
pageableResolver, sortResolver);
|
||||
|
||||
assertThat(variables.supportsParameter(getParameterMock(Pageable.class)), is(true));
|
||||
assertThat(variables.supportsParameter(getParameterMock(Sort.class)), is(true));
|
||||
assertThat(variables.supportsParameter(getParameterMock(Object.class)), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-467
|
||||
*/
|
||||
@Test
|
||||
public void forwardsEnhanceRequestForPageable() {
|
||||
assertForwardsEnhanceFor(new PageRequest(0, 10), pageableResolver, sortResolver);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-467
|
||||
*/
|
||||
@Test
|
||||
public void forwardsEnhanceRequestForSort() {
|
||||
assertForwardsEnhanceFor(new Sort("property"), sortResolver, pageableResolver);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private static MethodParameter getParameterMock(Class<?> type) {
|
||||
|
||||
MethodParameter parameter = mock(MethodParameter.class);
|
||||
when(parameter.getParameterType()).thenReturn((Class) type);
|
||||
|
||||
return parameter;
|
||||
}
|
||||
|
||||
private void assertForwardsEnhanceFor(Object value, UriComponentsContributor expected,
|
||||
UriComponentsContributor unexpected) {
|
||||
|
||||
PagingAndSortingTemplateVariables variables = new ArgumentResolverPagingAndSortingTemplateVariables(
|
||||
pageableResolver, sortResolver);
|
||||
|
||||
variables.enhance(builder, null, value);
|
||||
|
||||
verify(expected, times(1)).enhance(builder, null, value);
|
||||
verify(unexpected, times(0)).enhance(Mockito.any(UriComponentsBuilder.class), Mockito.any(MethodParameter.class),
|
||||
anyObject());
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2014 the original author or authors.
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -570,7 +570,7 @@ public class JpaWebTests extends CommonWebTests {
|
||||
|
||||
// Assert sort options advertised
|
||||
assertThat(findBySortedLink.isTemplated(), is(true));
|
||||
assertThat(findBySortedLink.getVariableNames(), contains("sort"));
|
||||
assertThat(findBySortedLink.getVariableNames(), hasItems("sort", "projection"));
|
||||
|
||||
// Assert results returned as specified
|
||||
client.follow(findBySortedLink.expand("title,desc")).//
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2014 the original author or authors.
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,9 +16,11 @@
|
||||
package org.springframework.data.rest.webmvc.jpa;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
@@ -37,9 +39,12 @@ import org.springframework.format.annotation.DateTimeFormat.ISO;
|
||||
public interface PersonRepository extends PagingAndSortingRepository<Person, Long> {
|
||||
|
||||
@RestResource(rel = "firstname", path = "firstname")
|
||||
Page<Person> findByFirstName(@Param("firstName") String firstName, Pageable pageable);
|
||||
Page<Person> findByFirstName(@Param("firstname") String firstName, Pageable pageable);
|
||||
|
||||
Person findFirstPersonByFirstName(@Param("firstName") String firstName);
|
||||
@RestResource(rel = "lastname", path = "lastname")
|
||||
List<Person> findByLastName(@Param("lastname") String lastName, Sort sort);
|
||||
|
||||
Person findFirstPersonByFirstName(@Param("firstname") String firstName);
|
||||
|
||||
Page<Person> findByCreatedGreaterThan(@Param("date") Date date, Pageable pageable);
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import static org.junit.Assert.*;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.webmvc.AbstractControllerIntegrationTests;
|
||||
import org.springframework.data.rest.webmvc.jpa.Book;
|
||||
@@ -28,6 +29,7 @@ import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig;
|
||||
import org.springframework.data.rest.webmvc.jpa.Order;
|
||||
import org.springframework.data.rest.webmvc.jpa.Person;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.Links;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
/**
|
||||
@@ -94,4 +96,72 @@ public class RepositoryEntityLinksIntegrationTests extends AbstractControllerInt
|
||||
assertThat(link.getVariableNames(), hasSize(2));
|
||||
assertThat(link.getVariableNames(), hasItems("sort", "projection"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-467
|
||||
*/
|
||||
@Test
|
||||
public void returnsLinksToSearchResources() {
|
||||
|
||||
Links links = entityLinks.linksToSearchResources(Person.class);
|
||||
|
||||
assertThat(links.hasLink("firstname"), is(true));
|
||||
|
||||
Link firstnameLink = links.getLink("firstname");
|
||||
assertThat(firstnameLink.isTemplated(), is(true));
|
||||
assertThat(firstnameLink.getVariableNames(), hasItems("page", "size"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-467
|
||||
*/
|
||||
@Test
|
||||
public void returnsLinkToSearchResource() {
|
||||
|
||||
Link link = entityLinks.linkToSearchResource(Person.class, "firstname");
|
||||
|
||||
assertThat(link, is(notNullValue()));
|
||||
assertThat(link.isTemplated(), is(true));
|
||||
assertThat(link.getVariableNames(), hasItems("firstname", "page", "size"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-467
|
||||
*/
|
||||
@Test
|
||||
public void prepopulatesPaginationInformationForSearchResourceLink() {
|
||||
|
||||
Link link = entityLinks.linkToSearchResource(Person.class, "firstname", new PageRequest(0, 10));
|
||||
|
||||
assertThat(link, is(notNullValue()));
|
||||
assertThat(link.isTemplated(), is(true));
|
||||
assertThat(link.getVariableNames(), hasItem("firstname"));
|
||||
assertThat(link.getVariableNames(), not(hasItems("page", "size")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-467
|
||||
*/
|
||||
@Test
|
||||
public void returnsTemplatedLinkForSortedSearchResource() {
|
||||
|
||||
Link link = entityLinks.linkToSearchResource(Person.class, "lastname");
|
||||
|
||||
assertThat(link.isTemplated(), is(true));
|
||||
assertThat(link.getVariableNames(), hasItems("lastname", "sort"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-467
|
||||
*/
|
||||
@Test
|
||||
public void prepopulatesSortInformationForSearchResourceLink() {
|
||||
|
||||
Link link = entityLinks.linkToSearchResource(Person.class, "lastname", new Sort("firstname"));
|
||||
|
||||
assertThat(link, is(notNullValue()));
|
||||
assertThat(link.isTemplated(), is(true));
|
||||
assertThat(link.getVariableNames(), hasItem("lastname"));
|
||||
assertThat(link.getVariableNames(), not(hasItems("sort")));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user