DATAREST-616 - Initial support to execute Querydsl predicates.
If Querydsl is on the classpath and the repository exposed implements QuerydslPredicateExecutor, we now register a component that customizes the RepositoryInvoker to route repository interactions for collection resource calls to the QuerydslPredicateExecutor. The invocation gets enriched with a Predicate obtained from the request data. Bindings can be customized through the QuerydslBinderCustomizer interface implemented in a default method on the repository. Tweaked the build to generate Querydsl meta-model classes for the MongoDB domain types.
This commit is contained in:
@@ -88,6 +88,15 @@
|
||||
<artifactId>json-patch</artifactId>
|
||||
<version>1.7</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Querydsl -->
|
||||
|
||||
<dependency>
|
||||
<groupId>com.mysema.querydsl</groupId>
|
||||
<artifactId>querydsl-core</artifactId>
|
||||
<version>${querydsl}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Optional store specifics -->
|
||||
|
||||
@@ -450,6 +459,33 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>com.mysema.maven</groupId>
|
||||
<artifactId>apt-maven-plugin</artifactId>
|
||||
<version>${apt}</version>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.mysema.querydsl</groupId>
|
||||
<artifactId>querydsl-apt</artifactId>
|
||||
<version>${querydsl}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>generate-test-sources</phase>
|
||||
<goals>
|
||||
<goal>test-process</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>target/generated-sources/annotations</outputDirectory>
|
||||
<processor>org.springframework.data.mongodb.repository.support.MongoAnnotationProcessor</processor>
|
||||
<logOnlyOnError>true</logOnlyOnError>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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.Map;
|
||||
|
||||
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.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.RootResourceInformation;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
|
||||
import com.mysema.query.types.Predicate;
|
||||
|
||||
/**
|
||||
* {@link HandlerMethodArgumentResolver} to create {@link RootResourceInformation} for injection into Spring MVC
|
||||
* controller methods.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 2.4
|
||||
*/
|
||||
class QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver
|
||||
extends RootResourceInformationHandlerMethodArgumentResolver {
|
||||
|
||||
private final Repositories repositories;
|
||||
private final QuerydslPredicateBuilder predicateBuilder;
|
||||
private final QuerydslBindingsFactory factory;
|
||||
|
||||
/**
|
||||
* Creates a new {@link QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver} using the given
|
||||
* {@link Repositories}, {@link RepositoryInvokerFactory} and {@link ResourceMetadataHandlerMethodArgumentResolver}.
|
||||
*
|
||||
* @param repositories must not be {@literal null}.
|
||||
* @param invokerFactory must not be {@literal null}.
|
||||
* @param resourceMetadataResolver must not be {@literal null}.
|
||||
*/
|
||||
public QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver(Repositories repositories,
|
||||
RepositoryInvokerFactory invokerFactory, ResourceMetadataHandlerMethodArgumentResolver resourceMetadataResolver,
|
||||
QuerydslPredicateBuilder predicateBuilder, QuerydslBindingsFactory factory) {
|
||||
|
||||
super(repositories, invokerFactory, resourceMetadataResolver);
|
||||
|
||||
this.repositories = repositories;
|
||||
this.predicateBuilder = predicateBuilder;
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.webmvc.config.RootResourceInformationHandlerMethodArgumentResolver#postProcess(org.springframework.data.repository.support.RepositoryInvoker, java.lang.Class, java.util.Map)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
protected RepositoryInvoker postProcess(RepositoryInvoker invoker, Class<?> domainType,
|
||||
Map<String, String[]> parameters) {
|
||||
|
||||
Object repository = repositories.getRepositoryFor(domainType);
|
||||
|
||||
if (!QueryDslPredicateExecutor.class.isInstance(repository)) {
|
||||
return invoker;
|
||||
}
|
||||
|
||||
ClassTypeInformation<?> type = ClassTypeInformation.from(domainType);
|
||||
|
||||
QuerydslBindings bindings = factory.createBindingsFor(null, type);
|
||||
Predicate predicate = predicateBuilder.getPredicate(type, toMultiValueMap(parameters), bindings);
|
||||
|
||||
return new QuerydslRepositoryInvokerAdapter(invoker, (QueryDslPredicateExecutor<Object>) repository, predicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given Map into a {@link MultiValueMap}.
|
||||
*
|
||||
* @param source must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private static MultiValueMap<String, String> toMultiValueMap(Map<String, String[]> source) {
|
||||
|
||||
MultiValueMap<String, String> result = new LinkedMultiValueMap<String, String>();
|
||||
|
||||
for (String key : source.keySet()) {
|
||||
result.put(key, Arrays.asList(source.get(key)));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,9 @@ import org.springframework.data.geo.GeoModule;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.context.PersistentEntities;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.querydsl.QueryDslUtils;
|
||||
import org.springframework.data.querydsl.binding.QuerydslBindingsFactory;
|
||||
import org.springframework.data.querydsl.binding.QuerydslPredicateBuilder;
|
||||
import org.springframework.data.repository.support.DefaultRepositoryInvokerFactory;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.repository.support.RepositoryInvokerFactory;
|
||||
@@ -97,6 +100,7 @@ import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks;
|
||||
import org.springframework.data.util.AnnotatedTypeScanner;
|
||||
import org.springframework.data.web.HateoasPageableHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.web.HateoasSortHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.web.config.EnableSpringDataWebSupport;
|
||||
import org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration;
|
||||
import org.springframework.data.web.config.SpringDataJacksonConfiguration;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
@@ -145,7 +149,7 @@ import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
@ComponentScan(basePackageClasses = RepositoryRestController.class,
|
||||
includeFilters = @Filter(BasePathAwareController.class) , useDefaultFilters = false)
|
||||
@ImportResource("classpath*:META-INF/spring-data-rest/**/*.xml")
|
||||
@Import(SpringDataJacksonConfiguration.class)
|
||||
@Import({ SpringDataJacksonConfiguration.class, EnableSpringDataWebSupport.QuerydslActivator.class })
|
||||
public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebConfiguration implements InitializingBean {
|
||||
|
||||
private static final boolean IS_JPA_AVAILABLE = ClassUtils.isPresent("javax.persistence.EntityManager",
|
||||
@@ -301,6 +305,17 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
*/
|
||||
@Bean
|
||||
public RootResourceInformationHandlerMethodArgumentResolver repoRequestArgumentResolver() {
|
||||
|
||||
if (QueryDslUtils.QUERY_DSL_PRESENT) {
|
||||
|
||||
QuerydslBindingsFactory factory = applicationContext.getBean(QuerydslBindingsFactory.class);
|
||||
QuerydslPredicateBuilder predicateBuilder = new QuerydslPredicateBuilder(defaultConversionService(),
|
||||
factory.getEntityPathResolver());
|
||||
|
||||
return new QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver(repositories(),
|
||||
repositoryInvokerFactory(), resourceMetadataHandlerMethodArgumentResolver(), predicateBuilder, factory);
|
||||
}
|
||||
|
||||
return new RootResourceInformationHandlerMethodArgumentResolver(repositories(), repositoryInvokerFactory(),
|
||||
resourceMetadataHandlerMethodArgumentResolver());
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2013 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.
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.rest.webmvc.config;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
@@ -86,6 +88,21 @@ public class RootResourceInformationHandlerMethodArgumentResolver implements Han
|
||||
PersistentEntity<?, ?> persistentEntity = repositories.getPersistentEntity(domainType);
|
||||
|
||||
// TODO reject if ResourceMetadata cannot be resolved
|
||||
return new RootResourceInformation(resourceMetadata, persistentEntity, repositoryInvoker);
|
||||
return new RootResourceInformation(resourceMetadata, persistentEntity,
|
||||
postProcess(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 invoker will never be {@literal null}.
|
||||
* @param domainType will never be {@literal null}.
|
||||
* @param parameters will never be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
protected RepositoryInvoker postProcess(RepositoryInvoker invoker, Class<?> domainType,
|
||||
Map<String, String[]> parameters) {
|
||||
return invoker;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
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.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
|
||||
import org.springframework.data.querydsl.QuerydslRepositoryInvokerAdapter;
|
||||
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.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.User;
|
||||
import org.springframework.data.rest.webmvc.mongodb.UserRepository;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class QuerydslAwareRootResourceInformationHandlerMethodArgumentResolverUnitTests {
|
||||
|
||||
static final Map<String, String[]> NO_PARAMETERS = Collections.emptyMap();
|
||||
|
||||
@Mock Repositories repositories;
|
||||
@Mock RepositoryInvokerFactory invokerFactory;
|
||||
@Mock ResourceMetadataHandlerMethodArgumentResolver resourceMetadataResolver;
|
||||
|
||||
@Mock RepositoryInvoker invoker;
|
||||
|
||||
QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver resolver;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
QuerydslBindingsFactory factory = new QuerydslBindingsFactory(SimpleEntityPathResolver.INSTANCE);
|
||||
ReflectionTestUtils.setField(factory, "repositories", repositories);
|
||||
QuerydslPredicateBuilder builder = new QuerydslPredicateBuilder(new DefaultConversionService(),
|
||||
factory.getEntityPathResolver());
|
||||
|
||||
this.resolver = new QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver(repositories, invokerFactory,
|
||||
resourceMetadataResolver, builder, factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-616
|
||||
*/
|
||||
@Test
|
||||
public void returnsInvokerIfRepositoryIsNotQuerydslAware() {
|
||||
|
||||
UserRepository repository = mock(UserRepository.class);
|
||||
when(repositories.getRepositoryFor(User.class)).thenReturn(repository);
|
||||
|
||||
RepositoryInvoker result = resolver.postProcess(invoker, User.class, NO_PARAMETERS);
|
||||
|
||||
assertThat(result, is(invoker));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-616
|
||||
*/
|
||||
@Test
|
||||
public void wrapsInvokerInQuerydslAdapter() {
|
||||
|
||||
Object repository = mock(QuerydslUserRepository.class);
|
||||
when(repositories.getRepositoryFor(User.class)).thenReturn(repository);
|
||||
|
||||
RepositoryInvoker result = resolver.postProcess(invoker, User.class, NO_PARAMETERS);
|
||||
|
||||
assertThat(result, is(instanceOf(QuerydslRepositoryInvokerAdapter.class)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-616
|
||||
*/
|
||||
@Test
|
||||
public void invokesCustomizationOnRepositoryIfItImplementsCustomizer() {
|
||||
|
||||
QuerydslCustomizingUserRepository repository = mock(QuerydslCustomizingUserRepository.class);
|
||||
when(repositories.hasRepositoryFor(User.class)).thenReturn(true);
|
||||
when(repositories.getRepositoryFor(User.class)).thenReturn(repository);
|
||||
|
||||
RepositoryInvoker result = resolver.postProcess(invoker, User.class, NO_PARAMETERS);
|
||||
|
||||
assertThat(result, is(instanceOf(QuerydslRepositoryInvokerAdapter.class)));
|
||||
verify(repository, times(1)).customize(Mockito.any(QuerydslBindings.class), Mockito.any(QUser.class));
|
||||
}
|
||||
|
||||
interface QuerydslUserRepository extends QueryDslPredicateExecutor<User> {}
|
||||
|
||||
interface QuerydslCustomizingUserRepository
|
||||
extends QueryDslPredicateExecutor<User>, QuerydslBinderCustomizer<QUser> {}
|
||||
}
|
||||
Reference in New Issue
Block a user