DATACMNS-1430 - Support for returning wrapper types of Streamable.
Extended IterableToStreamableConverter in use in QueryExecutionConverters so that it can not only return Streamable instances but also instances of types that have a factory method or constructor taking a Streamable (by making use of the ObjectToObjectConverter in a default ConversionService).
This commit is contained in:
@@ -36,6 +36,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@@ -44,9 +45,11 @@ import javax.annotation.Nonnull;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.ConditionalGenericConverter;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.converter.GenericConverter;
|
||||
import org.springframework.core.convert.support.ConfigurableConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.data.geo.GeoResults;
|
||||
@@ -273,7 +276,7 @@ public abstract class QueryExecutionConverters {
|
||||
}
|
||||
|
||||
conversionService.addConverter(new NullableWrapperToFutureConverter(conversionService));
|
||||
conversionService.addConverter(IterableToStreamableConverter.INSTANCE);
|
||||
conversionService.addConverter(new IterableToStreamableConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -757,18 +760,60 @@ public abstract class QueryExecutionConverters {
|
||||
}
|
||||
}
|
||||
|
||||
private enum IterableToStreamableConverter implements Converter<Iterable<?>, Streamable<?>> {
|
||||
@RequiredArgsConstructor
|
||||
private static class IterableToStreamableConverter implements ConditionalGenericConverter {
|
||||
|
||||
INSTANCE;
|
||||
private static final TypeDescriptor STREAMABLE = TypeDescriptor.valueOf(Streamable.class);
|
||||
|
||||
/*
|
||||
private final Map<TypeDescriptor, Boolean> TARGET_TYPE_CACHE = new ConcurrentHashMap<>();
|
||||
private final ConversionService conversionService = DefaultConversionService.getSharedInstance();
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
|
||||
* @see org.springframework.core.convert.converter.GenericConverter#getConvertibleTypes()
|
||||
*/
|
||||
@Nonnull
|
||||
@org.springframework.lang.NonNull
|
||||
@Override
|
||||
public Streamable<?> convert(Iterable<?> source) {
|
||||
return Streamable.of(source);
|
||||
public Set<ConvertiblePair> getConvertibleTypes() {
|
||||
return Collections.singleton(new ConvertiblePair(Iterable.class, Object.class));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.converter.ConditionalConverter#matches(org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
|
||||
*/
|
||||
@Override
|
||||
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
|
||||
if (!Iterable.class.isAssignableFrom(sourceType.getType())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Streamable.class.equals(targetType.getType())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return TARGET_TYPE_CACHE.computeIfAbsent(targetType, it -> {
|
||||
return conversionService.canConvert(STREAMABLE, targetType);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.converter.GenericConverter#convert(java.lang.Object, org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
@Override
|
||||
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
|
||||
Streamable<Object> streamable = source == null //
|
||||
? Streamable.empty() //
|
||||
: Streamable.of(Iterable.class.cast(source));
|
||||
|
||||
return Streamable.class.equals(targetType.getType()) //
|
||||
? streamable //
|
||||
: conversionService.convert(streamable, STREAMABLE, targetType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import io.vavr.control.Try;
|
||||
import javaslang.control.Option;
|
||||
import lombok.Value;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import rx.Completable;
|
||||
@@ -28,6 +29,7 @@ import rx.Single;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -372,6 +374,16 @@ public class QueryExecutionResultHandlerUnitTests {
|
||||
assertThat(result).isInstanceOfSatisfying(Option.class, it -> assertThat(it.get()).isEqualTo(entity));
|
||||
}
|
||||
|
||||
@Test // DATACMNS-1430
|
||||
public void convertsElementsAndValueIntoCustomStreamable() throws Exception {
|
||||
|
||||
Object result = handler.postProcessInvocationResult(Arrays.asList("foo"), getMethod("customStreamable"));
|
||||
|
||||
assertThat(result).isInstanceOfSatisfying(CustomStreamableWrapper.class, it -> {
|
||||
assertThat(it).containsExactly("foo");
|
||||
});
|
||||
}
|
||||
|
||||
private static Method getMethod(String methodName) throws Exception {
|
||||
return Sample.class.getMethod(methodName);
|
||||
}
|
||||
@@ -406,7 +418,23 @@ public class QueryExecutionResultHandlerUnitTests {
|
||||
|
||||
// DATACMNS-938
|
||||
Try<Option<Entity>> tryOfOption();
|
||||
|
||||
// DATACMNS-1430
|
||||
CustomStreamableWrapper<String> customStreamable();
|
||||
}
|
||||
|
||||
static class Entity {}
|
||||
|
||||
// DATACMNS-1430
|
||||
|
||||
@Value
|
||||
static class CustomStreamableWrapper<T> implements Streamable<T> {
|
||||
|
||||
Streamable<T> source;
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return source.iterator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import javaslang.collection.Seq;
|
||||
import javaslang.collection.Traversable;
|
||||
import javaslang.control.Try;
|
||||
import javaslang.control.Try.Failure;
|
||||
import lombok.Value;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import rx.Completable;
|
||||
@@ -35,6 +36,7 @@ import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -48,6 +50,7 @@ import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
|
||||
@@ -412,6 +415,29 @@ public class QueryExecutionConvertersUnitTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test // DATACMNS-1430
|
||||
public void returnsStreamableForIterable() throws Exception {
|
||||
|
||||
assertThat(conversionService.canConvert(Iterable.class, Streamable.class)).isTrue();
|
||||
assertThat(conversionService.convert(Arrays.asList("foo"), Streamable.class)).containsExactly("foo");
|
||||
}
|
||||
|
||||
@Test // DATACMNS-1430
|
||||
public void convertsToStreamableWrapper() throws Exception {
|
||||
|
||||
assertThat(conversionService.canConvert(Iterable.class, StreamableWrapper.class)).isTrue();
|
||||
assertThat(conversionService.convert(Arrays.asList("foo"), StreamableWrapper.class).getStreamable()) //
|
||||
.containsExactly("foo");
|
||||
}
|
||||
|
||||
@Test // DATACMNS-1430
|
||||
public void convertsToStreamableWrapperImplementingStreamable() throws Exception {
|
||||
|
||||
assertThat(conversionService.canConvert(Iterable.class, CustomStreamableWrapper.class)).isTrue();
|
||||
assertThat(conversionService.convert(Arrays.asList("foo"), CustomStreamableWrapper.class)) //
|
||||
.containsExactly("foo");
|
||||
}
|
||||
|
||||
interface Sample {
|
||||
|
||||
Page<String> pages();
|
||||
@@ -424,4 +450,22 @@ public class QueryExecutionConvertersUnitTests {
|
||||
|
||||
io.vavr.control.Try<io.vavr.collection.Seq<Sample>> vavrTryForSeqMethod();
|
||||
}
|
||||
|
||||
// DATACMNS-1430
|
||||
|
||||
@Value(staticConstructor = "of")
|
||||
static class StreamableWrapper {
|
||||
Streamable<String> streamable;
|
||||
}
|
||||
|
||||
@Value
|
||||
static class CustomStreamableWrapper<T> implements Streamable<T> {
|
||||
|
||||
Streamable<T> source;
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return source.iterator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user