DATACMNS-610 - Extracted QueryExecutionResultHandler from RepositoryFactorySupport.

To be able to verify the conversion of List based query execution results into Sets I extracted a QueryExecutionResultHandler that applies the already implemented handling for Optional types and eventually general prost processing in case the return type declared at the repository query method doesn't match the returned value.

Added additional unit tests for Optional handling.
This commit is contained in:
Oliver Gierke
2014-12-04 20:25:11 +01:00
parent 5b1b73b297
commit d8781888d8
3 changed files with 205 additions and 30 deletions

View File

@@ -0,0 +1,78 @@
/*
* Copyright 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.repository.core.support;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.repository.util.NullableWrapper;
import org.springframework.data.repository.util.QueryExecutionConverters;
/**
* Simple domain service to convert query results into a dedicated type.
*
* @author Oliver Gierke
*/
class QueryExecutionResultHandler {
private static final TypeDescriptor WRAPPER_TYPE = TypeDescriptor.valueOf(NullableWrapper.class);
private final GenericConversionService conversionService;
/**
* Creates a new {@link QueryExecutionResultHandler}.
*/
public QueryExecutionResultHandler() {
GenericConversionService conversionService = new DefaultConversionService();
QueryExecutionConverters.registerConvertersIn(conversionService);
this.conversionService = conversionService;
}
/**
* Post-processes the given result of a query invocation to the given type.
*
* @param result can be {@literal null}.
* @param returnTypeDesciptor can be {@literal null}, if so, no conversion is performed.
* @return
*/
public Object postProcessInvocationResult(Object result, TypeDescriptor returnTypeDesciptor) {
if (returnTypeDesciptor == null) {
return result;
}
Class<?> expectedReturnType = returnTypeDesciptor.getType();
if (result != null && expectedReturnType.isInstance(result)) {
return result;
}
if (QueryExecutionConverters.supports(expectedReturnType)
&& conversionService.canConvert(WRAPPER_TYPE, returnTypeDesciptor)
&& !conversionService.canBypassConvert(WRAPPER_TYPE, TypeDescriptor.valueOf(expectedReturnType))) {
return conversionService.convert(new NullableWrapper(result), expectedReturnType);
}
if (result == null) {
return null;
}
return conversionService.canConvert(result.getClass(), expectedReturnType) ? conversionService.convert(result,
expectedReturnType) : result;
}
}

View File

@@ -35,8 +35,6 @@ import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.data.repository.core.NamedQueries;
@@ -49,8 +47,6 @@ import org.springframework.data.repository.query.QueryLookupStrategy.Key;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.util.ClassUtils;
import org.springframework.data.repository.util.NullableWrapper;
import org.springframework.data.repository.util.QueryExecutionConverters;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
@@ -63,7 +59,6 @@ import org.springframework.util.ObjectUtils;
*/
public abstract class RepositoryFactorySupport implements BeanClassLoaderAware {
private static final TypeDescriptor WRAPPER_TYPE = TypeDescriptor.valueOf(NullableWrapper.class);
private static final boolean IS_JAVA_8 = org.springframework.util.ClassUtils.isPresent("java.util.Optional",
RepositoryFactorySupport.class.getClassLoader());
@@ -316,7 +311,7 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware {
private final Object customImplementation;
private final RepositoryInformation repositoryInformation;
private final GenericConversionService conversionService;
private final QueryExecutionResultHandler resultHandler;
private final Object target;
/**
@@ -329,10 +324,7 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware {
Assert.notNull(repositoryInformation, "RepositoryInformation must not be null!");
Assert.notNull(target, "Target must not be null!");
DefaultConversionService conversionService = new DefaultConversionService();
QueryExecutionConverters.registerConvertersIn(conversionService);
this.conversionService = conversionService;
this.resultHandler = new QueryExecutionResultHandler();
this.repositoryInformation = repositoryInformation;
this.customImplementation = customImplementation;
this.target = target;
@@ -380,30 +372,12 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware {
Object result = doInvoke(invocation);
Method method = invocation.getMethod();
// Looking up the TypeDescriptor for the return type - yes, this way o.O
Method method = invocation.getMethod();
MethodParameter parameter = new MethodParameter(method, -1);
TypeDescriptor methodReturnTypeDescriptor = TypeDescriptor.nested(parameter, 0);
Class<?> expectedReturnType = method.getReturnType();
if (result != null && expectedReturnType.isInstance(result)) {
return result;
}
if (QueryExecutionConverters.supports(expectedReturnType)
&& conversionService.canConvert(WRAPPER_TYPE, methodReturnTypeDescriptor)
&& !conversionService.canBypassConvert(WRAPPER_TYPE, TypeDescriptor.valueOf(expectedReturnType))) {
return conversionService.convert(new NullableWrapper(result), expectedReturnType);
}
if (result == null) {
return null;
}
return conversionService.canConvert(result.getClass(), expectedReturnType) ? conversionService.convert(result,
expectedReturnType) : result;
return resultHandler.postProcessInvocationResult(result, methodReturnTypeDescriptor);
}
private Object doInvoke(MethodInvocation invocation) throws Throwable {

View File

@@ -0,0 +1,123 @@
/*
* Copyright 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.repository.core.support;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.data.repository.Repository;
/**
* Unit tests for {@link QueryExecutionResultHandler}.
*
* @author Oliver Gierke
*/
public class QueryExecutionResultHandlerUnitTests {
QueryExecutionResultHandler handler = new QueryExecutionResultHandler();
/**
* @see DATACMNS-610
*/
@Test
public void convertsListsToSet() throws Exception {
TypeDescriptor descriptor = getTypeDescriptorFor("set");
List<Entity> source = Collections.singletonList(new Entity());
assertThat(handler.postProcessInvocationResult(source, descriptor), is(instanceOf(Set.class)));
}
/**
* @see DATACMNS-483
*/
@Test
public void turnsNullIntoJdk8Optional() throws Exception {
Object result = handler.postProcessInvocationResult(null, getTypeDescriptorFor("jdk8Optional"));
assertThat(result, is((Object) Optional.empty()));
}
/**
* @see DATACMNS-483
*/
@Test
@SuppressWarnings("unchecked")
public void wrapsValueIntoJdk8Optional() throws Exception {
Entity entity = new Entity();
Object result = handler.postProcessInvocationResult(entity, getTypeDescriptorFor("jdk8Optional"));
assertThat(result, is(instanceOf(Optional.class)));
Optional<Entity> optional = (Optional<Entity>) result;
assertThat(optional, is(Optional.of(entity)));
}
/**
* @see DATACMNS-483
*/
@Test
public void turnsNullIntoGuavaOptional() throws Exception {
Object result = handler.postProcessInvocationResult(null, getTypeDescriptorFor("guavaOptional"));
assertThat(result, is((Object) com.google.common.base.Optional.absent()));
}
/**
* @see DATACMNS-483
*/
@Test
@SuppressWarnings("unchecked")
public void wrapsValueIntoGuavaOptional() throws Exception {
Entity entity = new Entity();
Object result = handler.postProcessInvocationResult(entity, getTypeDescriptorFor("guavaOptional"));
assertThat(result, is(instanceOf(com.google.common.base.Optional.class)));
com.google.common.base.Optional<Entity> optional = (com.google.common.base.Optional<Entity>) result;
assertThat(optional, is(com.google.common.base.Optional.of(entity)));
}
private static TypeDescriptor getTypeDescriptorFor(String methodName) throws Exception {
Method method = Sample.class.getMethod(methodName);
MethodParameter parameter = new MethodParameter(method, -1);
return TypeDescriptor.nested(parameter, 0);
}
static interface Sample extends Repository<Entity, Long> {
Set<Entity> set();
Optional<Entity> jdk8Optional();
com.google.common.base.Optional<Entity> guavaOptional();
}
static class Entity {}
}