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); }