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.
This commit is contained in:
7
pom.xml
7
pom.xml
@@ -114,6 +114,13 @@
|
||||
<version>${springdata.mongodb}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.querydsl</groupId>
|
||||
<artifactId>querydsl-mongodb</artifactId>
|
||||
<version>${querydsl}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
|
||||
|
||||
@@ -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<Link> links = new ArrayList<Link>();
|
||||
|
||||
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<Link> links = new ArrayList<Link>(resources.getLinks());
|
||||
|
||||
for (Resource<?> resource : ((Resources<Resource<?>>) resources).getContent()) {
|
||||
PersistentEntityResource persistentEntityResource = (PersistentEntityResource) resource;
|
||||
links.add(resourceLink(repoRequest, persistentEntityResource));
|
||||
links.add(resourceLink(resourceinformation, persistentEntityResource));
|
||||
}
|
||||
if (resources instanceof PagedResources) {
|
||||
return new PagedResources<Object>(Collections.emptyList(), ((PagedResources<?>) resources).getMetadata(), links);
|
||||
|
||||
@@ -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<String, Object> result = new LinkedMultiValueMap<String, Object>(parameters);
|
||||
MethodParameters methodParameters = new MethodParameters(method, new AnnotationAttribute(Param.class));
|
||||
List<MethodParameter> parameterList = methodParameters.getParameters();
|
||||
List<TypeInformation<?>> parameterTypeInformations = ClassTypeInformation.from(method.getDeclaringClass())
|
||||
.getParameterTypes(method);
|
||||
|
||||
for (Entry<String, List<Object>> 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()));
|
||||
|
||||
@@ -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<String, String[]> parameters) {
|
||||
protected RepositoryInvoker postProcess(MethodParameter parameter, RepositoryInvoker invoker,
|
||||
Class<?> domainType, Map<String, String[]> parameters) {
|
||||
|
||||
Object repository = repositories.getRepositoryFor(domainType);
|
||||
|
||||
if (!QueryDslPredicateExecutor.class.isInstance(repository)) {
|
||||
if (!QueryDslPredicateExecutor.class.isInstance(repository)
|
||||
|| !parameter.hasParameterAnnotation(QuerydslPredicate.class)) {
|
||||
return invoker;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<String, String[]> parameters) {
|
||||
return invoker;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<User, BigInteger> {
|
||||
public interface UserRepository extends CrudRepository<User, BigInteger>, QueryDslPredicateExecutor<User> {
|
||||
|
||||
List<User> findByFirstname(String firstname);
|
||||
|
||||
List<User> findByColleaguesContains(@Param("colleagues") User colleague);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user