Polishing and minor refactoring in GraphQlArgumentInitializer
This commit is contained in:
@@ -30,7 +30,6 @@ import org.springframework.graphql.data.method.annotation.Argument;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.DataBinder;
|
||||
|
||||
/**
|
||||
* Resolver for {@link Argument @Argument} annotated method parameters, obtained
|
||||
@@ -43,15 +42,14 @@ import org.springframework.validation.DataBinder;
|
||||
*/
|
||||
public class ArgumentMethodArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
private final GraphQlArgumentInstantiator instantiator;
|
||||
private final GraphQlArgumentInitializer argumentInitializer;
|
||||
|
||||
private final ConversionService conversionService;
|
||||
|
||||
public ArgumentMethodArgumentResolver(@Nullable ConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
this.instantiator = new GraphQlArgumentInstantiator(conversionService);
|
||||
this.argumentInitializer = new GraphQlArgumentInitializer(conversionService);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return parameter.getParameterAnnotation(Argument.class) != null;
|
||||
@@ -73,49 +71,48 @@ public class ArgumentMethodArgumentResolver implements HandlerMethodArgumentReso
|
||||
}
|
||||
|
||||
Object rawValue = environment.getArgument(name);
|
||||
TypeDescriptor parameterType = new TypeDescriptor(parameter);
|
||||
TypeDescriptor typeDescriptor = new TypeDescriptor(parameter);
|
||||
|
||||
if (rawValue == null) {
|
||||
return returnValue(rawValue, parameterType.getType());
|
||||
return wrapAsOptionalIfNecessary(null, typeDescriptor.getType());
|
||||
}
|
||||
|
||||
// From Collection
|
||||
|
||||
if (CollectionFactory.isApproximableCollectionType(rawValue.getClass())) {
|
||||
Assert.isAssignable(Collection.class, parameterType.getType(),
|
||||
"Argument '" + name + "' is a Collection while the @Argument method parameter is " + parameterType.getType());
|
||||
Class<?> elementType = parameterType.getElementTypeDescriptor().getType();
|
||||
return this.instantiator.instantiateCollection(elementType, (Collection<Object>) rawValue);
|
||||
Assert.isAssignable(Collection.class, typeDescriptor.getType(),
|
||||
"Argument '" + name + "' is a Collection " +
|
||||
"while the @Argument method parameter is " + typeDescriptor.getType());
|
||||
Class<?> elementType = typeDescriptor.getElementTypeDescriptor().getType();
|
||||
return this.argumentInitializer.initializeFromCollection((Collection<Object>) rawValue, elementType);
|
||||
}
|
||||
|
||||
MethodParameter nestedParameter = parameter.nestedIfOptional();
|
||||
Object value = convert(rawValue, nestedParameter.getNestedParameterType());
|
||||
return returnValue(value, parameterType.getType());
|
||||
}
|
||||
|
||||
private Object returnValue(Object value, Class<?> parameterType) {
|
||||
if (parameterType.equals(Optional.class)) {
|
||||
return Optional.ofNullable(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Object convert(Object rawValue, Class<?> targetType) {
|
||||
Class<?> targetType = parameter.nestedIfOptional().getNestedParameterType();
|
||||
Object target;
|
||||
|
||||
// From Map
|
||||
|
||||
if (rawValue instanceof Map) {
|
||||
target = this.instantiator.instantiate((Map<String, Object>) rawValue, targetType);
|
||||
target = this.argumentInitializer.initializeFromMap((Map<String, Object>) rawValue, targetType);
|
||||
return wrapAsOptionalIfNecessary(target, typeDescriptor.getType());
|
||||
}
|
||||
else if (targetType.isAssignableFrom(rawValue.getClass())) {
|
||||
return returnValue(rawValue, targetType);
|
||||
|
||||
// From Scalar
|
||||
|
||||
if (targetType.isAssignableFrom(rawValue.getClass())) {
|
||||
return wrapAsOptionalIfNecessary(rawValue, targetType);
|
||||
}
|
||||
else {
|
||||
DataBinder converter = new DataBinder(null);
|
||||
converter.setConversionService(this.conversionService);
|
||||
target = converter.convertIfNecessary(rawValue, targetType);
|
||||
Assert.isTrue(target != null,
|
||||
() -> "Value of type [" + rawValue.getClass() + "] cannot be converted to argument of type [" +
|
||||
targetType.getName() + "].");
|
||||
}
|
||||
return target;
|
||||
|
||||
target = this.argumentInitializer.getTypeConverter().convertIfNecessary(rawValue, targetType);
|
||||
Assert.state(target != null, () ->
|
||||
"Cannot convert value type [" + rawValue.getClass() + "] " +
|
||||
"to argument type [" + targetType.getName() + "].");
|
||||
return wrapAsOptionalIfNecessary(target, typeDescriptor.getType());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object wrapAsOptionalIfNecessary(@Nullable Object value, Class<?> type) {
|
||||
return (type.equals(Optional.class) ? Optional.ofNullable(value) : value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ import java.util.Stack;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.MutablePropertyValues;
|
||||
import org.springframework.beans.SimpleTypeConverter;
|
||||
import org.springframework.beans.TypeConverter;
|
||||
import org.springframework.core.CollectionFactory;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
@@ -39,21 +41,32 @@ import org.springframework.validation.DataBinder;
|
||||
* {@link graphql.schema.DataFetchingEnvironment} arguments.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Greg Turnquist
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class GraphQlArgumentInstantiator {
|
||||
public class GraphQlArgumentInitializer {
|
||||
|
||||
private final DataBinder converter;
|
||||
private final SimpleTypeConverter typeConverter;
|
||||
|
||||
public GraphQlArgumentInstantiator(@Nullable ConversionService conversionService) {
|
||||
this.converter = new DataBinder(null);
|
||||
this.converter.setConversionService(conversionService);
|
||||
|
||||
public GraphQlArgumentInitializer(@Nullable ConversionService conversionService) {
|
||||
this.typeConverter = new SimpleTypeConverter();
|
||||
this.typeConverter.setConversionService(conversionService);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Instantiate the given target type and bind data from
|
||||
* {@link graphql.schema.DataFetchingEnvironment} arguments.
|
||||
* <p>This is considering the default constructor or a primary constructor
|
||||
* Return the underlying {@link DataBinder}.
|
||||
*/
|
||||
public TypeConverter getTypeConverter() {
|
||||
return this.typeConverter;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Instantiate an Object of the given target type and bind
|
||||
* {@link graphql.schema.DataFetchingEnvironment} argument values to it.
|
||||
* This considers using the default constructor or a primary constructor,
|
||||
* if available.
|
||||
*
|
||||
* @param arguments the data fetching environment arguments
|
||||
@@ -63,7 +76,7 @@ public class GraphQlArgumentInstantiator {
|
||||
* @throws IllegalStateException if there is no suitable constructor.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T instantiate(Map<String, Object> arguments, Class<T> targetType) {
|
||||
public <T> T initializeFromMap(Map<String, Object> arguments, Class<T> targetType) {
|
||||
Object target;
|
||||
Constructor<?> ctor = BeanUtils.getResolvableConstructor(targetType);
|
||||
|
||||
@@ -72,33 +85,35 @@ public class GraphQlArgumentInstantiator {
|
||||
target = BeanUtils.instantiateClass(ctor);
|
||||
DataBinder dataBinder = new DataBinder(target);
|
||||
dataBinder.bind(propertyValues);
|
||||
return (T) target;
|
||||
}
|
||||
else {
|
||||
// Data class constructor
|
||||
String[] paramNames = BeanUtils.getParameterNames(ctor);
|
||||
Class<?>[] paramTypes = ctor.getParameterTypes();
|
||||
Object[] args = new Object[paramTypes.length];
|
||||
for (int i = 0; i < paramNames.length; i++) {
|
||||
String paramName = paramNames[i];
|
||||
Object value = arguments.get(paramName);
|
||||
MethodParameter methodParam = new MethodParameter(ctor, i);
|
||||
if (value == null && methodParam.isOptional()) {
|
||||
args[i] = (methodParam.getParameterType() == Optional.class ? Optional.empty() : null);
|
||||
}
|
||||
else if (value != null && CollectionFactory.isApproximableCollectionType(value.getClass())) {
|
||||
TypeDescriptor typeDescriptor = new TypeDescriptor(methodParam);
|
||||
Class<?> elementType = typeDescriptor.getElementTypeDescriptor().getType();
|
||||
args[i] = instantiateCollection(elementType, (Collection<Object>) value);
|
||||
}
|
||||
else if (value instanceof Map) {
|
||||
args[i] = this.instantiate((Map<String, Object>) value, methodParam.getParameterType());
|
||||
} else {
|
||||
args[i] = this.converter.convertIfNecessary(value, paramTypes[i], methodParam);
|
||||
}
|
||||
|
||||
// Data class constructor
|
||||
|
||||
String[] paramNames = BeanUtils.getParameterNames(ctor);
|
||||
Class<?>[] paramTypes = ctor.getParameterTypes();
|
||||
Object[] args = new Object[paramTypes.length];
|
||||
for (int i = 0; i < paramNames.length; i++) {
|
||||
String paramName = paramNames[i];
|
||||
Object value = arguments.get(paramName);
|
||||
MethodParameter methodParam = new MethodParameter(ctor, i);
|
||||
if (value == null && methodParam.isOptional()) {
|
||||
args[i] = (methodParam.getParameterType() == Optional.class ? Optional.empty() : null);
|
||||
}
|
||||
else if (value != null && CollectionFactory.isApproximableCollectionType(value.getClass())) {
|
||||
TypeDescriptor typeDescriptor = new TypeDescriptor(methodParam);
|
||||
Class<?> elementType = typeDescriptor.getElementTypeDescriptor().getType();
|
||||
args[i] = initializeFromCollection((Collection<Object>) value, elementType);
|
||||
}
|
||||
else if (value instanceof Map) {
|
||||
args[i] = this.initializeFromMap((Map<String, Object>) value, methodParam.getParameterType());
|
||||
}
|
||||
else {
|
||||
args[i] = this.typeConverter.convertIfNecessary(value, paramTypes[i], methodParam);
|
||||
}
|
||||
target = BeanUtils.instantiateClass(ctor, args);
|
||||
}
|
||||
return (T) target;
|
||||
|
||||
return (T) BeanUtils.instantiateClass(ctor, args);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,14 +121,14 @@ public class GraphQlArgumentInstantiator {
|
||||
* <p>This will instantiate a new Collection of the closest type possible
|
||||
* from the one provided as an argument.
|
||||
*
|
||||
* @param elementType the type of elements in the given Collection
|
||||
* @param values the collection of values to bind and instantiate
|
||||
* @param <T> the type of Collection elements
|
||||
* @param values the collection of values to bind and instantiate
|
||||
* @param elementType the type of elements in the given Collection
|
||||
* @return the instantiated and populated Collection.
|
||||
* @throws IllegalStateException if there is no suitable constructor.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Collection<T> instantiateCollection(Class<T> elementType, Collection<Object> values) {
|
||||
public <T> Collection<T> initializeFromCollection(Collection<Object> values, Class<T> elementType) {
|
||||
Assert.state(CollectionFactory.isApproximableCollectionType(values.getClass()),
|
||||
() -> "Cannot instantiate Collection for type " + values.getClass());
|
||||
Collection<T> instances = CollectionFactory.createApproximateCollection(values, values.size());
|
||||
@@ -123,10 +138,10 @@ public class GraphQlArgumentInstantiator {
|
||||
value = (T) item;
|
||||
}
|
||||
else if (item instanceof Map) {
|
||||
value = this.instantiate((Map<String, Object>)item, elementType);
|
||||
value = this.initializeFromMap((Map<String, Object>)item, elementType);
|
||||
}
|
||||
else {
|
||||
value = this.converter.convertIfNecessary(item, elementType);
|
||||
value = this.typeConverter.convertIfNecessary(item, elementType);
|
||||
}
|
||||
instances.add(value);
|
||||
});
|
||||
@@ -179,4 +194,5 @@ public class GraphQlArgumentInstantiator {
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -57,7 +57,7 @@ import org.springframework.data.repository.query.ReactiveQueryByExampleExecutor;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.graphql.data.GraphQlRepository;
|
||||
import org.springframework.graphql.data.method.annotation.support.GraphQlArgumentInstantiator;
|
||||
import org.springframework.graphql.data.method.annotation.support.GraphQlArgumentInitializer;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -110,12 +110,12 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
|
||||
private final TypeInformation<T> domainType;
|
||||
|
||||
private final GraphQlArgumentInstantiator instantiator;
|
||||
private final GraphQlArgumentInitializer argumentInitializer;
|
||||
|
||||
|
||||
QueryByExampleDataFetcher(TypeInformation<T> domainType) {
|
||||
this.domainType = domainType;
|
||||
this.instantiator = new GraphQlArgumentInstantiator(null);
|
||||
this.argumentInitializer = new GraphQlArgumentInitializer(null);
|
||||
}
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
* @return the resulting example
|
||||
*/
|
||||
protected Example<T> buildExample(DataFetchingEnvironment env) {
|
||||
return Example.of(this.instantiator.instantiate(env.getArguments(), this.domainType.getType()));
|
||||
return Example.of(this.argumentInitializer.initializeFromMap(env.getArguments(), this.domainType.getType()));
|
||||
}
|
||||
|
||||
protected boolean requiresProjection(Class<?> resultType) {
|
||||
|
||||
@@ -32,21 +32,21 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Tests for {@link GraphQlArgumentInstantiator}
|
||||
* Tests for {@link GraphQlArgumentInitializer}
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class GraphQlArgumentInstantiatorTests {
|
||||
class GraphQlArgumentInitializerTests {
|
||||
|
||||
private ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
private GraphQlArgumentInstantiator instantiator = new GraphQlArgumentInstantiator(null);
|
||||
private GraphQlArgumentInitializer instantiator = new GraphQlArgumentInitializer(null);
|
||||
|
||||
@Test
|
||||
void shouldInstantiateDefaultConstructor() throws Exception {
|
||||
String payload = "{\"simpleBean\": { \"name\": \"test\"} }";
|
||||
DataFetchingEnvironment environment = initEnvironment(payload);
|
||||
SimpleBean result = instantiator.instantiate(environment.getArgument("simpleBean"), SimpleBean.class);
|
||||
SimpleBean result = instantiator.initializeFromMap(environment.getArgument("simpleBean"), SimpleBean.class);
|
||||
|
||||
assertThat(result).isNotNull().isInstanceOf(SimpleBean.class);
|
||||
assertThat(result).hasFieldOrPropertyWithValue("name", "test");
|
||||
@@ -56,7 +56,7 @@ class GraphQlArgumentInstantiatorTests {
|
||||
void shouldInstantiatePrimaryConstructor() throws Exception {
|
||||
String payload = "{\"constructorBean\": { \"name\": \"test\"} }";
|
||||
DataFetchingEnvironment environment = initEnvironment(payload);
|
||||
ContructorBean result = instantiator.instantiate(environment.getArgument("constructorBean"), ContructorBean.class);
|
||||
ContructorBean result = instantiator.initializeFromMap(environment.getArgument("constructorBean"), ContructorBean.class);
|
||||
|
||||
assertThat(result).isNotNull().isInstanceOf(ContructorBean.class);
|
||||
assertThat(result).hasFieldOrPropertyWithValue("name", "test");
|
||||
@@ -66,7 +66,7 @@ class GraphQlArgumentInstantiatorTests {
|
||||
void shouldFailIfNoPrimaryConstructor() throws Exception {
|
||||
String payload = "{\"noPrimary\": { \"name\": \"test\"} }";
|
||||
DataFetchingEnvironment environment = initEnvironment(payload);
|
||||
assertThatThrownBy(() -> instantiator.instantiate(environment.getArgument("noPrimary"), NoPrimaryConstructor.class))
|
||||
assertThatThrownBy(() -> instantiator.initializeFromMap(environment.getArgument("noPrimary"), NoPrimaryConstructor.class))
|
||||
.isInstanceOf(IllegalStateException.class).hasMessageContaining("No primary or single unique constructor found");
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ class GraphQlArgumentInstantiatorTests {
|
||||
void shouldInstantiateNestedBean() throws Exception {
|
||||
String payload = "{\"book\": { \"name\": \"test name\", \"author\": { \"firstName\": \"Jane\", \"lastName\": \"Spring\"} } }";
|
||||
DataFetchingEnvironment environment = initEnvironment(payload);
|
||||
Book result = instantiator.instantiate(environment.getArgument("book"), Book.class);
|
||||
Book result = instantiator.initializeFromMap(environment.getArgument("book"), Book.class);
|
||||
|
||||
assertThat(result).isNotNull().isInstanceOf(Book.class);
|
||||
assertThat(result).hasFieldOrPropertyWithValue("name", "test name");
|
||||
@@ -87,7 +87,7 @@ class GraphQlArgumentInstantiatorTests {
|
||||
void shouldInstantiateNestedBeanLists() throws Exception {
|
||||
String payload = "{\"nestedList\": { \"items\": [ {\"name\": \"first\"}, {\"name\": \"second\"}] } }";
|
||||
DataFetchingEnvironment environment = initEnvironment(payload);
|
||||
NestedList result = instantiator.instantiate(environment.getArgument("nestedList"), NestedList.class);
|
||||
NestedList result = instantiator.initializeFromMap(environment.getArgument("nestedList"), NestedList.class);
|
||||
|
||||
assertThat(result).isNotNull().isInstanceOf(NestedList.class);
|
||||
assertThat(result.getItems()).hasSize(2).extracting("name").containsExactly("first", "second");
|
||||
@@ -97,7 +97,7 @@ class GraphQlArgumentInstantiatorTests {
|
||||
void shouldInstantiatePrimaryConstructorNestedBeanLists() throws Exception {
|
||||
String payload = "{\"nestedList\": { \"items\": [ {\"name\": \"first\"}, {\"name\": \"second\"}] } }";
|
||||
DataFetchingEnvironment environment = initEnvironment(payload);
|
||||
PrimaryConstructorNestedList result = instantiator.instantiate(environment.getArgument("nestedList"), PrimaryConstructorNestedList.class);
|
||||
PrimaryConstructorNestedList result = instantiator.initializeFromMap(environment.getArgument("nestedList"), PrimaryConstructorNestedList.class);
|
||||
|
||||
assertThat(result).isNotNull().isInstanceOf(PrimaryConstructorNestedList.class);
|
||||
assertThat(result.getItems()).hasSize(2).extracting("name").containsExactly("first", "second");
|
||||
@@ -107,7 +107,7 @@ class GraphQlArgumentInstantiatorTests {
|
||||
void shouldInstantiateComplexNestedBean() throws Exception {
|
||||
String payload = "{\"complex\": { \"item\": {\"name\": \"Item name\"}, \"name\": \"Hello\" } }";
|
||||
DataFetchingEnvironment environment = initEnvironment(payload);
|
||||
PrimaryConstructorComplexInput result = instantiator.instantiate(environment.getArgument("complex"), PrimaryConstructorComplexInput.class);
|
||||
PrimaryConstructorComplexInput result = instantiator.initializeFromMap(environment.getArgument("complex"), PrimaryConstructorComplexInput.class);
|
||||
|
||||
assertThat(result).isNotNull().isInstanceOf(PrimaryConstructorComplexInput.class);
|
||||
assertThat(result.item.name).isEqualTo("Item name");
|
||||
Reference in New Issue
Block a user