diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/GraphQlArgumentBinder.java b/spring-graphql/src/main/java/org/springframework/graphql/data/GraphQlArgumentBinder.java index ca306179..16163bc2 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/GraphQlArgumentBinder.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/GraphQlArgumentBinder.java @@ -149,7 +149,7 @@ public class GraphQlArgumentBinder { if (isApproximableCollectionType(rawValue)) { segments.push(argumentName); - return createCollection((Collection) rawValue, targetType, bindingResult, segments); + return bindCollection((Collection) rawValue, targetType, bindingResult, segments); } if (targetClass == Optional.class) { @@ -160,7 +160,7 @@ public class GraphQlArgumentBinder { // From Map if (rawValue instanceof Map) { - Object target = createValue((Map) rawValue, targetType, bindingResult, segments); + Object target = bindMap((Map) rawValue, targetType, bindingResult, segments); return wrapAsOptionalIfNecessary(target, targetType); } @@ -195,7 +195,7 @@ public class GraphQlArgumentBinder { } @SuppressWarnings({"ConstantConditions", "unchecked"}) - private Collection createCollection( + private Collection bindCollection( Collection rawCollection, ResolvableType collectionType, BindingResult bindingResult, Stack segments) { @@ -219,7 +219,7 @@ public class GraphQlArgumentBinder { collection.add((T) rawValue); } else if (rawValue instanceof Map) { - collection.add((T) createValueOrNull((Map) rawValue, elementType, bindingResult, segments)); + collection.add((T) bindMap((Map) rawValue, elementType, bindingResult, segments)); } else { collection.add((T) convertValue(rawValue, elementClass, bindingResult, segments)); @@ -230,134 +230,128 @@ public class GraphQlArgumentBinder { } @Nullable - private Object createValueOrNull( - Map rawMap, ResolvableType targetType, BindingResult result, Stack segments) { + @SuppressWarnings("unchecked") + private Object bindMap( + Map rawMap, ResolvableType targetType, BindingResult bindingResult, + Stack segments) { try { - return createValue(rawMap, targetType, result, segments); + Class targetClass = targetType.resolve(); + Assert.notNull(targetClass, "Unknown target class"); + + if (Map.class.isAssignableFrom(targetClass)) { + ResolvableType valueType = targetType.asMap().getGeneric(1); + Class valueClass = valueType.resolve(); + if (valueClass == null) { + bindingResult.rejectValue(toArgumentPath(segments), "unknownMapValueType", "Unknown Map value type"); + return Collections.emptyMap(); + } + Map map = CollectionFactory.createMap(targetClass, rawMap.size()); + for (Map.Entry entry : rawMap.entrySet()) { + Object rawValue = entry.getValue(); + segments.push("[" + entry.getKey() + "]"); + if (rawValue == null || valueType.isAssignableFrom(rawValue.getClass())) { + map.put(entry.getKey(), entry.getValue()); + } + else if (rawValue instanceof Map) { + map.put(entry.getKey(), bindMap( + (Map) rawValue, valueType, bindingResult, segments)); + } + else { + map.put(entry.getKey(), convertValue(rawValue, valueClass, bindingResult, segments)); + } + segments.pop(); + } + return map; + } + + Object target; + Constructor ctor = BeanUtils.getResolvableConstructor(targetClass); + + // Default constructor + data binding via properties + + 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(toPropertyValues(rawMap)); + + if (dataBinder.getBindingResult().hasErrors()) { + addDataBinderErrors(dataBinder, bindingResult, segments); + throw new BindException(bindingResult); + } + + return target; + } + + // Data class constructor + + if (!segments.isEmpty()) { + segments.push("."); + } + + 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 rawValue = rawMap.get(paramName); + segments.push(paramName); + MethodParameter methodParam = new MethodParameter(ctor, i); + if (rawValue == null && methodParam.isOptional()) { + args[i] = (paramTypes[i] == Optional.class ? Optional.empty() : null); + } + else if (paramTypes[i] == Object.class) { + args[i] = rawValue; + } + else if (isApproximableCollectionType(rawValue)) { + ResolvableType elementType = ResolvableType.forMethodParameter(methodParam); + args[i] = bindCollection((Collection) rawValue, elementType, bindingResult, segments); + } + else if (rawValue instanceof Map) { + boolean isOptional = (paramTypes[i] == Optional.class); + ResolvableType type = ResolvableType.forMethodParameter(methodParam.nestedIfOptional()); + Object value = bindMap((Map) rawValue, type, bindingResult, segments); + args[i] = (isOptional ? Optional.ofNullable(value) : value); + } + else { + args[i] = convertValue(rawValue, paramTypes[i], new TypeDescriptor(methodParam), bindingResult, segments); + } + segments.pop(); + } + + if (segments.size() > 1) { + segments.pop(); + } + + try { + return BeanUtils.instantiateClass(ctor, args); + } + catch (BeanInstantiationException ex) { + // Swallow if we had binding errors, it's as far as we could go + checkBindingResult(bindingResult); + throw ex; + } } catch (BindException ex) { return null; } } - @SuppressWarnings("unchecked") - private Object createValue( - Map rawMap, ResolvableType targetType, BindingResult bindingResult, - Stack segments) throws BindException { - - Class targetClass = targetType.resolve(); - Assert.notNull(targetClass, "Unknown target class"); - - if (Map.class.isAssignableFrom(targetClass)) { - ResolvableType valueType = targetType.asMap().getGeneric(1); - Class valueClass = valueType.resolve(); - if (valueClass == null) { - bindingResult.rejectValue(toArgumentPath(segments), "unknownMapValueType", "Unknown Map value type"); - return Collections.emptyMap(); - } - Map map = CollectionFactory.createMap(targetClass, rawMap.size()); - for (Map.Entry entry : rawMap.entrySet()) { - Object rawValue = entry.getValue(); - segments.push("[" + entry.getKey() + "]"); - if (rawValue == null || valueType.isAssignableFrom(rawValue.getClass())) { - map.put(entry.getKey(), entry.getValue()); - } - else if (rawValue instanceof Map) { - map.put(entry.getKey(), createValueOrNull( - (Map) rawValue, valueType, bindingResult, segments)); - } - else { - map.put(entry.getKey(), convertValue(rawValue, valueClass, bindingResult, segments)); - } - segments.pop(); - } - return map; - } - - Object target; - Constructor ctor = BeanUtils.getResolvableConstructor(targetClass); - - // Default constructor + data binding via properties - - 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)); - - if (dataBinder.getBindingResult().hasErrors()) { - addErrors(dataBinder, bindingResult, segments); - throw new BindException(bindingResult); - } - - return target; - } - - // Data class constructor - - if (!segments.isEmpty()) { - segments.push("."); - } - - 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 rawValue = rawMap.get(paramName); - segments.push(paramName); - MethodParameter methodParam = new MethodParameter(ctor, i); - if (rawValue == null && methodParam.isOptional()) { - args[i] = (paramTypes[i] == Optional.class ? Optional.empty() : null); - } - else if (paramTypes[i] == Object.class) { - args[i] = rawValue; - } - else if (isApproximableCollectionType(rawValue)) { - ResolvableType elementType = ResolvableType.forMethodParameter(methodParam); - args[i] = createCollection((Collection) rawValue, elementType, bindingResult, segments); - } - else if (rawValue instanceof Map) { - boolean isOptional = (paramTypes[i] == Optional.class); - ResolvableType type = ResolvableType.forMethodParameter(methodParam.nestedIfOptional()); - Object value = createValueOrNull((Map) rawValue, type, bindingResult, segments); - args[i] = (isOptional ? Optional.ofNullable(value) : value); - } - else { - args[i] = convertValue(rawValue, paramTypes[i], new TypeDescriptor(methodParam), bindingResult, segments); - } - segments.pop(); - } - - if (segments.size() > 1) { - segments.pop(); - } - - try { - return BeanUtils.instantiateClass(ctor, args); - } - catch (BeanInstantiationException ex) { - // Swallow if we had binding errors, it's as far as we could go - checkBindingResult(bindingResult); - throw ex; - } - } - - private MutablePropertyValues initBindValues(Map rawMap) { + private static MutablePropertyValues toPropertyValues(Map rawMap) { MutablePropertyValues mpvs = new MutablePropertyValues(); Stack segments = new Stack<>(); for (String key : rawMap.keySet()) { - addBindValues(mpvs, key, rawMap.get(key), segments); + addPropertyValue(mpvs, key, rawMap.get(key), segments); } return mpvs; } @SuppressWarnings("unchecked") - private void addBindValues(MutablePropertyValues mpvs, String name, Object value, Stack segments) { + private static void addPropertyValue(MutablePropertyValues mpvs, String name, Object value, Stack segments) { if (value instanceof List) { List items = (List) value; if (items.isEmpty()) { @@ -367,7 +361,7 @@ public class GraphQlArgumentBinder { } else { for (int i = 0; i < items.size(); i++) { - addBindValues(mpvs, name + "[" + i + "]", items.get(i), segments); + addPropertyValue(mpvs, name + "[" + i + "]", items.get(i), segments); } } } @@ -375,7 +369,7 @@ public class GraphQlArgumentBinder { segments.push(name + "."); Map map = (Map) value; for (String key : map.keySet()) { - addBindValues(mpvs, key, map.get(key), segments); + addPropertyValue(mpvs, key, map.get(key), segments); } segments.pop(); } @@ -386,7 +380,7 @@ public class GraphQlArgumentBinder { } } - private String toArgumentPath(Stack path) { + private static String toArgumentPath(Stack path) { StringBuilder sb = new StringBuilder(); path.forEach(sb::append); return sb.toString(); @@ -415,7 +409,7 @@ public class GraphQlArgumentBinder { return null; } - private void addErrors(DataBinder binder, BindingResult bindingResult, Stack segments) { + private static void addDataBinderErrors(DataBinder binder, BindingResult bindingResult, Stack segments) { String path = (!segments.isEmpty() ? toArgumentPath(segments) + "." : ""); binder.getBindingResult().getFieldErrors().forEach(error -> bindingResult.addError( new FieldError(bindingResult.getObjectName(), path + error.getField(),