DATACMNS-714 - Added support for CompletableFuture and ListenableFuture for query methods.

Spring Framework 4.2 is going to introduce support for CompletableFuture as return type for @Async methods. Support for Spring's ListenableFuture has already been supported in the framework 4.1. We've added the necessary infrastructure to  allow these types as return types on repository methods, too.

Related tickets: SPR-13128.
This commit is contained in:
Oliver Gierke
2015-06-15 12:57:57 +02:00
parent a8b64c3828
commit b294bfce3b
2 changed files with 99 additions and 18 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2014-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.
@@ -18,6 +18,7 @@ package org.springframework.data.repository.util;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import org.springframework.core.convert.ConversionService;
@@ -28,6 +29,7 @@ import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.concurrent.ListenableFuture;
import com.google.common.base.Optional;
@@ -42,7 +44,7 @@ public abstract class QueryExecutionConverters {
private static final boolean GUAVA_PRESENT = ClassUtils.isPresent("com.google.common.base.Optional",
QueryExecutionConverters.class.getClassLoader());
private static final boolean JDK_PRESENT = ClassUtils.isPresent("java.util.Optional",
private static final boolean JDK_8_PRESENT = ClassUtils.isPresent("java.util.Optional",
QueryExecutionConverters.class.getClassLoader());
private static final Set<Class<?>> WRAPPER_TYPES = new HashSet<Class<?>>();
@@ -50,13 +52,15 @@ public abstract class QueryExecutionConverters {
static {
WRAPPER_TYPES.add(Future.class);
WRAPPER_TYPES.add(ListenableFuture.class);
if (GUAVA_PRESENT) {
WRAPPER_TYPES.add(NullableWrapperToGuavaOptionalConverter.getWrapperType());
}
if (JDK_PRESENT) {
if (JDK_8_PRESENT) {
WRAPPER_TYPES.add(NullableWrapperToJdk8OptionalConverter.getWrapperType());
WRAPPER_TYPES.add(NullableWrapperToCompletableFutureConverter.getWrapperType());
}
}
@@ -87,8 +91,9 @@ public abstract class QueryExecutionConverters {
conversionService.addConverter(new NullableWrapperToGuavaOptionalConverter(conversionService));
}
if (JDK_PRESENT) {
if (JDK_8_PRESENT) {
conversionService.addConverter(new NullableWrapperToJdk8OptionalConverter(conversionService));
conversionService.addConverter(new NullableWrapperToCompletableFutureConverter(conversionService));
}
conversionService.addConverter(new NullableWrapperToFutureConverter(conversionService));
@@ -102,23 +107,23 @@ public abstract class QueryExecutionConverters {
*/
private static abstract class AbstractWrapperTypeConverter implements GenericConverter {
@SuppressWarnings("unused")//
@SuppressWarnings("unused") //
private final ConversionService conversionService;
private final Class<?> wrapperType;
private final Class<?>[] wrapperTypes;
/**
* Creates a new {@link AbstractWrapperTypeConverter} using the given {@link ConversionService} and wrapper type.
*
* @param conversionService must not be {@literal null}.
* @param wrapperType must not be {@literal null}.
* @param wrapperTypes must not be {@literal null}.
*/
protected AbstractWrapperTypeConverter(ConversionService conversionService, Class<?> wrapperType) {
protected AbstractWrapperTypeConverter(ConversionService conversionService, Class<?>... wrapperTypes) {
Assert.notNull(conversionService, "ConversionService must not be null!");
Assert.notNull(wrapperType, "Wrapper type must not be null!");
Assert.notEmpty(wrapperTypes, "Wrapper type must not be empty!");
this.conversionService = conversionService;
this.wrapperType = wrapperType;
this.wrapperTypes = wrapperTypes;
}
/*
@@ -127,7 +132,14 @@ public abstract class QueryExecutionConverters {
*/
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(NullableWrapper.class, wrapperType));
Set<ConvertiblePair> pairs = new HashSet<ConvertiblePair>(wrapperTypes.length);
for (Class<?> wrapperType : wrapperTypes) {
pairs.add(new ConvertiblePair(NullableWrapper.class, wrapperType));
}
return Collections.unmodifiableSet(pairs);
}
/*
@@ -253,7 +265,7 @@ public abstract class QueryExecutionConverters {
* @param conversionService must not be {@literal null}.
*/
public NullableWrapperToFutureConverter(ConversionService conversionService) {
super(conversionService, Future.class);
super(conversionService, Future.class, ListenableFuture.class);
}
/*
@@ -274,4 +286,45 @@ public abstract class QueryExecutionConverters {
return new AsyncResult<Object>(source);
}
}
/**
* A Spring {@link Converter} to support returning {@link CompletableFuture} instances from repository methods.
*
* @author Oliver Gierke
*/
private static class NullableWrapperToCompletableFutureConverter extends AbstractWrapperTypeConverter {
private static final CompletableFuture<Object> NULL_OBJECT = CompletableFuture.completedFuture(null);
/**
* Creates a new {@link NullableWrapperToCompletableFutureConverter} using the given {@link ConversionService}.
*
* @param conversionService must not be {@literal null}.
*/
public NullableWrapperToCompletableFutureConverter(ConversionService conversionService) {
super(conversionService, CompletableFuture.class);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.util.QueryExecutionConverters.AbstractWrapperTypeConverter#getNullValue()
*/
@Override
protected Object getNullValue() {
return NULL_OBJECT;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.util.QueryExecutionConverters.AbstractWrapperTypeConverter#wrap(java.lang.Object)
*/
@Override
protected Object wrap(Object source) {
return source instanceof CompletableFuture ? source : CompletableFuture.completedFuture(source);
}
public static Class<?> getWrapperType() {
return CompletableFuture.class;
}
}
}