DATACMNS-509 - Fixed non-NullableWrapper conversion.

In case the repository method execution returns a collection that needs to be converted into a different collection (e.g. List -> Set) we erroneously ran into a NullableWrapper conversion. This was due to the fact that we only used the raw method return type, which caused the ObjectToCollectionConverter to kick in (as the target type's component type falls back to Object if we use raw types).

We're now using Spring's MethodParameter abstraction to create a TypeDescriptor for the method's return type which fully supports generics, so that the conversion check for NullableWrapper -> target type does not match the ObjectToCollectionWrapper.

Prepared the QueryExecutionConverters to be able to support recursive type conversion once we move to Spring 4 as TypeDescriptor exposes the ResolvedType as of this version so that we can inspect the type parameter of the wrapper type to invoke a nested conversion (e.g. Integer -> Optional<String> requires NullableWrapperTo(Guava|Jdk)OptionalConverter being invoked and in turn recursively convert the Integer to String).
This commit is contained in:
Oliver Gierke
2014-05-20 08:46:08 +02:00
parent 2d024bc38e
commit 5541989058
4 changed files with 226 additions and 48 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012 the original author or authors.
* Copyright 2012-2014 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.
@@ -29,15 +29,27 @@ import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
import org.springframework.data.repository.query.RepositoryQuery;
/**
* Dummy implementation for {@link RepositoryFactorySupport} that is equipped with mocks to simulate behavior for test
* cases.
*
* @author Oliver Gierke
*/
public class DummyRepositoryFactory extends RepositoryFactorySupport {
public final MyRepositoryQuery queryOne = mock(MyRepositoryQuery.class);
public final RepositoryQuery queryTwo = mock(RepositoryQuery.class);
public final QueryLookupStrategy strategy = mock(QueryLookupStrategy.class);
private final Object repository;
public DummyRepositoryFactory(Object repository) {
this.repository = repository;
when(
strategy.resolveQuery(Mockito.any(Method.class), Mockito.any(RepositoryMetadata.class),
Mockito.any(NamedQueries.class))).thenReturn(queryOne);
}
/*
@@ -74,13 +86,6 @@ public class DummyRepositoryFactory extends RepositoryFactorySupport {
*/
@Override
protected QueryLookupStrategy getQueryLookupStrategy(Key key) {
QueryLookupStrategy strategy = mock(QueryLookupStrategy.class);
when(
strategy.resolveQuery(Mockito.any(Method.class), Mockito.any(RepositoryMetadata.class),
Mockito.any(NamedQueries.class))).thenReturn(queryOne, queryTwo);
return strategy;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011 the original author or authors.
* Copyright 2011-2014 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,12 +15,15 @@
*/
package org.springframework.data.repository.core.support;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Future;
import org.junit.Assume;
@@ -41,6 +44,8 @@ import org.springframework.data.domain.Sort;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.RepositoryDefinition;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor;
@@ -71,6 +76,12 @@ public class RepositoryFactorySupportUnitTests {
@Test
public void invokesCustomQueryCreationListenerForSpecialRepositoryQueryOnly() throws Exception {
Mockito.reset(factory.strategy);
when(
factory.strategy.resolveQuery(Mockito.any(Method.class), Mockito.any(RepositoryMetadata.class),
Mockito.any(NamedQueries.class))).thenReturn(factory.queryOne, factory.queryTwo);
factory.addQueryCreationListener(listener);
factory.addQueryCreationListener(otherListener);
@@ -148,11 +159,11 @@ public class RepositoryFactorySupportUnitTests {
}
});
AsyncRepository repository = factory.getRepository(AsyncRepository.class);
ConvertingRepository repository = factory.getRepository(ConvertingRepository.class);
AsyncAnnotationBeanPostProcessor processor = new AsyncAnnotationBeanPostProcessor();
processor.setBeanFactory(new DefaultListableBeanFactory());
repository = (AsyncRepository) processor.postProcessAfterInitialization(repository, null);
repository = (ConvertingRepository) processor.postProcessAfterInitialization(repository, null);
Future<Object> future = repository.findByFirstname("Foo");
@@ -167,6 +178,40 @@ public class RepositoryFactorySupportUnitTests {
verify(factory.queryOne, times(1)).execute(Mockito.any(Object[].class));
}
/**
* @see DATACMNS-509
*/
@Test
public void convertsWithSameElementType() {
List<String> names = Collections.singletonList("Dave");
when(factory.queryOne.execute(Mockito.any(Object[].class))).thenReturn(names);
ConvertingRepository repository = factory.getRepository(ConvertingRepository.class);
Set<String> result = repository.convertListToStringSet();
assertThat(result, hasSize(1));
assertThat(result.iterator().next(), is("Dave"));
}
/**
* @see DATACMNS-509
*/
@Test
public void convertsCollectionToOtherCollectionWithElementSuperType() {
List<String> names = Collections.singletonList("Dave");
when(factory.queryOne.execute(Mockito.any(Object[].class))).thenReturn(names);
ConvertingRepository repository = factory.getRepository(ConvertingRepository.class);
Set<Object> result = repository.convertListToObjectSet();
assertThat(result, hasSize(1));
assertThat(result.iterator().next(), is((Object) "Dave"));
}
interface ObjectRepository extends Repository<Object, Serializable>, ObjectRepositoryCustom {
Object findByClass(Class<?> clazz);
@@ -217,7 +262,11 @@ public class RepositoryFactorySupportUnitTests {
}
interface AsyncRepository extends Repository<Object, Long> {
interface ConvertingRepository extends Repository<Object, Long> {
Set<String> convertListToStringSet();
Set<Object> convertListToObjectSet();
@Async
Future<Object> findByFirstname(String firstname);