|
16 | 16 |
|
17 | 17 | package org.springframework.web.reactive.result.method.annotation;
|
18 | 18 |
|
| 19 | +import java.util.Collections; |
19 | 20 | import java.util.List;
|
20 | 21 |
|
| 22 | +import reactor.core.publisher.Flux; |
21 | 23 | import reactor.core.publisher.Mono;
|
22 | 24 |
|
23 | 25 | import org.springframework.core.MethodParameter;
|
24 | 26 | import org.springframework.core.ReactiveAdapter;
|
25 | 27 | import org.springframework.core.ReactiveAdapterRegistry;
|
| 28 | +import org.springframework.core.io.buffer.DataBuffer; |
| 29 | +import org.springframework.http.HttpHeaders; |
| 30 | +import org.springframework.http.codec.HttpMessageReader; |
26 | 31 | import org.springframework.http.codec.multipart.Part;
|
| 32 | +import org.springframework.http.server.reactive.ServerHttpRequest; |
| 33 | +import org.springframework.http.server.reactive.ServerHttpRequestDecorator; |
27 | 34 | import org.springframework.util.CollectionUtils;
|
28 | 35 | import org.springframework.web.bind.annotation.RequestPart;
|
29 |
| -import org.springframework.web.bind.annotation.ValueConstants; |
| 36 | +import org.springframework.web.reactive.BindingContext; |
30 | 37 | import org.springframework.web.server.ServerWebExchange;
|
31 | 38 | import org.springframework.web.server.ServerWebInputException;
|
32 | 39 |
|
33 | 40 | /**
|
34 |
| - * Resolver for method arguments annotated with @{@link RequestPart}. |
| 41 | + * Resolver for {@code @RequestPart} arguments where the named part is decoded |
| 42 | + * much like an {@code @RequestBody} argument but based on the content of an |
| 43 | + * individual part instead. The arguments may be wrapped with a reactive type |
| 44 | + * for a single value (e.g. Reactor {@code Mono}, RxJava {@code Single}). |
| 45 | + * |
| 46 | + * <p>This resolver also supports arguments of type {@link Part} which may be |
| 47 | + * wrapped with are reactive type for a single or multiple values. |
35 | 48 | *
|
36 |
| - * @author Sebastien Deleuze |
37 | 49 | * @author Rossen Stoyanchev
|
38 | 50 | * @since 5.0
|
39 | 51 | */
|
40 |
| -public class RequestPartMethodArgumentResolver extends AbstractNamedValueArgumentResolver { |
41 |
| - |
42 |
| - /** |
43 |
| - * Class constructor with a default resolution mode flag. |
44 |
| - * @param registry for checking reactive type wrappers |
45 |
| - */ |
46 |
| - public RequestPartMethodArgumentResolver(ReactiveAdapterRegistry registry) { |
47 |
| - super(null, registry); |
| 52 | +public class RequestPartMethodArgumentResolver extends AbstractMessageReaderArgumentResolver { |
| 53 | + |
| 54 | + |
| 55 | + public RequestPartMethodArgumentResolver(List<HttpMessageReader<?>> readers, |
| 56 | + ReactiveAdapterRegistry registry) { |
| 57 | + |
| 58 | + super(readers, registry); |
48 | 59 | }
|
49 | 60 |
|
50 | 61 |
|
51 | 62 | @Override
|
52 | 63 | public boolean supportsParameter(MethodParameter parameter) {
|
53 |
| - return parameter.hasParameterAnnotation(RequestPart.class); |
| 64 | + return parameter.hasParameterAnnotation(RequestPart.class) || |
| 65 | + checkParameterType(parameter, Part.class::isAssignableFrom); |
54 | 66 | }
|
55 | 67 |
|
56 | 68 |
|
57 | 69 | @Override
|
58 |
| - protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) { |
59 |
| - RequestPart ann = parameter.getParameterAnnotation(RequestPart.class); |
60 |
| - return (ann != null ? new RequestPartNamedValueInfo(ann) : new RequestPartNamedValueInfo()); |
| 70 | + public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext, |
| 71 | + ServerWebExchange exchange) { |
| 72 | + |
| 73 | + RequestPart requestPart = parameter.getParameterAnnotation(RequestPart.class); |
| 74 | + boolean isRequired = requestPart == null || requestPart.required(); |
| 75 | + String name = getPartName(parameter, requestPart); |
| 76 | + |
| 77 | + Flux<Part> partFlux = getPartValues(name, exchange); |
| 78 | + if (isRequired) { |
| 79 | + partFlux = partFlux.switchIfEmpty(Flux.error(getMissingPartException(name, parameter))); |
| 80 | + } |
| 81 | + |
| 82 | + ReactiveAdapter adapter = getAdapterRegistry().getAdapter(parameter.getParameterType()); |
| 83 | + MethodParameter elementType = adapter != null ? parameter.nested() : parameter; |
| 84 | + |
| 85 | + if (Part.class.isAssignableFrom(elementType.getNestedParameterType())) { |
| 86 | + if (adapter != null) { |
| 87 | + partFlux = adapter.isMultiValue() ? partFlux : partFlux.take(1); |
| 88 | + return Mono.just(adapter.fromPublisher(partFlux)); |
| 89 | + } |
| 90 | + else { |
| 91 | + return partFlux.next().cast(Object.class); |
| 92 | + } |
| 93 | + } |
| 94 | + |
| 95 | + return partFlux.next().flatMap(part -> { |
| 96 | + ServerHttpRequest partRequest = new PartServerHttpRequest(exchange.getRequest(), part); |
| 97 | + ServerWebExchange partExchange = exchange.mutate().request(partRequest).build(); |
| 98 | + return readBody(parameter, isRequired, bindingContext, partExchange); |
| 99 | + }); |
61 | 100 | }
|
62 | 101 |
|
63 |
| - @Override |
64 |
| - protected Mono<Object> resolveName(String name, MethodParameter param, ServerWebExchange exchange) { |
| 102 | + private String getPartName(MethodParameter methodParam, RequestPart requestPart) { |
| 103 | + String partName = (requestPart != null ? requestPart.name() : ""); |
| 104 | + if (partName.isEmpty()) { |
| 105 | + partName = methodParam.getParameterName(); |
| 106 | + if (partName == null) { |
| 107 | + throw new IllegalArgumentException("Request part name for argument type [" + |
| 108 | + methodParam.getNestedParameterType().getName() + |
| 109 | + "] not specified, and parameter name information not found in class file either."); |
| 110 | + } |
| 111 | + } |
| 112 | + return partName; |
| 113 | + } |
65 | 114 |
|
66 |
| - Mono<Object> partsMono = exchange.getMultipartData() |
| 115 | + private Flux<Part> getPartValues(String name, ServerWebExchange exchange) { |
| 116 | + return exchange.getMultipartData() |
67 | 117 | .filter(map -> !CollectionUtils.isEmpty(map.get(name)))
|
68 |
| - .map(map -> { |
69 |
| - List<Part> parts = map.get(name); |
70 |
| - return parts.size() == 1 ? parts.get(0) : parts; |
71 |
| - }); |
72 |
| - |
73 |
| - ReactiveAdapter adapter = getAdapterRegistry().getAdapter(param.getParameterType()); |
74 |
| - return (adapter != null ? Mono.just(adapter.fromPublisher(partsMono)) : partsMono); |
| 118 | + .flatMapIterable(map -> map.getOrDefault(name, Collections.emptyList())); |
75 | 119 | }
|
76 | 120 |
|
77 |
| - @Override |
78 |
| - protected void handleMissingValue(String name, MethodParameter param, ServerWebExchange exchange) { |
79 |
| - String type = param.getNestedParameterType().getSimpleName(); |
80 |
| - String reason = "Required " + type + " parameter '" + name + "' is not present"; |
81 |
| - throw new ServerWebInputException(reason, param); |
| 121 | + private ServerWebInputException getMissingPartException(String name, MethodParameter param) { |
| 122 | + String reason = "Required request part '" + name + "' is not present"; |
| 123 | + return new ServerWebInputException(reason, param); |
82 | 124 | }
|
83 | 125 |
|
84 | 126 |
|
85 |
| - private static class RequestPartNamedValueInfo extends NamedValueInfo { |
| 127 | + private static class PartServerHttpRequest extends ServerHttpRequestDecorator { |
| 128 | + |
| 129 | + private final Part part; |
| 130 | + |
| 131 | + public PartServerHttpRequest(ServerHttpRequest delegate, Part part) { |
| 132 | + super(delegate); |
| 133 | + this.part = part; |
| 134 | + } |
86 | 135 |
|
87 |
| - RequestPartNamedValueInfo() { |
88 |
| - super("", false, ValueConstants.DEFAULT_NONE); |
| 136 | + @Override |
| 137 | + public HttpHeaders getHeaders() { |
| 138 | + return this.part.headers(); |
89 | 139 | }
|
90 | 140 |
|
91 |
| - RequestPartNamedValueInfo(RequestPart annotation) { |
92 |
| - super(annotation.name(), annotation.required(), ValueConstants.DEFAULT_NONE); |
| 141 | + @Override |
| 142 | + public Flux<DataBuffer> getBody() { |
| 143 | + return this.part.content(); |
93 | 144 | }
|
94 | 145 | }
|
95 | 146 |
|
|
0 commit comments