From b663b5ff733789f6d0761d4e6c14b1128eb15719 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Tue, 24 Nov 2015 15:35:39 +0100 Subject: [PATCH] DATAREST-712 - Fixed application of Querydsl bindings to only kick in for collection resources. The Querydsl integration in RootResourceInformationHandlerMethodArgumentResolver now only kicks in if the controller method parameter is annotated with @QuerydslPredicate. This allows us to apply the QuerydslBindings for collection resources but make sure they aren't accidentally applied during executions of query methods etc. Improved the type lookup for query method execution to make sure the correct mapping is looked up for collection parameters. --- pom.xml | 7 ++++++ .../webmvc/RepositoryEntityController.java | 23 ++++++++----------- .../webmvc/RepositorySearchController.java | 10 +++++++- ...ormationHandlerMethodArgumentResolver.java | 9 +++++--- ...ormationHandlerMethodArgumentResolver.java | 7 +++--- ...andlerMethodArgumentResolverUnitTests.java | 18 ++++++++++----- .../rest/webmvc/mongodb/MongoWebTests.java | 18 +++++++++++++++ .../rest/webmvc/mongodb/UserRepository.java | 6 ++++- 8 files changed, 70 insertions(+), 28 deletions(-) diff --git a/pom.xml b/pom.xml index 86fb0b081..3b91abfe4 100644 --- a/pom.xml +++ b/pom.xml @@ -114,6 +114,13 @@ ${springdata.mongodb} test + + + com.querydsl + querydsl-mongodb + ${querydsl} + test + diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java index a680c7622..e0e839cb0 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java @@ -34,6 +34,7 @@ import org.springframework.data.domain.Sort; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.model.ConvertingPropertyAccessor; +import org.springframework.data.querydsl.binding.QuerydslPredicate; import org.springframework.data.repository.support.Repositories; import org.springframework.data.repository.support.RepositoryInvoker; import org.springframework.data.rest.core.config.RepositoryRestConfiguration; @@ -190,8 +191,8 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem */ @ResponseBody @RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET) - public Resources getCollectionResource(RootResourceInformation resourceInformation, DefaultedPageable pageable, - Sort sort, PersistentEntityResourceAssembler assembler) + public Resources getCollectionResource(@QuerydslPredicate RootResourceInformation resourceInformation, + DefaultedPageable pageable, Sort sort, PersistentEntityResourceAssembler assembler) throws ResourceNotFoundException, HttpRequestMethodNotSupportedException { resourceInformation.verifySupportedMethod(HttpMethod.GET, ResourceType.COLLECTION); @@ -202,13 +203,8 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem throw new ResourceNotFoundException(); } - Iterable results; - - if (pageable.getPageable() != null) { - results = invoker.invokeFindAll(pageable.getPageable()); - } else { - results = invoker.invokeFindAll(sort); - } + Iterable results = pageable.getPageable() != null ? invoker.invokeFindAll(pageable.getPageable()) + : invoker.invokeFindAll(sort); ResourceMetadata metadata = resourceInformation.getResourceMetadata(); Link baseLink = entityLinks.linkToPagedResource(resourceInformation.getDomainType(), @@ -226,7 +222,6 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem SearchResourceMappings searchMappings = metadata.getSearchResourceMappings(); List links = new ArrayList(); - links.add(new Link(ProfileController.getPath(this.config, metadata), ProfileResourceProcessor.PROFILE_REL)); if (searchMappings.isExported()) { @@ -241,16 +236,16 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem @SuppressWarnings({ "unchecked" }) @RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET, produces = { "application/x-spring-data-compact+json", "text/uri-list" }) - public Resources getCollectionResourceCompact(RootResourceInformation repoRequest, DefaultedPageable pageable, - Sort sort, PersistentEntityResourceAssembler assembler) + public Resources getCollectionResourceCompact(@QuerydslPredicate RootResourceInformation resourceinformation, + DefaultedPageable pageable, Sort sort, PersistentEntityResourceAssembler assembler) throws ResourceNotFoundException, HttpRequestMethodNotSupportedException { - Resources resources = getCollectionResource(repoRequest, pageable, sort, assembler); + Resources resources = getCollectionResource(resourceinformation, pageable, sort, assembler); List links = new ArrayList(resources.getLinks()); for (Resource resource : ((Resources>) resources).getContent()) { PersistentEntityResource persistentEntityResource = (PersistentEntityResource) resource; - links.add(resourceLink(repoRequest, persistentEntityResource)); + links.add(resourceLink(resourceinformation, persistentEntityResource)); } if (resources instanceof PagedResources) { return new PagedResources(Collections.emptyList(), ((PagedResources) resources).getMetadata(), links); diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositorySearchController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositorySearchController.java index 9c60e3681..66c63fe37 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositorySearchController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositorySearchController.java @@ -37,6 +37,8 @@ import org.springframework.data.rest.core.mapping.ResourceMetadata; 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.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; import org.springframework.data.web.PagedResourcesAssembler; import org.springframework.hateoas.EntityLinks; import org.springframework.hateoas.Link; @@ -294,6 +296,9 @@ class RepositorySearchController extends AbstractRepositoryRestController { MultiValueMap result = new LinkedMultiValueMap(parameters); MethodParameters methodParameters = new MethodParameters(method, new AnnotationAttribute(Param.class)); + List parameterList = methodParameters.getParameters(); + List> parameterTypeInformations = ClassTypeInformation.from(method.getDeclaringClass()) + .getParameterTypes(method); for (Entry> entry : parameters.entrySet()) { @@ -303,7 +308,10 @@ class RepositorySearchController extends AbstractRepositoryRestController { continue; } - ResourceMetadata metadata = mappings.getMetadataFor(parameter.getParameterType()); + int parameterIndex = parameterList.indexOf(parameter); + TypeInformation domainType = parameterTypeInformations.get(parameterIndex).getActualType(); + + ResourceMetadata metadata = mappings.getMetadataFor(domainType.getType()); if (metadata != null && metadata.isExported()) { result.put(parameter.getParameterName(), prepareUris(entry.getValue())); diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver.java index 93193c3a1..762b76669 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver.java @@ -18,10 +18,12 @@ package org.springframework.data.rest.webmvc.config; import java.util.Arrays; import java.util.Map; +import org.springframework.core.MethodParameter; import org.springframework.data.querydsl.QueryDslPredicateExecutor; import org.springframework.data.querydsl.QuerydslRepositoryInvokerAdapter; import org.springframework.data.querydsl.binding.QuerydslBindings; import org.springframework.data.querydsl.binding.QuerydslBindingsFactory; +import org.springframework.data.querydsl.binding.QuerydslPredicate; import org.springframework.data.querydsl.binding.QuerydslPredicateBuilder; import org.springframework.data.repository.support.Repositories; import org.springframework.data.repository.support.RepositoryInvoker; @@ -73,12 +75,13 @@ class QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver */ @Override @SuppressWarnings({ "unchecked" }) - protected RepositoryInvoker postProcess(RepositoryInvoker invoker, Class domainType, - Map parameters) { + protected RepositoryInvoker postProcess(MethodParameter parameter, RepositoryInvoker invoker, + Class domainType, Map parameters) { Object repository = repositories.getRepositoryFor(domainType); - if (!QueryDslPredicateExecutor.class.isInstance(repository)) { + if (!QueryDslPredicateExecutor.class.isInstance(repository) + || !parameter.hasParameterAnnotation(QuerydslPredicate.class)) { return invoker; } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RootResourceInformationHandlerMethodArgumentResolver.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RootResourceInformationHandlerMethodArgumentResolver.java index 2b01d06c4..50afb00a5 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RootResourceInformationHandlerMethodArgumentResolver.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RootResourceInformationHandlerMethodArgumentResolver.java @@ -89,19 +89,20 @@ public class RootResourceInformationHandlerMethodArgumentResolver implements Han // TODO reject if ResourceMetadata cannot be resolved return new RootResourceInformation(resourceMetadata, persistentEntity, - postProcess(repositoryInvoker, domainType, webRequest.getParameterMap())); + postProcess(parameter, repositoryInvoker, domainType, webRequest.getParameterMap())); } /** * Potentially customize the given {@link RepositoryInvoker} for the given domain type. Default implementations simply * returns the given invoker as is. * + * @param parameter must not be {@literal null}. * @param invoker will never be {@literal null}. * @param domainType will never be {@literal null}. * @param parameters will never be {@literal null}. - * @return + * @return the post-processed {@link RepositoryInvoker}. */ - protected RepositoryInvoker postProcess(RepositoryInvoker invoker, Class domainType, + protected RepositoryInvoker postProcess(MethodParameter parameter, RepositoryInvoker invoker, Class domainType, Map parameters) { return invoker; } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/config/QuerydslAwareRootResourceInformationHandlerMethodArgumentResolverUnitTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/config/QuerydslAwareRootResourceInformationHandlerMethodArgumentResolverUnitTests.java index bbfa721f8..71e2b0c4f 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/config/QuerydslAwareRootResourceInformationHandlerMethodArgumentResolverUnitTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/config/QuerydslAwareRootResourceInformationHandlerMethodArgumentResolverUnitTests.java @@ -28,6 +28,7 @@ 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.core.convert.support.DefaultConversionService; import org.springframework.data.querydsl.QueryDslPredicateExecutor; import org.springframework.data.querydsl.QuerydslRepositoryInvokerAdapter; @@ -35,13 +36,15 @@ import org.springframework.data.querydsl.SimpleEntityPathResolver; import org.springframework.data.querydsl.binding.QuerydslBinderCustomizer; import org.springframework.data.querydsl.binding.QuerydslBindings; import org.springframework.data.querydsl.binding.QuerydslBindingsFactory; +import org.springframework.data.querydsl.binding.QuerydslPredicate; import org.springframework.data.querydsl.binding.QuerydslPredicateBuilder; import org.springframework.data.repository.support.Repositories; import org.springframework.data.repository.support.RepositoryInvoker; import org.springframework.data.repository.support.RepositoryInvokerFactory; import org.springframework.data.rest.webmvc.mongodb.QUser; +import org.springframework.data.rest.webmvc.mongodb.Receipt; +import org.springframework.data.rest.webmvc.mongodb.ReceiptRepository; import org.springframework.data.rest.webmvc.mongodb.User; -import org.springframework.data.rest.webmvc.mongodb.UserRepository; import org.springframework.test.util.ReflectionTestUtils; /** @@ -59,6 +62,7 @@ public class QuerydslAwareRootResourceInformationHandlerMethodArgumentResolverUn @Mock ResourceMetadataHandlerMethodArgumentResolver resourceMetadataResolver; @Mock RepositoryInvoker invoker; + @Mock MethodParameter parameter; QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver resolver; @@ -72,6 +76,8 @@ public class QuerydslAwareRootResourceInformationHandlerMethodArgumentResolverUn this.resolver = new QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver(repositories, invokerFactory, resourceMetadataResolver, builder, factory); + + when(parameter.hasParameterAnnotation(QuerydslPredicate.class)).thenReturn(true); } /** @@ -80,10 +86,10 @@ public class QuerydslAwareRootResourceInformationHandlerMethodArgumentResolverUn @Test public void returnsInvokerIfRepositoryIsNotQuerydslAware() { - UserRepository repository = mock(UserRepository.class); - when(repositories.getRepositoryFor(User.class)).thenReturn(repository); + ReceiptRepository repository = mock(ReceiptRepository.class); + when(repositories.getRepositoryFor(Receipt.class)).thenReturn(repository); - RepositoryInvoker result = resolver.postProcess(invoker, User.class, NO_PARAMETERS); + RepositoryInvoker result = resolver.postProcess(parameter, invoker, Receipt.class, NO_PARAMETERS); assertThat(result, is(invoker)); } @@ -97,7 +103,7 @@ public class QuerydslAwareRootResourceInformationHandlerMethodArgumentResolverUn Object repository = mock(QuerydslUserRepository.class); when(repositories.getRepositoryFor(User.class)).thenReturn(repository); - RepositoryInvoker result = resolver.postProcess(invoker, User.class, NO_PARAMETERS); + RepositoryInvoker result = resolver.postProcess(parameter, invoker, User.class, NO_PARAMETERS); assertThat(result, is(instanceOf(QuerydslRepositoryInvokerAdapter.class))); } @@ -112,7 +118,7 @@ public class QuerydslAwareRootResourceInformationHandlerMethodArgumentResolverUn when(repositories.hasRepositoryFor(User.class)).thenReturn(true); when(repositories.getRepositoryFor(User.class)).thenReturn(repository); - RepositoryInvoker result = resolver.postProcess(invoker, User.class, NO_PARAMETERS); + RepositoryInvoker result = resolver.postProcess(parameter, invoker, User.class, NO_PARAMETERS); assertThat(result, is(instanceOf(QuerydslRepositoryInvokerAdapter.class))); verify(repository, times(1)).customize(Mockito.any(QuerydslBindings.class), Mockito.any(QUser.class)); diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/MongoWebTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/MongoWebTests.java index aa8156ac1..45ccb4d81 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/MongoWebTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/MongoWebTests.java @@ -31,6 +31,7 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.rest.webmvc.CommonWebTests; import org.springframework.data.rest.webmvc.RestMediaTypes; +import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks; import org.springframework.hateoas.Link; import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletResponse; @@ -52,6 +53,7 @@ public class MongoWebTests extends CommonWebTests { @Autowired ProfileRepository repository; @Autowired UserRepository userRepository; + @Autowired RepositoryEntityLinks entityLinks; ObjectMapper mapper = new ObjectMapper(); @@ -327,4 +329,20 @@ public class MongoWebTests extends CommonWebTests { mvc.perform(get(link.expand("").getHref())).// andExpect(status().isNotFound()); } + + /** + * @see DATAREST-712 + */ + @Test + public void invokesQueryMethodTakingAReferenceCorrectly() throws Exception { + + Link link = client.discoverUnique("users", "search", "findByColleaguesContains"); + + User thomas = userRepository.findAll(QUser.user.firstname.eq("Thomas")).iterator().next(); + Link thomasUri = entityLinks.linkToSingleResource(User.class, thomas.id).expand(); + + String href = link.expand(thomasUri.getHref()).getHref(); + + mvc.perform(get(href)).andExpect(status().isOk()); + } } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/UserRepository.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/UserRepository.java index 428729cfa..dbabad4c4 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/UserRepository.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/UserRepository.java @@ -18,12 +18,16 @@ package org.springframework.data.rest.webmvc.mongodb; import java.math.BigInteger; import java.util.List; +import org.springframework.data.querydsl.QueryDslPredicateExecutor; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; /** * @author Oliver Gierke */ -public interface UserRepository extends CrudRepository { +public interface UserRepository extends CrudRepository, QueryDslPredicateExecutor { List findByFirstname(String firstname); + + List findByColleaguesContains(@Param("colleagues") User colleague); }