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}