Polish "Recursively instantiate arguments"
Closes gh-147
This commit is contained in:
@@ -80,11 +80,8 @@ public class ArgumentMethodArgumentResolver implements HandlerMethodArgumentReso
|
||||
if (CollectionFactory.isApproximableCollectionType(rawValue.getClass())) {
|
||||
Assert.isAssignable(Collection.class, parameterType.getType(),
|
||||
"Argument '" + name + "' is a Collection while the @Argument method parameter is " + parameterType.getType());
|
||||
Collection<Object> rawCollection = (Collection<Object>) rawValue;
|
||||
Collection<Object> values = CollectionFactory.createApproximateCollection(rawValue, rawCollection.size());
|
||||
Class<?> elementType = parameterType.getElementTypeDescriptor().getType();
|
||||
rawCollection.forEach(item -> values.add(convert(item, elementType)));
|
||||
return values;
|
||||
return this.instantiator.instantiateCollection(elementType, (Collection<Object>) rawValue);
|
||||
}
|
||||
|
||||
MethodParameter nestedParameter = parameter.nestedIfOptional();
|
||||
@@ -103,7 +100,7 @@ public class ArgumentMethodArgumentResolver implements HandlerMethodArgumentReso
|
||||
private Object convert(Object rawValue, Class<?> targetType) {
|
||||
Object target;
|
||||
if (rawValue instanceof Map) {
|
||||
target = this.instantiator.instantiate(targetType, (Map<String, Object>) rawValue);
|
||||
target = this.instantiator.instantiate((Map<String, Object>) rawValue, targetType);
|
||||
}
|
||||
else if (targetType.isAssignableFrom(rawValue.getClass())) {
|
||||
return returnValue(rawValue, targetType);
|
||||
|
||||
@@ -26,10 +26,10 @@ import java.util.Stack;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.MutablePropertyValues;
|
||||
import org.springframework.beans.PropertyValues;
|
||||
import org.springframework.core.CollectionFactory;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.validation.DataBinder;
|
||||
|
||||
/**
|
||||
@@ -40,20 +40,22 @@ import org.springframework.validation.DataBinder;
|
||||
*/
|
||||
class GraphQlArgumentInstantiator {
|
||||
|
||||
private final DataBinder converter = new DataBinder(null);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* if available.
|
||||
*
|
||||
* @param targetType the type of the argument to instantiate
|
||||
* @param arguments the data fetching environment arguments
|
||||
* @param targetType the type of the argument to instantiate
|
||||
* @param <T> the type of the input argument
|
||||
* @return the instantiated and populated input argument.
|
||||
* @throws IllegalStateException if there is no suitable constructor.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T instantiate(Class<T> targetType, Map<String, Object> arguments) {
|
||||
public <T> T instantiate(Map<String, Object> arguments, Class<T> targetType) {
|
||||
Object target;
|
||||
Constructor<?> ctor = BeanUtils.getResolvableConstructor(targetType);
|
||||
|
||||
@@ -65,7 +67,6 @@ class GraphQlArgumentInstantiator {
|
||||
}
|
||||
else {
|
||||
// Data class constructor
|
||||
DataBinder binder = new DataBinder(null);
|
||||
String[] paramNames = BeanUtils.getParameterNames(ctor);
|
||||
Class<?>[] paramTypes = ctor.getParameterTypes();
|
||||
Object[] args = new Object[paramTypes.length];
|
||||
@@ -77,16 +78,12 @@ class GraphQlArgumentInstantiator {
|
||||
args[i] = (methodParam.getParameterType() == Optional.class ? Optional.empty() : null);
|
||||
}
|
||||
else if (value != null && CollectionFactory.isApproximableCollectionType(value.getClass())) {
|
||||
Collection<Object> rawCollection = (Collection<Object>) value;
|
||||
Collection<Object> values = CollectionFactory.createApproximateCollection(value, rawCollection.size());
|
||||
|
||||
TypeDescriptor typeDescriptor = new TypeDescriptor(methodParam);
|
||||
Class<?> elementType = typeDescriptor.getElementTypeDescriptor().getType();
|
||||
rawCollection.forEach(item -> values.add(this.instantiate(elementType, (Map<String, Object>)item)));
|
||||
args[i] = values;
|
||||
args[i] = instantiateCollection(elementType, (Collection<Object>) value);
|
||||
}
|
||||
else {
|
||||
args[i] = binder.convertIfNecessary(value, paramTypes[i], methodParam);
|
||||
args[i] = this.converter.convertIfNecessary(value, paramTypes[i], methodParam);
|
||||
}
|
||||
}
|
||||
target = BeanUtils.instantiateClass(ctor, args);
|
||||
@@ -94,6 +91,38 @@ class GraphQlArgumentInstantiator {
|
||||
return (T) target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a collection of {@code elementType} using the given {@code values}.
|
||||
* <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
|
||||
* @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) {
|
||||
Assert.state(CollectionFactory.isApproximableCollectionType(values.getClass()),
|
||||
() -> "Cannot instantiate Collection for type " + values.getClass());
|
||||
Collection<T> instances = CollectionFactory.createApproximateCollection(values, values.size());
|
||||
values.forEach(item -> {
|
||||
T value;
|
||||
if (elementType.isAssignableFrom(item.getClass())) {
|
||||
value = (T) item;
|
||||
}
|
||||
else if (item instanceof Map) {
|
||||
value = this.instantiate((Map<String, Object>)item, elementType);
|
||||
}
|
||||
else {
|
||||
value = this.converter.convertIfNecessary(item, elementType);
|
||||
}
|
||||
instances.add(value);
|
||||
});
|
||||
return instances;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a Depth First Search in the given JSON map to collect attribute values
|
||||
* as {@link MutablePropertyValues} using the full property path as key.
|
||||
|
||||
@@ -46,7 +46,7 @@ class GraphQlArgumentInstantiatorTests {
|
||||
void shouldInstantiateDefaultConstructor() throws Exception {
|
||||
String payload = "{\"simpleBean\": { \"name\": \"test\"} }";
|
||||
DataFetchingEnvironment environment = initEnvironment(payload);
|
||||
SimpleBean result = instantiator.instantiate(SimpleBean.class, environment.getArgument("simpleBean"));
|
||||
SimpleBean result = instantiator.instantiate(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(ContructorBean.class, environment.getArgument("constructorBean"));
|
||||
ContructorBean result = instantiator.instantiate(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(NoPrimaryConstructor.class, environment.getArgument("noPrimary")))
|
||||
assertThatThrownBy(() -> instantiator.instantiate(environment.getArgument("noPrimary"), NoPrimaryConstructor.class))
|
||||
.isInstanceOf(IllegalStateException.class).hasMessageContaining("No primary or single public 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(Book.class, environment.getArgument("book"));
|
||||
Book result = instantiator.instantiate(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(NestedList.class, environment.getArgument("nestedList"));
|
||||
NestedList result = instantiator.instantiate(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(PrimaryConstructorNestedList.class, environment.getArgument("nestedList"));
|
||||
PrimaryConstructorNestedList result = instantiator.instantiate(environment.getArgument("nestedList"), PrimaryConstructorNestedList.class);
|
||||
|
||||
assertThat(result).isNotNull().isInstanceOf(PrimaryConstructorNestedList.class);
|
||||
assertThat(result.getItems()).hasSize(2).extracting("name").containsExactly("first", "second");
|
||||
|
||||
Reference in New Issue
Block a user