DATACMNS-1005 - Paginated query methods now support Javaslang collection types.

To eagerly catch invalid declarations, query methods using pagination had a check applied that the method either returns a Page or List. We've introduced support for alternative collection libraries (Javaslang in particular) and thus the check needs to take these newly supported types into account.

This is now fixed by externalizing the valid types into QueryExecutionConverters and using the ones returned by that to check against.

Original pull request: #200.
This commit is contained in:
Maciek Opala
2017-03-07 20:08:29 +01:00
committed by Oliver Gierke
parent 6ef41f6a76
commit 0d9c909e69
4 changed files with 63 additions and 3 deletions

View File

@@ -18,7 +18,8 @@ package org.springframework.data.repository.query;
import static org.springframework.data.repository.util.ClassUtils.*;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Arrays;
import java.util.Set;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
@@ -30,6 +31,7 @@ import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.util.QueryExecutionConverters;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.ReflectionUtils;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
/**
@@ -78,7 +80,8 @@ public class QueryMethod {
if (hasParameterOfType(method, Pageable.class)) {
if (!isStreamQuery()) {
assertReturnTypeAssignable(method, Slice.class, Page.class, List.class);
final Set<Class<?>> allowedPageableTypes = QueryExecutionConverters.getAllowedPageableTypes();
assertReturnTypeAssignable(method, allowedPageableTypes.toArray(new Class<?>[allowedPageableTypes.size()]));
}
if (hasParameterOfType(method, Sort.class)) {
@@ -275,4 +278,21 @@ public class QueryMethod {
return method.getReturnType();
}
}
private static void assertReturnTypeAssignable(Method method, Class<?>... types) {
Assert.notNull(method, "Method must not be null!");
Assert.notEmpty(types, "Types must not be null or empty!");
TypeInformation<?> returnType = ClassTypeInformation.fromReturnTypeOf(method);
returnType = QueryExecutionConverters.isSingleValue(returnType.getType()) ? returnType.getComponentType() : returnType;
for (Class<?> type : types) {
if (type.isAssignableFrom(returnType.getType())) {
return;
}
}
throw new IllegalStateException("Method has to have one of the following return types! " + Arrays.toString(types));
}
}

View File

@@ -15,10 +15,13 @@
*/
package org.springframework.data.repository.util;
import javaslang.collection.Seq;
import javaslang.collection.Traversable;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Slice;
import scala.Function0;
import scala.Option;
import scala.runtime.AbstractFunction0;
@@ -27,6 +30,7 @@ import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
@@ -75,12 +79,17 @@ public abstract class QueryExecutionConverters {
private static final Set<WrapperType> WRAPPER_TYPES = new HashSet<WrapperType>();
private static final Set<Converter<Object, Object>> UNWRAPPERS = new HashSet<Converter<Object, Object>>();
private static final Set<Class<?>> ALLOWED_PAGEABLE_TYPES = new HashSet<Class<?>>();
static {
WRAPPER_TYPES.add(WrapperType.singleValue(Future.class));
WRAPPER_TYPES.add(WrapperType.singleValue(ListenableFuture.class));
ALLOWED_PAGEABLE_TYPES.add(Slice.class);
ALLOWED_PAGEABLE_TYPES.add(Page.class);
ALLOWED_PAGEABLE_TYPES.add(List.class);
if (GUAVA_PRESENT) {
WRAPPER_TYPES.add(NullableWrapperToGuavaOptionalConverter.getWrapperType());
UNWRAPPERS.add(GuavaOptionalUnwrapper.INSTANCE);
@@ -106,6 +115,8 @@ public abstract class QueryExecutionConverters {
WRAPPER_TYPES.add(JavaslangCollections.ToJavaConverter.INSTANCE.getWrapperType());
UNWRAPPERS.add(JavaslangOptionUnwrapper.INSTANCE);
ALLOWED_PAGEABLE_TYPES.add(Seq.class);
}
}
@@ -141,6 +152,10 @@ public abstract class QueryExecutionConverters {
return false;
}
public static Set<Class<?>> getAllowedPageableTypes() {
return Collections.unmodifiableSet(ALLOWED_PAGEABLE_TYPES);
}
/**
* Registers converters for wrapper types found on the classpath.
*

View File

@@ -211,6 +211,15 @@ public class QueryMethodUnitTests {
assertThat(new QueryMethod(method, repositoryMetadata, factory).isCollectionQuery(), is(false));
}
@Test // DATACMNS-1005
public void doesNotRejectSeqForPagination() throws Exception {
RepositoryMetadata repositoryMetadata = new DefaultRepositoryMetadata(SampleRepository.class);
Method method = SampleRepository.class.getMethod("returnsSeq", Pageable.class);
assertThat(new QueryMethod(method, repositoryMetadata, factory).isCollectionQuery(), is(true));
}
interface SampleRepository extends Repository<User, Serializable> {
String pagingMethodWithInvalidReturnType(Pageable pageable);
@@ -249,6 +258,9 @@ public class QueryMethodUnitTests {
Seq<User> returnsSeq();
// DATACMNS-1005
Seq<User> returnsSeq(Pageable pageable);
Future<Seq<User>> returnsFutureOfSeq();
Future<Option<User>> returnsFutureOfOption();

View File

@@ -21,7 +21,10 @@ import static org.springframework.data.repository.util.QueryExecutionConverters.
import javaslang.collection.HashMap;
import javaslang.collection.HashSet;
import javaslang.collection.Seq;
import javaslang.collection.Traversable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Slice;
import scala.Option;
import java.lang.reflect.Method;
@@ -219,6 +222,16 @@ public class QueryExecutionConvertersUnitTests {
assertThat(unwrap(javaslangMap("key", "value")), is(instanceOf(Map.class)));
}
@Test // DATACMNS-1005
public void registersAllowedPageabletypes() {
final Set<Class<?>> allowedPageableTypes = QueryExecutionConverters.getAllowedPageableTypes();
assertThat(allowedPageableTypes, hasItem(Page.class));
assertThat(allowedPageableTypes, hasItem(Slice.class));
assertThat(allowedPageableTypes, hasItem(List.class));
assertThat(allowedPageableTypes, hasItem(Seq.class));
}
@SuppressWarnings("unchecked")
private static javaslang.control.Option<Object> optionNone() {