001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.bind.support; 018 019import java.util.List; 020import java.util.Map; 021import java.util.TreeMap; 022import java.util.stream.Collectors; 023 024import reactor.core.publisher.Mono; 025 026import org.springframework.beans.MutablePropertyValues; 027import org.springframework.http.codec.multipart.FormFieldPart; 028import org.springframework.http.codec.multipart.Part; 029import org.springframework.lang.Nullable; 030import org.springframework.util.CollectionUtils; 031import org.springframework.util.MultiValueMap; 032import org.springframework.web.bind.WebDataBinder; 033import org.springframework.web.server.ServerWebExchange; 034 035/** 036 * Specialized {@link org.springframework.validation.DataBinder} to perform data 037 * binding from URL query params or form data in the request data to Java objects. 038 * 039 * @author Rossen Stoyanchev 040 * @since 5.0 041 */ 042public class WebExchangeDataBinder extends WebDataBinder { 043 044 /** 045 * Create a new instance, with default object name. 046 * @param target the target object to bind onto (or {@code null} if the 047 * binder is just used to convert a plain parameter value) 048 * @see #DEFAULT_OBJECT_NAME 049 */ 050 public WebExchangeDataBinder(@Nullable Object target) { 051 super(target); 052 } 053 054 /** 055 * Create a new instance. 056 * @param target the target object to bind onto (or {@code null} if the 057 * binder is just used to convert a plain parameter value) 058 * @param objectName the name of the target object 059 */ 060 public WebExchangeDataBinder(@Nullable Object target, String objectName) { 061 super(target, objectName); 062 } 063 064 065 /** 066 * Bind query params, form data, and or multipart form data to the binder target. 067 * @param exchange the current exchange 068 * @return a {@code Mono<Void>} when binding is complete 069 */ 070 public Mono<Void> bind(ServerWebExchange exchange) { 071 return getValuesToBind(exchange) 072 .doOnNext(values -> doBind(new MutablePropertyValues(values))) 073 .then(); 074 } 075 076 /** 077 * Protected method to obtain the values for data binding. By default this 078 * method delegates to {@link #extractValuesToBind(ServerWebExchange)}. 079 */ 080 protected Mono<Map<String, Object>> getValuesToBind(ServerWebExchange exchange) { 081 return extractValuesToBind(exchange); 082 } 083 084 085 /** 086 * Combine query params and form data for multipart form data from the body 087 * of the request into a {@code Map<String, Object>} of values to use for 088 * data binding purposes. 089 * @param exchange the current exchange 090 * @return a {@code Mono} with the values to bind 091 * @see org.springframework.http.server.reactive.ServerHttpRequest#getQueryParams() 092 * @see ServerWebExchange#getFormData() 093 * @see ServerWebExchange#getMultipartData() 094 */ 095 public static Mono<Map<String, Object>> extractValuesToBind(ServerWebExchange exchange) { 096 MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams(); 097 Mono<MultiValueMap<String, String>> formData = exchange.getFormData(); 098 Mono<MultiValueMap<String, Part>> multipartData = exchange.getMultipartData(); 099 100 return Mono.zip(Mono.just(queryParams), formData, multipartData) 101 .map(tuple -> { 102 Map<String, Object> result = new TreeMap<>(); 103 tuple.getT1().forEach((key, values) -> addBindValue(result, key, values)); 104 tuple.getT2().forEach((key, values) -> addBindValue(result, key, values)); 105 tuple.getT3().forEach((key, values) -> addBindValue(result, key, values)); 106 return result; 107 }); 108 } 109 110 private static void addBindValue(Map<String, Object> params, String key, List<?> values) { 111 if (!CollectionUtils.isEmpty(values)) { 112 values = values.stream() 113 .map(value -> value instanceof FormFieldPart ? ((FormFieldPart) value).value() : value) 114 .collect(Collectors.toList()); 115 params.put(key, values.size() == 1 ? values.get(0) : values); 116 } 117 } 118 119}