Skip to content

Commit 96f158b

Browse files
committed
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
1 parent 4dd13ed commit 96f158b

File tree

3 files changed

+60
-0
lines changed

3 files changed

+60
-0
lines changed

spring-graphql/src/main/java/org/springframework/graphql/data/GraphQlArgumentBinder.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
package org.springframework.graphql.data;
1818

1919
import java.lang.reflect.Constructor;
20+
import java.util.ArrayList;
2021
import java.util.Collection;
2122
import java.util.Collections;
2223
import java.util.List;
2324
import java.util.Map;
2425
import java.util.Optional;
2526
import java.util.Stack;
27+
import java.util.function.Consumer;
2628

2729
import graphql.schema.DataFetchingEnvironment;
2830

@@ -59,11 +61,18 @@
5961
*/
6062
public class GraphQlArgumentBinder {
6163

64+
/**
65+
* Use a larger {@link DataBinder#DEFAULT_AUTO_GROW_COLLECTION_LIMIT} for GraphQL use cases
66+
*/
67+
private static final int DEFAULT_AUTO_GROW_COLLECTION_LIMIT = 1024;
68+
6269
@Nullable
6370
private final SimpleTypeConverter typeConverter;
6471

6572
private final BindingErrorProcessor bindingErrorProcessor = new DefaultBindingErrorProcessor();
6673

74+
private List<Consumer<DataBinder>> dataBinderInitializers = new ArrayList<>();
75+
6776

6877
public GraphQlArgumentBinder() {
6978
this(null);
@@ -90,6 +99,15 @@ private ConversionService getConversionService() {
9099
return (this.typeConverter != null ? this.typeConverter.getConversionService() : null);
91100
}
92101

102+
/**
103+
* Add a {@link DataBinder} consumer that initializes the binder instance before the binding process.
104+
* @param dataBinderInitializer the data binder initializer
105+
* @since 1.0.1
106+
*/
107+
public void addDataBinderInitializer(Consumer<DataBinder> dataBinderInitializer) {
108+
this.dataBinderInitializers.add(dataBinderInitializer);
109+
}
110+
93111

94112
/**
95113
* Bind a single argument, or the full arguments map, onto an object of the
@@ -122,6 +140,7 @@ public Object bind(
122140
Assert.notNull(targetClass, "Could not determine target type from " + targetType);
123141

124142
DataBinder binder = new DataBinder(null, argumentName != null ? argumentName : "arguments");
143+
initDataBinder(binder);
125144
BindingResult bindingResult = binder.getBindingResult();
126145
Stack<String> segments = new Stack<>();
127146

@@ -159,6 +178,11 @@ public Object bind(
159178
}
160179
}
161180

181+
private void initDataBinder(DataBinder binder) {
182+
binder.setAutoGrowCollectionLimit(DEFAULT_AUTO_GROW_COLLECTION_LIMIT);
183+
this.dataBinderInitializers.forEach(initializer -> initializer.accept(binder));
184+
}
185+
162186
@Nullable
163187
private Object wrapAsOptionalIfNecessary(@Nullable Object value, ResolvableType type) {
164188
return (type.resolve(Object.class).equals(Optional.class) ? Optional.ofNullable(value) : value);
@@ -228,6 +252,7 @@ private Object createValue(
228252
if (ctor.getParameterCount() == 0) {
229253
target = BeanUtils.instantiateClass(ctor);
230254
DataBinder dataBinder = new DataBinder(target);
255+
initDataBinder(dataBinder);
231256
dataBinder.getBindingResult().setNestedPath(toArgumentPath(segments));
232257
dataBinder.setConversionService(getConversionService());
233258
dataBinder.bind(initBindValues(rawMap));

spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/AnnotatedControllerConfigurer.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.Set;
2828
import java.util.concurrent.Callable;
2929
import java.util.concurrent.Executor;
30+
import java.util.function.Consumer;
3031
import java.util.stream.Collectors;
3132

3233
import javax.validation.Validator;
@@ -69,6 +70,7 @@
6970
import org.springframework.util.Assert;
7071
import org.springframework.util.ClassUtils;
7172
import org.springframework.util.StringUtils;
73+
import org.springframework.validation.DataBinder;
7274

7375
/**
7476
* {@link RuntimeWiringConfigurer} that detects {@link SchemaMapping @SchemaMapping}
@@ -127,6 +129,9 @@ public class AnnotatedControllerConfigurer
127129
@Nullable
128130
private HandlerMethodInputValidator validator;
129131

132+
@Nullable
133+
private Consumer<DataBinder> dataBinderInitializer;
134+
130135

131136
/**
132137
* Add a {@code FormatterRegistrar} to customize the {@link ConversionService}
@@ -149,6 +154,15 @@ public void setExecutor(Executor executor) {
149154
this.executor = executor;
150155
}
151156

157+
/**
158+
* Configure an initializer that configures the {@link DataBinder} before the binding process.
159+
* @param dataBinderInitializer the data binder initializer
160+
* @since 1.0.1
161+
*/
162+
public void setDataBinderInitializer(@Nullable Consumer<DataBinder> dataBinderInitializer) {
163+
this.dataBinderInitializer = dataBinderInitializer;
164+
}
165+
152166
@Override
153167
public void setApplicationContext(ApplicationContext applicationContext) {
154168
this.applicationContext = applicationContext;
@@ -176,6 +190,9 @@ private HandlerMethodArgumentResolverComposite initArgumentResolvers() {
176190
}
177191
resolvers.addResolver(new ArgumentMapMethodArgumentResolver());
178192
GraphQlArgumentBinder argumentBinder = new GraphQlArgumentBinder(this.conversionService);
193+
if (this.dataBinderInitializer != null) {
194+
argumentBinder.addDataBinderInitializer(this.dataBinderInitializer);
195+
}
179196
resolvers.addResolver(new ArgumentMethodArgumentResolver(argumentBinder));
180197
resolvers.addResolver(new ArgumentsMethodArgumentResolver(argumentBinder));
181198
resolvers.addResolver(new ContextValueMethodArgumentResolver());

spring-graphql/src/test/java/org/springframework/graphql/data/GraphQlArgumentBinderTests.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,14 @@
1616

1717
package org.springframework.graphql.data;
1818

19+
import java.util.ArrayList;
1920
import java.util.Collections;
2021
import java.util.HashMap;
2122
import java.util.List;
2223
import java.util.Map;
24+
import java.util.stream.Collectors;
25+
import java.util.stream.IntStream;
26+
import java.util.stream.Stream;
2327

2428
import com.fasterxml.jackson.core.JsonProcessingException;
2529
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -30,6 +34,8 @@
3034
import org.springframework.core.ResolvableType;
3135
import org.springframework.graphql.Book;
3236
import org.springframework.graphql.data.GraphQlArgumentBinder;
37+
import org.springframework.util.ReflectionUtils;
38+
import org.springframework.util.StringUtils;
3339
import org.springframework.validation.BindException;
3440
import org.springframework.validation.FieldError;
3541

@@ -227,6 +233,7 @@ void primaryConstructorBindingErrorWithNestedBeanList() {
227233
}
228234

229235
@Test // gh-410
236+
@SuppressWarnings("unchecked")
230237
void coercionWithSingletonList() throws Exception {
231238

232239
Map<String, String> itemMap = new HashMap<>();
@@ -250,6 +257,17 @@ void coercionWithSingletonList() throws Exception {
250257
assertThat(items.get(0).getAge()).isEqualTo(37);
251258
}
252259

260+
@Test // gh-392
261+
@SuppressWarnings("unchecked")
262+
void shouldHaveHigherDefaultAutoGrowLimit() throws Exception {
263+
String items = IntStream.range(0, 260).mapToObj(value -> "{\"name\":\"test\"}").collect(Collectors.joining(","));
264+
Object result = this.binder.bind(
265+
environment("{\"key\":{\"items\":[" + items + "]}}"), "key",
266+
ResolvableType.forClass(ItemListHolder.class));
267+
assertThat(result).isNotNull().isInstanceOf(ItemListHolder.class);
268+
assertThat(((ItemListHolder) result).getItems()).hasSize(260);
269+
}
270+
253271

254272
@SuppressWarnings("unchecked")
255273
private DataFetchingEnvironment environment(String jsonPayload) throws JsonProcessingException {

0 commit comments

Comments
 (0)