001/* 002 * Copyright 2002-2019 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.reactive.result.method.annotation; 018 019import java.util.Collections; 020import java.util.List; 021 022import reactor.core.publisher.Flux; 023import reactor.core.publisher.Mono; 024 025import org.springframework.core.MethodParameter; 026import org.springframework.core.ReactiveAdapter; 027import org.springframework.core.ReactiveAdapterRegistry; 028import org.springframework.core.io.buffer.DataBuffer; 029import org.springframework.http.HttpHeaders; 030import org.springframework.http.codec.HttpMessageReader; 031import org.springframework.http.codec.multipart.Part; 032import org.springframework.http.server.reactive.ServerHttpRequest; 033import org.springframework.http.server.reactive.ServerHttpRequestDecorator; 034import org.springframework.lang.Nullable; 035import org.springframework.util.CollectionUtils; 036import org.springframework.web.bind.annotation.RequestPart; 037import org.springframework.web.reactive.BindingContext; 038import org.springframework.web.server.ServerWebExchange; 039import org.springframework.web.server.ServerWebInputException; 040 041/** 042 * Resolver for {@code @RequestPart} arguments where the named part is decoded 043 * much like an {@code @RequestBody} argument but based on the content of an 044 * individual part instead. The arguments may be wrapped with a reactive type 045 * for a single value (e.g. Reactor {@code Mono}, RxJava {@code Single}). 046 * 047 * <p>This resolver also supports arguments of type {@link Part} which may be 048 * wrapped with are reactive type for a single or multiple values. 049 * 050 * @author Rossen Stoyanchev 051 * @since 5.0 052 */ 053public class RequestPartMethodArgumentResolver extends AbstractMessageReaderArgumentResolver { 054 055 public RequestPartMethodArgumentResolver(List<HttpMessageReader<?>> readers, ReactiveAdapterRegistry registry) { 056 super(readers, registry); 057 } 058 059 060 @Override 061 public boolean supportsParameter(MethodParameter parameter) { 062 return (parameter.hasParameterAnnotation(RequestPart.class) || 063 checkParameterType(parameter, Part.class::isAssignableFrom)); 064 } 065 066 @Override 067 public Mono<Object> resolveArgument( 068 MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange) { 069 070 RequestPart requestPart = parameter.getParameterAnnotation(RequestPart.class); 071 boolean isRequired = (requestPart == null || requestPart.required()); 072 String name = getPartName(parameter, requestPart); 073 074 Flux<Part> parts = exchange.getMultipartData() 075 .flatMapIterable(map -> { 076 List<Part> list = map.get(name); 077 if (CollectionUtils.isEmpty(list)) { 078 if (isRequired) { 079 throw getMissingPartException(name, parameter); 080 } 081 return Collections.emptyList(); 082 } 083 return list; 084 }); 085 086 if (Part.class.isAssignableFrom(parameter.getParameterType())) { 087 return parts.next().cast(Object.class); 088 } 089 090 if (List.class.isAssignableFrom(parameter.getParameterType())) { 091 MethodParameter elementType = parameter.nested(); 092 if (Part.class.isAssignableFrom(elementType.getNestedParameterType())) { 093 return parts.collectList().cast(Object.class); 094 } 095 else { 096 return decodePartValues(parts, elementType, bindingContext, exchange, isRequired) 097 .collectList().cast(Object.class); 098 } 099 } 100 101 ReactiveAdapter adapter = getAdapterRegistry().getAdapter(parameter.getParameterType()); 102 if (adapter != null) { 103 MethodParameter elementType = parameter.nested(); 104 return Mono.just(adapter.fromPublisher( 105 Part.class.isAssignableFrom(elementType.getNestedParameterType()) ? 106 parts : decodePartValues(parts, elementType, bindingContext, exchange, isRequired))); 107 } 108 109 return decodePartValues(parts, parameter, bindingContext, exchange, isRequired) 110 .next().cast(Object.class); 111 } 112 113 private String getPartName(MethodParameter methodParam, @Nullable RequestPart requestPart) { 114 String partName = (requestPart != null ? requestPart.name() : ""); 115 if (partName.isEmpty()) { 116 partName = methodParam.getParameterName(); 117 if (partName == null) { 118 throw new IllegalArgumentException("Request part name for argument type [" + 119 methodParam.getNestedParameterType().getName() + 120 "] not specified, and parameter name information not found in class file either."); 121 } 122 } 123 return partName; 124 } 125 126 private ServerWebInputException getMissingPartException(String name, MethodParameter param) { 127 String reason = "Required request part '" + name + "' is not present"; 128 return new ServerWebInputException(reason, param); 129 } 130 131 132 private Flux<?> decodePartValues(Flux<Part> parts, MethodParameter elementType, BindingContext bindingContext, 133 ServerWebExchange exchange, boolean isRequired) { 134 135 return parts.flatMap(part -> { 136 ServerHttpRequest partRequest = new PartServerHttpRequest(exchange.getRequest(), part); 137 ServerWebExchange partExchange = exchange.mutate().request(partRequest).build(); 138 if (logger.isDebugEnabled()) { 139 logger.debug(exchange.getLogPrefix() + "Decoding part '" + part.name() + "'"); 140 } 141 return readBody(elementType, isRequired, bindingContext, partExchange); 142 }); 143 } 144 145 146 private static class PartServerHttpRequest extends ServerHttpRequestDecorator { 147 148 private final Part part; 149 150 public PartServerHttpRequest(ServerHttpRequest delegate, Part part) { 151 super(delegate); 152 this.part = part; 153 } 154 155 @Override 156 public HttpHeaders getHeaders() { 157 return this.part.headers(); 158 } 159 160 @Override 161 public Flux<DataBuffer> getBody() { 162 return this.part.content(); 163 } 164 } 165 166}