Skip to content

Commit d878188

Browse files
committed
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.
1 parent 5b1b73b commit d878188

File tree

3 files changed

+205
-30
lines changed

3 files changed

+205
-30
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.repository.core.support;
17+
18+
import org.springframework.core.convert.TypeDescriptor;
19+
import org.springframework.core.convert.support.DefaultConversionService;
20+
import org.springframework.core.convert.support.GenericConversionService;
21+
import org.springframework.data.repository.util.NullableWrapper;
22+
import org.springframework.data.repository.util.QueryExecutionConverters;
23+
24+
/**
25+
* Simple domain service to convert query results into a dedicated type.
26+
*
27+
* @author Oliver Gierke
28+
*/
29+
class QueryExecutionResultHandler {
30+
31+
private static final TypeDescriptor WRAPPER_TYPE = TypeDescriptor.valueOf(NullableWrapper.class);
32+
33+
private final GenericConversionService conversionService;
34+
35+
/**
36+
* Creates a new {@link QueryExecutionResultHandler}.
37+
*/
38+
public QueryExecutionResultHandler() {
39+
40+
GenericConversionService conversionService = new DefaultConversionService();
41+
QueryExecutionConverters.registerConvertersIn(conversionService);
42+
43+
this.conversionService = conversionService;
44+
}
45+
46+
/**
47+
* Post-processes the given result of a query invocation to the given type.
48+
*
49+
* @param result can be {@literal null}.
50+
* @param returnTypeDesciptor can be {@literal null}, if so, no conversion is performed.
51+
* @return
52+
*/
53+
public Object postProcessInvocationResult(Object result, TypeDescriptor returnTypeDesciptor) {
54+
55+
if (returnTypeDesciptor == null) {
56+
return result;
57+
}
58+
59+
Class<?> expectedReturnType = returnTypeDesciptor.getType();
60+
61+
if (result != null && expectedReturnType.isInstance(result)) {
62+
return result;
63+
}
64+
65+
if (QueryExecutionConverters.supports(expectedReturnType)
66+
&& conversionService.canConvert(WRAPPER_TYPE, returnTypeDesciptor)
67+
&& !conversionService.canBypassConvert(WRAPPER_TYPE, TypeDescriptor.valueOf(expectedReturnType))) {
68+
return conversionService.convert(new NullableWrapper(result), expectedReturnType);
69+
}
70+
71+
if (result == null) {
72+
return null;
73+
}
74+
75+
return conversionService.canConvert(result.getClass(), expectedReturnType) ? conversionService.convert(result,
76+
expectedReturnType) : result;
77+
}
78+
}

src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java

+4-30
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@
3535
import org.springframework.core.GenericTypeResolver;
3636
import org.springframework.core.MethodParameter;
3737
import org.springframework.core.convert.TypeDescriptor;
38-
import org.springframework.core.convert.support.DefaultConversionService;
39-
import org.springframework.core.convert.support.GenericConversionService;
4038
import org.springframework.data.repository.Repository;
4139
import org.springframework.data.repository.core.EntityInformation;
4240
import org.springframework.data.repository.core.NamedQueries;
@@ -49,8 +47,6 @@
4947
import org.springframework.data.repository.query.QueryMethod;
5048
import org.springframework.data.repository.query.RepositoryQuery;
5149
import org.springframework.data.repository.util.ClassUtils;
52-
import org.springframework.data.repository.util.NullableWrapper;
53-
import org.springframework.data.repository.util.QueryExecutionConverters;
5450
import org.springframework.util.Assert;
5551
import org.springframework.util.ObjectUtils;
5652

@@ -63,7 +59,6 @@
6359
*/
6460
public abstract class RepositoryFactorySupport implements BeanClassLoaderAware {
6561

66-
private static final TypeDescriptor WRAPPER_TYPE = TypeDescriptor.valueOf(NullableWrapper.class);
6762
private static final boolean IS_JAVA_8 = org.springframework.util.ClassUtils.isPresent("java.util.Optional",
6863
RepositoryFactorySupport.class.getClassLoader());
6964

@@ -316,7 +311,7 @@ public class QueryExecutorMethodInterceptor implements MethodInterceptor {
316311

317312
private final Object customImplementation;
318313
private final RepositoryInformation repositoryInformation;
319-
private final GenericConversionService conversionService;
314+
private final QueryExecutionResultHandler resultHandler;
320315
private final Object target;
321316

322317
/**
@@ -329,10 +324,7 @@ public QueryExecutorMethodInterceptor(RepositoryInformation repositoryInformatio
329324
Assert.notNull(repositoryInformation, "RepositoryInformation must not be null!");
330325
Assert.notNull(target, "Target must not be null!");
331326

332-
DefaultConversionService conversionService = new DefaultConversionService();
333-
QueryExecutionConverters.registerConvertersIn(conversionService);
334-
this.conversionService = conversionService;
335-
327+
this.resultHandler = new QueryExecutionResultHandler();
336328
this.repositoryInformation = repositoryInformation;
337329
this.customImplementation = customImplementation;
338330
this.target = target;
@@ -380,30 +372,12 @@ public Object invoke(MethodInvocation invocation) throws Throwable {
380372

381373
Object result = doInvoke(invocation);
382374

383-
Method method = invocation.getMethod();
384-
385375
// Looking up the TypeDescriptor for the return type - yes, this way o.O
376+
Method method = invocation.getMethod();
386377
MethodParameter parameter = new MethodParameter(method, -1);
387378
TypeDescriptor methodReturnTypeDescriptor = TypeDescriptor.nested(parameter, 0);
388379

389-
Class<?> expectedReturnType = method.getReturnType();
390-
391-
if (result != null && expectedReturnType.isInstance(result)) {
392-
return result;
393-
}
394-
395-
if (QueryExecutionConverters.supports(expectedReturnType)
396-
&& conversionService.canConvert(WRAPPER_TYPE, methodReturnTypeDescriptor)
397-
&& !conversionService.canBypassConvert(WRAPPER_TYPE, TypeDescriptor.valueOf(expectedReturnType))) {
398-
return conversionService.convert(new NullableWrapper(result), expectedReturnType);
399-
}
400-
401-
if (result == null) {
402-
return null;
403-
}
404-
405-
return conversionService.canConvert(result.getClass(), expectedReturnType) ? conversionService.convert(result,
406-
expectedReturnType) : result;
380+
return resultHandler.postProcessInvocationResult(result, methodReturnTypeDescriptor);
407381
}
408382

409383
private Object doInvoke(MethodInvocation invocation) throws Throwable {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Copyright 2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.repository.core.support;
17+
18+
import static org.hamcrest.CoreMatchers.*;
19+
import static org.junit.Assert.*;
20+
21+
import java.lang.reflect.Method;
22+
import java.util.Collections;
23+
import java.util.List;
24+
import java.util.Optional;
25+
import java.util.Set;
26+
27+
import org.junit.Test;
28+
import org.springframework.core.MethodParameter;
29+
import org.springframework.core.convert.TypeDescriptor;
30+
import org.springframework.data.repository.Repository;
31+
32+
/**
33+
* Unit tests for {@link QueryExecutionResultHandler}.
34+
*
35+
* @author Oliver Gierke
36+
*/
37+
public class QueryExecutionResultHandlerUnitTests {
38+
39+
QueryExecutionResultHandler handler = new QueryExecutionResultHandler();
40+
41+
/**
42+
* @see DATACMNS-610
43+
*/
44+
@Test
45+
public void convertsListsToSet() throws Exception {
46+
47+
TypeDescriptor descriptor = getTypeDescriptorFor("set");
48+
List<Entity> source = Collections.singletonList(new Entity());
49+
50+
assertThat(handler.postProcessInvocationResult(source, descriptor), is(instanceOf(Set.class)));
51+
}
52+
53+
/**
54+
* @see DATACMNS-483
55+
*/
56+
@Test
57+
public void turnsNullIntoJdk8Optional() throws Exception {
58+
59+
Object result = handler.postProcessInvocationResult(null, getTypeDescriptorFor("jdk8Optional"));
60+
assertThat(result, is((Object) Optional.empty()));
61+
}
62+
63+
/**
64+
* @see DATACMNS-483
65+
*/
66+
@Test
67+
@SuppressWarnings("unchecked")
68+
public void wrapsValueIntoJdk8Optional() throws Exception {
69+
70+
Entity entity = new Entity();
71+
72+
Object result = handler.postProcessInvocationResult(entity, getTypeDescriptorFor("jdk8Optional"));
73+
assertThat(result, is(instanceOf(Optional.class)));
74+
75+
Optional<Entity> optional = (Optional<Entity>) result;
76+
assertThat(optional, is(Optional.of(entity)));
77+
}
78+
79+
/**
80+
* @see DATACMNS-483
81+
*/
82+
@Test
83+
public void turnsNullIntoGuavaOptional() throws Exception {
84+
85+
Object result = handler.postProcessInvocationResult(null, getTypeDescriptorFor("guavaOptional"));
86+
assertThat(result, is((Object) com.google.common.base.Optional.absent()));
87+
}
88+
89+
/**
90+
* @see DATACMNS-483
91+
*/
92+
@Test
93+
@SuppressWarnings("unchecked")
94+
public void wrapsValueIntoGuavaOptional() throws Exception {
95+
96+
Entity entity = new Entity();
97+
98+
Object result = handler.postProcessInvocationResult(entity, getTypeDescriptorFor("guavaOptional"));
99+
assertThat(result, is(instanceOf(com.google.common.base.Optional.class)));
100+
101+
com.google.common.base.Optional<Entity> optional = (com.google.common.base.Optional<Entity>) result;
102+
assertThat(optional, is(com.google.common.base.Optional.of(entity)));
103+
}
104+
105+
private static TypeDescriptor getTypeDescriptorFor(String methodName) throws Exception {
106+
107+
Method method = Sample.class.getMethod(methodName);
108+
MethodParameter parameter = new MethodParameter(method, -1);
109+
110+
return TypeDescriptor.nested(parameter, 0);
111+
}
112+
113+
static interface Sample extends Repository<Entity, Long> {
114+
115+
Set<Entity> set();
116+
117+
Optional<Entity> jdk8Optional();
118+
119+
com.google.common.base.Optional<Entity> guavaOptional();
120+
}
121+
122+
static class Entity {}
123+
}

0 commit comments

Comments
 (0)