diff --git a/src/main/java/org/springframework/data/repository/util/QueryExecutionConverters.java b/src/main/java/org/springframework/data/repository/util/QueryExecutionConverters.java index 9e326e3ae..4c628e566 100644 --- a/src/main/java/org/springframework/data/repository/util/QueryExecutionConverters.java +++ b/src/main/java/org/springframework/data/repository/util/QueryExecutionConverters.java @@ -18,9 +18,11 @@ package org.springframework.data.repository.util; import java.util.Collections; import java.util.HashSet; import java.util.Set; +import java.util.concurrent.Future; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.support.ConfigurableConversionService; +import org.springframework.scheduling.annotation.AsyncResult; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -48,6 +50,9 @@ public class QueryExecutionConverters { Set> wrapperTypes = new HashSet>(); Set> converters = new HashSet>(); + wrapperTypes.add(Future.class); + converters.add(ObjectToFutureConverter.INSTANCE); + if (GUAVA_PRESENT) { wrapperTypes.add(ObjectToGuavaOptionalConverter.INSTANCE.getWrapperType()); converters.add(ObjectToGuavaOptionalConverter.INSTANCE); @@ -133,4 +138,23 @@ public class QueryExecutionConverters { return java.util.Optional.class; } } + + /** + * A Spring {@link Converter} to support returning {@link Future} instances from repository methods. + * + * @author Oliver Gierke + */ + private static enum ObjectToFutureConverter implements Converter> { + + INSTANCE; + + /* + * (non-Javadoc) + * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) + */ + @Override + public Future convert(NullableWrapper source) { + return new AsyncResult(source.getValue()); + } + } } diff --git a/src/test/java/org/springframework/data/repository/core/support/DummyRepositoryFactory.java b/src/test/java/org/springframework/data/repository/core/support/DummyRepositoryFactory.java index 7eab792c0..d107c62ce 100644 --- a/src/test/java/org/springframework/data/repository/core/support/DummyRepositoryFactory.java +++ b/src/test/java/org/springframework/data/repository/core/support/DummyRepositoryFactory.java @@ -31,6 +31,9 @@ import org.springframework.data.repository.query.RepositoryQuery; public class DummyRepositoryFactory extends RepositoryFactorySupport { + public final MyRepositoryQuery queryOne = mock(MyRepositoryQuery.class); + public final RepositoryQuery queryTwo = mock(RepositoryQuery.class); + private final Object repository; public DummyRepositoryFactory(Object repository) { @@ -44,7 +47,6 @@ public class DummyRepositoryFactory extends RepositoryFactorySupport { @Override @SuppressWarnings("unchecked") public EntityInformation getEntityInformation(Class domainClass) { - return mock(EntityInformation.class); } @@ -73,10 +75,8 @@ public class DummyRepositoryFactory extends RepositoryFactorySupport { @Override protected QueryLookupStrategy getQueryLookupStrategy(Key key) { - MyRepositoryQuery queryOne = mock(MyRepositoryQuery.class); - RepositoryQuery queryTwo = mock(RepositoryQuery.class); - QueryLookupStrategy strategy = mock(QueryLookupStrategy.class); + when( strategy.resolveQuery(Mockito.any(Method.class), Mockito.any(RepositoryMetadata.class), Mockito.any(NamedQueries.class))).thenReturn(queryOne, queryTwo); diff --git a/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java index 364e6f252..c2c4e48ff 100644 --- a/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java +++ b/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java @@ -21,13 +21,19 @@ import static org.mockito.Mockito.*; import java.io.Serializable; import java.util.List; +import java.util.concurrent.Future; +import org.junit.Assume; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.core.SpringVersion; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -36,6 +42,8 @@ import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.Repository; import org.springframework.data.repository.RepositoryDefinition; import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.util.ClassUtils; @@ -47,7 +55,7 @@ import org.springframework.util.ClassUtils; @RunWith(MockitoJUnitRunner.class) public class RepositoryFactorySupportUnitTests { - RepositoryFactorySupport factory; + DummyRepositoryFactory factory; @Mock PagingAndSortingRepository backingRepo; @Mock ObjectRepositoryCustom customImplementation; @@ -121,6 +129,44 @@ public class RepositoryFactorySupportUnitTests { assertThat(ReflectionTestUtils.getField(factory, "classLoader"), is((Object) ClassUtils.getDefaultClassLoader())); } + /** + * @see DATACMNS-489 + */ + @Test + public void wrapsExecutionResultIntoFutureIfConfigured() throws Exception { + + // TODO: Remove once Spring 3.2.9 is released. + Assume.assumeThat(SpringVersion.getVersion().startsWith("4"), is(true)); + + final Object reference = new Object(); + + when(factory.queryOne.execute(Mockito.any(Object[].class))).then(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + Thread.sleep(500); + return reference; + } + }); + + AsyncRepository repository = factory.getRepository(AsyncRepository.class); + + AsyncAnnotationBeanPostProcessor processor = new AsyncAnnotationBeanPostProcessor(); + processor.setBeanFactory(new DefaultListableBeanFactory()); + repository = (AsyncRepository) processor.postProcessAfterInitialization(repository, null); + + Future future = repository.findByFirstname("Foo"); + + assertThat(future.isDone(), is(false)); + + while (!future.isDone()) { + Thread.sleep(300); + } + + assertThat(future.get(), is(reference)); + + verify(factory.queryOne, times(1)).execute(Mockito.any(Object[].class)); + } + interface ObjectRepository extends Repository, ObjectRepositoryCustom { Object findByClass(Class clazz); @@ -170,4 +216,10 @@ public class RepositoryFactorySupportUnitTests { interface AnnotatedRepository { } + + interface AsyncRepository extends Repository { + + @Async + Future findByFirstname(String firstname); + } } diff --git a/template.mf b/template.mf index 3482dc713..ae2a326a0 100644 --- a/template.mf +++ b/template.mf @@ -24,6 +24,7 @@ Import-Template: org.springframework.format.*;version="${spring:[=.=.=,+1.1.0)}";resolution:=optional, org.springframework.hateoas.*;version="${springhateoas:[=.=.=,+1.1.0)}";resolution:=optional, org.springframework.oxm.*;version="${spring:[=.=.=,+1.1.0)}";resolution:=optional, + org.springframework.scheduling.*;version="${spring:[=.=.=,+1.1.0)}";resolution:=optional, org.springframework.transaction.*;version="${spring:[=.=.=,+1.1.0)}";resolution:=optional, org.springframework.util.*;version="${spring:[=.=.=,+1.1.0)}", org.springframework.validation.*;version="${spring:[=.=.=,+1.1.0)}";resolution:=optional,