Configure auto grow collection limit in DataBinder
Prior to this commit, the `GraphQlArgumentBinder` would use the default `DataBinder` configuration, including its `DEFAULT_AUTO_GROW_COLLECTION_LIMIT` which is 256. While this is a sensible default value for many use cases, GraphQL applications are data oriented. This commit configures a larger default for GraphQL applications at 1024. This also provides a way to configure the underlying data binder before the binding proces starts. Closes gh-392
This commit is contained in:
@@ -17,12 +17,14 @@
|
||||
package org.springframework.graphql.data;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Stack;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import graphql.schema.DataFetchingEnvironment;
|
||||
|
||||
@@ -59,11 +61,18 @@ import org.springframework.validation.FieldError;
|
||||
*/
|
||||
public class GraphQlArgumentBinder {
|
||||
|
||||
/**
|
||||
* Use a larger {@link DataBinder#DEFAULT_AUTO_GROW_COLLECTION_LIMIT} for GraphQL use cases
|
||||
*/
|
||||
private static final int DEFAULT_AUTO_GROW_COLLECTION_LIMIT = 1024;
|
||||
|
||||
@Nullable
|
||||
private final SimpleTypeConverter typeConverter;
|
||||
|
||||
private final BindingErrorProcessor bindingErrorProcessor = new DefaultBindingErrorProcessor();
|
||||
|
||||
private List<Consumer<DataBinder>> dataBinderInitializers = new ArrayList<>();
|
||||
|
||||
|
||||
public GraphQlArgumentBinder() {
|
||||
this(null);
|
||||
@@ -90,6 +99,15 @@ public class GraphQlArgumentBinder {
|
||||
return (this.typeConverter != null ? this.typeConverter.getConversionService() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link DataBinder} consumer that initializes the binder instance before the binding process.
|
||||
* @param dataBinderInitializer the data binder initializer
|
||||
* @since 1.0.1
|
||||
*/
|
||||
public void addDataBinderInitializer(Consumer<DataBinder> dataBinderInitializer) {
|
||||
this.dataBinderInitializers.add(dataBinderInitializer);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Bind a single argument, or the full arguments map, onto an object of the
|
||||
@@ -122,6 +140,7 @@ public class GraphQlArgumentBinder {
|
||||
Assert.notNull(targetClass, "Could not determine target type from " + targetType);
|
||||
|
||||
DataBinder binder = new DataBinder(null, argumentName != null ? argumentName : "arguments");
|
||||
initDataBinder(binder);
|
||||
BindingResult bindingResult = binder.getBindingResult();
|
||||
Stack<String> segments = new Stack<>();
|
||||
|
||||
@@ -159,6 +178,11 @@ public class GraphQlArgumentBinder {
|
||||
}
|
||||
}
|
||||
|
||||
private void initDataBinder(DataBinder binder) {
|
||||
binder.setAutoGrowCollectionLimit(DEFAULT_AUTO_GROW_COLLECTION_LIMIT);
|
||||
this.dataBinderInitializers.forEach(initializer -> initializer.accept(binder));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object wrapAsOptionalIfNecessary(@Nullable Object value, ResolvableType type) {
|
||||
return (type.resolve(Object.class).equals(Optional.class) ? Optional.ofNullable(value) : value);
|
||||
@@ -228,6 +252,7 @@ public class GraphQlArgumentBinder {
|
||||
if (ctor.getParameterCount() == 0) {
|
||||
target = BeanUtils.instantiateClass(ctor);
|
||||
DataBinder dataBinder = new DataBinder(target);
|
||||
initDataBinder(dataBinder);
|
||||
dataBinder.getBindingResult().setNestedPath(toArgumentPath(segments));
|
||||
dataBinder.setConversionService(getConversionService());
|
||||
dataBinder.bind(initBindValues(rawMap));
|
||||
|
||||
@@ -27,6 +27,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.validation.Validator;
|
||||
@@ -69,6 +70,7 @@ import org.springframework.stereotype.Controller;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.DataBinder;
|
||||
|
||||
/**
|
||||
* {@link RuntimeWiringConfigurer} that detects {@link SchemaMapping @SchemaMapping}
|
||||
@@ -127,6 +129,9 @@ public class AnnotatedControllerConfigurer
|
||||
@Nullable
|
||||
private HandlerMethodInputValidator validator;
|
||||
|
||||
@Nullable
|
||||
private Consumer<DataBinder> dataBinderInitializer;
|
||||
|
||||
|
||||
/**
|
||||
* Add a {@code FormatterRegistrar} to customize the {@link ConversionService}
|
||||
@@ -149,6 +154,15 @@ public class AnnotatedControllerConfigurer
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure an initializer that configures the {@link DataBinder} before the binding process.
|
||||
* @param dataBinderInitializer the data binder initializer
|
||||
* @since 1.0.1
|
||||
*/
|
||||
public void setDataBinderInitializer(@Nullable Consumer<DataBinder> dataBinderInitializer) {
|
||||
this.dataBinderInitializer = dataBinderInitializer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) {
|
||||
this.applicationContext = applicationContext;
|
||||
@@ -176,6 +190,9 @@ public class AnnotatedControllerConfigurer
|
||||
}
|
||||
resolvers.addResolver(new ArgumentMapMethodArgumentResolver());
|
||||
GraphQlArgumentBinder argumentBinder = new GraphQlArgumentBinder(this.conversionService);
|
||||
if (this.dataBinderInitializer != null) {
|
||||
argumentBinder.addDataBinderInitializer(this.dataBinderInitializer);
|
||||
}
|
||||
resolvers.addResolver(new ArgumentMethodArgumentResolver(argumentBinder));
|
||||
resolvers.addResolver(new ArgumentsMethodArgumentResolver(argumentBinder));
|
||||
resolvers.addResolver(new ContextValueMethodArgumentResolver());
|
||||
|
||||
@@ -16,10 +16,14 @@
|
||||
|
||||
package org.springframework.graphql.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -30,6 +34,8 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.graphql.Book;
|
||||
import org.springframework.graphql.data.GraphQlArgumentBinder;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.FieldError;
|
||||
|
||||
@@ -227,6 +233,7 @@ class GraphQlArgumentBinderTests {
|
||||
}
|
||||
|
||||
@Test // gh-410
|
||||
@SuppressWarnings("unchecked")
|
||||
void coercionWithSingletonList() throws Exception {
|
||||
|
||||
Map<String, String> itemMap = new HashMap<>();
|
||||
@@ -250,6 +257,17 @@ class GraphQlArgumentBinderTests {
|
||||
assertThat(items.get(0).getAge()).isEqualTo(37);
|
||||
}
|
||||
|
||||
@Test // gh-392
|
||||
@SuppressWarnings("unchecked")
|
||||
void shouldHaveHigherDefaultAutoGrowLimit() throws Exception {
|
||||
String items = IntStream.range(0, 260).mapToObj(value -> "{\"name\":\"test\"}").collect(Collectors.joining(","));
|
||||
Object result = this.binder.bind(
|
||||
environment("{\"key\":{\"items\":[" + items + "]}}"), "key",
|
||||
ResolvableType.forClass(ItemListHolder.class));
|
||||
assertThat(result).isNotNull().isInstanceOf(ItemListHolder.class);
|
||||
assertThat(((ItemListHolder) result).getItems()).hasSize(260);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private DataFetchingEnvironment environment(String jsonPayload) throws JsonProcessingException {
|
||||
|
||||
Reference in New Issue
Block a user