From f0f1979fc534ff6d742c0ab62fce84fd6aa3e7f1 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 24 Oct 2018 20:44:58 +0200 Subject: [PATCH] Support for @RequestParam Map declared with MultipartFile/Part values Issue: SPR-17405 --- ...RequestParamMapMethodArgumentResolver.java | 98 +++++++++--- .../support/MultipartResolutionDelegate.java | 38 ++--- ...stParamMapMethodArgumentResolverTests.java | 150 ++++++++++++++---- src/docs/asciidoc/web/webmvc.adoc | 55 ++++--- 4 files changed, 246 insertions(+), 95 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMapMethodArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMapMethodArgumentResolver.java index 8e9c482c01d..db4722940a8 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMapMethodArgumentResolver.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMapMethodArgumentResolver.java @@ -16,10 +16,14 @@ package org.springframework.web.method.annotation; +import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.Part; import org.springframework.core.MethodParameter; +import org.springframework.core.ResolvableType; import org.springframework.lang.Nullable; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -29,22 +33,29 @@ import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.MultipartRequest; +import org.springframework.web.multipart.support.MultipartResolutionDelegate; /** * Resolves {@link Map} method arguments annotated with an @{@link RequestParam} * where the annotation does not specify a request parameter name. - * See {@link RequestParamMethodArgumentResolver} for resolving {@link Map} - * method arguments with a request parameter name. * - *

The created {@link Map} contains all request parameter name/value pairs. - * If the method parameter type is {@link MultiValueMap} instead, the created - * map contains all request parameters and all there values for cases where - * request parameters have multiple values. + *

The created {@link Map} contains all request parameter name/value pairs, + * or all multipart files for a given parameter name if specifically declared + * with {@link MultipartFile} as the value type. If the method parameter type is + * {@link MultiValueMap} instead, the created map contains all request parameters + * and all their values for cases where request parameters have multiple values + * (or multiple multipart files of the same name). * * @author Arjen Poutsma * @author Rossen Stoyanchev + * @author Juergen Hoeller * @since 3.1 * @see RequestParamMethodArgumentResolver + * @see HttpServletRequest#getParameterMap() + * @see MultipartRequest#getMultiFileMap() + * @see MultipartRequest#getFileMap() */ public class RequestParamMapMethodArgumentResolver implements HandlerMethodArgumentResolver { @@ -59,26 +70,71 @@ public class RequestParamMapMethodArgumentResolver implements HandlerMethodArgum public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { - Class paramType = parameter.getParameterType(); + ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter); - Map parameterMap = webRequest.getParameterMap(); - if (MultiValueMap.class.isAssignableFrom(paramType)) { - MultiValueMap result = new LinkedMultiValueMap<>(parameterMap.size()); - parameterMap.forEach((key, values) -> { - for (String value : values) { - result.add(key, value); + if (MultiValueMap.class.isAssignableFrom(parameter.getParameterType())) { + // MultiValueMap + Class valueType = resolvableType.as(MultiValueMap.class).getGeneric(1).resolve(); + if (valueType == MultipartFile.class) { + MultipartRequest multipartRequest = MultipartResolutionDelegate.resolveMultipartRequest(webRequest); + return (multipartRequest != null ? multipartRequest.getMultiFileMap() : new LinkedMultiValueMap<>(0)); + } + else if (valueType == Part.class) { + HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class); + if (servletRequest != null && MultipartResolutionDelegate.isMultipartRequest(servletRequest)) { + Collection parts = servletRequest.getParts(); + LinkedMultiValueMap result = new LinkedMultiValueMap<>(parts.size()); + for (Part part : parts) { + result.add(part.getName(), part); + } + return result; } - }); - return result; + return new LinkedMultiValueMap<>(0); + } + else { + Map parameterMap = webRequest.getParameterMap(); + MultiValueMap result = new LinkedMultiValueMap<>(parameterMap.size()); + parameterMap.forEach((key, values) -> { + for (String value : values) { + result.add(key, value); + } + }); + return result; + } } + else { - Map result = new LinkedHashMap<>(parameterMap.size()); - parameterMap.forEach((key, values) -> { - if (values.length > 0) { - result.put(key, values[0]); + // Regular Map + Class valueType = resolvableType.asMap().getGeneric(1).resolve(); + if (valueType == MultipartFile.class) { + MultipartRequest multipartRequest = MultipartResolutionDelegate.resolveMultipartRequest(webRequest); + return (multipartRequest != null ? multipartRequest.getFileMap() : new LinkedHashMap<>(0)); + } + else if (valueType == Part.class) { + HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class); + if (servletRequest != null && MultipartResolutionDelegate.isMultipartRequest(servletRequest)) { + Collection parts = servletRequest.getParts(); + LinkedHashMap result = new LinkedHashMap<>(parts.size()); + for (Part part : parts) { + if (!result.containsKey(part.getName())) { + result.put(part.getName(), part); + } + } + return result; } - }); - return result; + return new LinkedHashMap<>(0); + } + else { + Map parameterMap = webRequest.getParameterMap(); + Map result = new LinkedHashMap<>(parameterMap.size()); + parameterMap.forEach((key, values) -> { + if (values.length > 0) { + result.put(key, values[0]); + } + }); + return result; + } } } + } diff --git a/spring-web/src/main/java/org/springframework/web/multipart/support/MultipartResolutionDelegate.java b/spring-web/src/main/java/org/springframework/web/multipart/support/MultipartResolutionDelegate.java index 9a32f1881fc..01b5481cc84 100644 --- a/spring-web/src/main/java/org/springframework/web/multipart/support/MultipartResolutionDelegate.java +++ b/spring-web/src/main/java/org/springframework/web/multipart/support/MultipartResolutionDelegate.java @@ -25,8 +25,10 @@ import javax.servlet.http.Part; import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; import org.springframework.lang.Nullable; +import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartHttpServletRequest; +import org.springframework.web.multipart.MultipartRequest; import org.springframework.web.util.WebUtils; /** @@ -44,6 +46,19 @@ public abstract class MultipartResolutionDelegate { public static final Object UNRESOLVABLE = new Object(); + @Nullable + public static MultipartRequest resolveMultipartRequest(NativeWebRequest webRequest) { + MultipartRequest multipartRequest = webRequest.getNativeRequest(MultipartRequest.class); + if (multipartRequest != null) { + return multipartRequest; + } + HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class); + if (servletRequest != null && isMultipartContent(servletRequest)) { + return new StandardMultipartHttpServletRequest(servletRequest); + } + return null; + } + public static boolean isMultipartRequest(HttpServletRequest request) { return (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null || isMultipartContent(request)); @@ -103,13 +118,13 @@ public abstract class MultipartResolutionDelegate { } } else if (Part.class == parameter.getNestedParameterType()) { - return (isMultipart ? resolvePart(request, name) : null); + return (isMultipart ? request.getPart(name): null); } else if (isPartCollection(parameter)) { return (isMultipart ? resolvePartList(request, name) : null); } else if (isPartArray(parameter)) { - return (isMultipart ? resolvePartArray(request, name) : null); + return (isMultipart ? resolvePartList(request, name).toArray(new Part[0]) : null); } else { return UNRESOLVABLE; @@ -144,12 +159,8 @@ public abstract class MultipartResolutionDelegate { return null; } - private static Part resolvePart(HttpServletRequest servletRequest, String name) throws Exception { - return servletRequest.getPart(name); - } - - private static List resolvePartList(HttpServletRequest servletRequest, String name) throws Exception { - Collection parts = servletRequest.getParts(); + private static List resolvePartList(HttpServletRequest request, String name) throws Exception { + Collection parts = request.getParts(); List result = new ArrayList<>(parts.size()); for (Part part : parts) { if (part.getName().equals(name)) { @@ -159,15 +170,4 @@ public abstract class MultipartResolutionDelegate { return result; } - private static Part[] resolvePartArray(HttpServletRequest servletRequest, String name) throws Exception { - Collection parts = servletRequest.getParts(); - List result = new ArrayList<>(parts.size()); - for (Part part : parts) { - if (part.getName().equals(name)) { - result.add(part); - } - } - return result.toArray(new Part[0]); - } - } diff --git a/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMapMethodArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMapMethodArgumentResolverTests.java index 33b677af194..12b457d1ca8 100644 --- a/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMapMethodArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMapMethodArgumentResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,74 +18,68 @@ package org.springframework.web.method.annotation; import java.util.Collections; import java.util.Map; +import javax.servlet.http.Part; -import org.junit.Before; import org.junit.Test; import org.springframework.core.MethodParameter; import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.mock.web.test.MockHttpServletResponse; +import org.springframework.mock.web.test.MockMultipartFile; +import org.springframework.mock.web.test.MockMultipartHttpServletRequest; +import org.springframework.mock.web.test.MockPart; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.method.ResolvableMethod; +import org.springframework.web.multipart.MultipartFile; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.springframework.web.method.MvcAnnotationPredicates.requestParam; +import static org.junit.Assert.*; +import static org.springframework.web.method.MvcAnnotationPredicates.*; /** * Test fixture with {@link RequestParamMapMethodArgumentResolver}. * * @author Arjen Poutsma * @author Rossen Stoyanchev + * @author Juergen Hoeller */ public class RequestParamMapMethodArgumentResolverTests { - private RequestParamMapMethodArgumentResolver resolver; + private RequestParamMapMethodArgumentResolver resolver = new RequestParamMapMethodArgumentResolver(); - private NativeWebRequest webRequest; + private MockHttpServletRequest request = new MockHttpServletRequest(); - private MockHttpServletRequest request; + private NativeWebRequest webRequest = new ServletWebRequest(request, new MockHttpServletResponse()); private ResolvableMethod testMethod = ResolvableMethod.on(getClass()).named("handle").build(); - @Before - public void setUp() throws Exception { - resolver = new RequestParamMapMethodArgumentResolver(); - - request = new MockHttpServletRequest(); - webRequest = new ServletWebRequest(request, new MockHttpServletResponse()); - } - - @Test public void supportsParameter() { - MethodParameter param = this.testMethod.annot(requestParam().noName()).arg(Map.class); + MethodParameter param = this.testMethod.annot(requestParam().noName()).arg(Map.class, String.class, String.class); assertTrue(resolver.supportsParameter(param)); - param = this.testMethod.annotPresent(RequestParam.class).arg(MultiValueMap.class); + param = this.testMethod.annotPresent(RequestParam.class).arg(MultiValueMap.class, String.class, String.class); assertTrue(resolver.supportsParameter(param)); - param = this.testMethod.annot(requestParam().name("name")).arg(Map.class); + param = this.testMethod.annot(requestParam().name("name")).arg(Map.class, String.class, String.class); assertFalse(resolver.supportsParameter(param)); - param = this.testMethod.annotNotPresent(RequestParam.class).arg(Map.class); + param = this.testMethod.annotNotPresent(RequestParam.class).arg(Map.class, String.class, String.class); assertFalse(resolver.supportsParameter(param)); } @Test - public void resolveMapArgument() throws Exception { + public void resolveMapOfString() throws Exception { String name = "foo"; String value = "bar"; request.addParameter(name, value); Map expected = Collections.singletonMap(name, value); - MethodParameter param = this.testMethod.annot(requestParam().noName()).arg(Map.class); + MethodParameter param = this.testMethod.annot(requestParam().noName()).arg(Map.class, String.class, String.class); Object result = resolver.resolveArgument(param, null, webRequest, null); assertTrue(result instanceof Map); @@ -93,7 +87,7 @@ public class RequestParamMapMethodArgumentResolverTests { } @Test - public void resolveMultiValueMapArgument() throws Exception { + public void resolveMultiValueMapOfString() throws Exception { String name = "foo"; String value1 = "bar"; String value2 = "baz"; @@ -103,19 +97,115 @@ public class RequestParamMapMethodArgumentResolverTests { expected.add(name, value1); expected.add(name, value2); - MethodParameter param = this.testMethod.annotPresent(RequestParam.class).arg(MultiValueMap.class); + MethodParameter param = this.testMethod.annotPresent(RequestParam.class).arg(MultiValueMap.class, String.class, String.class); Object result = resolver.resolveArgument(param, null, webRequest, null); assertTrue(result instanceof MultiValueMap); assertEquals("Invalid result", expected, result); } + @Test + @SuppressWarnings("unchecked") + public void resolveMapOfMultipartFile() throws Exception { + MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest(); + MultipartFile expected1 = new MockMultipartFile("mfile", "Hello World".getBytes()); + MultipartFile expected2 = new MockMultipartFile("other", "Hello World 3".getBytes()); + request.addFile(expected1); + request.addFile(expected2); + webRequest = new ServletWebRequest(request); + + MethodParameter param = this.testMethod.annot(requestParam().noName()).arg(Map.class, String.class, MultipartFile.class); + Object result = resolver.resolveArgument(param, null, webRequest, null); + + assertTrue(result instanceof Map); + Map resultMap = (Map) result; + assertEquals(2, resultMap.size()); + assertEquals(expected1, resultMap.get("mfile")); + assertEquals(expected2, resultMap.get("other")); + } + + @Test + @SuppressWarnings("unchecked") + public void resolveMultiValueMapOfMultipartFile() throws Exception { + MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest(); + MultipartFile expected1 = new MockMultipartFile("mfilelist", "Hello World 1".getBytes()); + MultipartFile expected2 = new MockMultipartFile("mfilelist", "Hello World 2".getBytes()); + MultipartFile expected3 = new MockMultipartFile("other", "Hello World 3".getBytes()); + request.addFile(expected1); + request.addFile(expected2); + request.addFile(expected3); + webRequest = new ServletWebRequest(request); + + MethodParameter param = this.testMethod.annot(requestParam().noName()).arg(MultiValueMap.class, String.class, MultipartFile.class); + Object result = resolver.resolveArgument(param, null, webRequest, null); + + assertTrue(result instanceof MultiValueMap); + MultiValueMap resultMap = (MultiValueMap) result; + assertEquals(2, resultMap.size()); + assertEquals(2, resultMap.get("mfilelist").size()); + assertEquals(expected1, resultMap.get("mfilelist").get(0)); + assertEquals(expected2, resultMap.get("mfilelist").get(1)); + assertEquals(1, resultMap.get("other").size()); + assertEquals(expected3, resultMap.get("other").get(0)); + } + + @Test + @SuppressWarnings("unchecked") + public void resolveMapOfPart() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setContentType("multipart/form-data"); + Part expected1 = new MockPart("mfile", "Hello World".getBytes()); + Part expected2 = new MockPart("other", "Hello World 3".getBytes()); + request.addPart(expected1); + request.addPart(expected2); + webRequest = new ServletWebRequest(request); + + MethodParameter param = this.testMethod.annot(requestParam().noName()).arg(Map.class, String.class, Part.class); + Object result = resolver.resolveArgument(param, null, webRequest, null); + + assertTrue(result instanceof Map); + Map resultMap = (Map) result; + assertEquals(2, resultMap.size()); + assertEquals(expected1, resultMap.get("mfile")); + assertEquals(expected2, resultMap.get("other")); + } + + @Test + @SuppressWarnings("unchecked") + public void resolveMultiValueMapOfPart() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setContentType("multipart/form-data"); + Part expected1 = new MockPart("mfilelist", "Hello World 1".getBytes()); + Part expected2 = new MockPart("mfilelist", "Hello World 2".getBytes()); + Part expected3 = new MockPart("other", "Hello World 3".getBytes()); + request.addPart(expected1); + request.addPart(expected2); + request.addPart(expected3); + webRequest = new ServletWebRequest(request); + + MethodParameter param = this.testMethod.annot(requestParam().noName()).arg(MultiValueMap.class, String.class, Part.class); + Object result = resolver.resolveArgument(param, null, webRequest, null); + + assertTrue(result instanceof MultiValueMap); + MultiValueMap resultMap = (MultiValueMap) result; + assertEquals(2, resultMap.size()); + assertEquals(2, resultMap.get("mfilelist").size()); + assertEquals(expected1, resultMap.get("mfilelist").get(0)); + assertEquals(expected2, resultMap.get("mfilelist").get(1)); + assertEquals(1, resultMap.get("other").size()); + assertEquals(expected3, resultMap.get("other").get(0)); + } + public void handle( - @RequestParam Map param1, - @RequestParam MultiValueMap param2, - @RequestParam("name") Map param3, - Map param4) { + @RequestParam Map param1, + @RequestParam MultiValueMap param2, + @RequestParam Map param3, + @RequestParam MultiValueMap param4, + @RequestParam Map param5, + @RequestParam MultiValueMap param6, + @RequestParam("name") Map param7, + Map param8) { } } diff --git a/src/docs/asciidoc/web/webmvc.adoc b/src/docs/asciidoc/web/webmvc.adoc index 9d6f5026b4f..07da1850d41 100644 --- a/src/docs/asciidoc/web/webmvc.adoc +++ b/src/docs/asciidoc/web/webmvc.adoc @@ -1790,10 +1790,11 @@ and others) and is equivalent to `required=false`. | For access to name-value pairs in URI path segments. See <>. | `@RequestParam` -| For access to the Servlet request parameters. Parameter values are converted to the declared - method argument type. See <>. +| For access to the Servlet request parameters, including multipart files. Parameter values + are converted to the declared method argument type. See <> as well + as <>. - Note that use of `@RequestParam` is optional (for example, to set its attributes). + Note that use of `@RequestParam` is optional for simple parameter values. See "`Any other argument`", at the end of this table. | `@RequestHeader` @@ -1809,12 +1810,12 @@ and others) and is equivalent to `required=false`. argument type by using `HttpMessageConverter` implementations. See <>. | `HttpEntity` -| For access to request headers and body. The body is converted with `HttpMessageConverter` implementations. +| For access to request headers and body. The body is converted with an `HttpMessageConverter`. See <>. | `@RequestPart` -| For access to a part in a `multipart/form-data` request. - See <>. +| For access to a part in a `multipart/form-data` request, converting the part's body + with an `HttpMessageConverter`. See <>. | `java.util.Map`, `org.springframework.ui.Model`, `org.springframework.ui.ModelMap` | For access to the model that is used in HTML controllers and exposed to templates as @@ -2073,8 +2074,8 @@ To get all matrix variables, you can use a `MultiValueMap`, as the following exa ---- ==== -Note that you need to enable the use of matrix variables. In the MVC Java configuration, you need -to set a `UrlPathHelper` with `removeSemicolonContent=false` through +Note that you need to enable the use of matrix variables. In the MVC Java configuration, +you need to set a `UrlPathHelper` with `removeSemicolonContent=false` through <>. In the MVC XML namespace, you can set ``. @@ -2084,8 +2085,8 @@ to set a `UrlPathHelper` with `removeSemicolonContent=false` through ==== `@RequestParam` [.small]#<># -You can use the `@RequestParam` annotation to bind Servlet request parameters (that is, query -parameters or form data) to a method argument in a controller. +You can use the `@RequestParam` annotation to bind Servlet request parameters (that is, +query parameters or form data) to a method argument in a controller. The following example shows how to do so: @@ -2114,17 +2115,20 @@ The following example shows how to do so: ==== By default, method parameters that use this annotation are required, but you can specify that -a method parameter is optional by setting the `@RequestParam` annotation's `required` flag to `false` -or by declaring the argument with an `java.util.Optional` wrapper. +a method parameter is optional by setting the `@RequestParam` annotation's `required` flag to +`false` or by declaring the argument with an `java.util.Optional` wrapper. Type conversion is automatically applied if the target method parameter type is not `String`. See <>. +Declaring the argument type as an array or list allows for resolving multiple parameter +values for the same parameter name. + When an `@RequestParam` annotation is declared as a `Map` or -`MultiValueMap` argument, the map is populated with all request -parameters. +`MultiValueMap`, without a parameter name specified in the annotation, +then the map is populated with the request parameter values for each given parameter name. -Note that use of `@RequestParam` is optional, (for example, to set its attributes). +Note that use of `@RequestParam` is optional (for example, to set its attributes). By default, any argument that is a simple value type (as determined by {api-spring-framework}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]) and is not resolved by any other argument resolver, is treated as if it were annotated @@ -2534,8 +2538,8 @@ after the redirect, attributes from the "`input`" `FlashMap` are automatically a .Matching requests to flash attributes **** -The concept of flash attributes exists in many other web frameworks and has proven to sometimes be -exposed to concurrency issues. This is because, by definition, flash attributes +The concept of flash attributes exists in many other web frameworks and has proven to sometimes +be exposed to concurrency issues. This is because, by definition, flash attributes are to be stored until the next request. However the very "`next`" request may not be the intended recipient but another asynchronous request (for example, polling or resource requests), in which case the flash attributes are removed too early. @@ -2577,16 +2581,21 @@ public class FileUploadController { // store the bytes somewhere return "redirect:uploadSuccess"; } - return "redirect:uploadFailure"; } - } ---- ==== -NOTE: When you use Servlet 3.0 multipart parsing, you can also use `javax.servlet.http.Part`, -instead of Spring's `MultipartFile`, as a method argument +Declaring the argument type as a `List` allows for resolving multiple +files for the same parameter name. + +When the `@RequestParam` annotation is declared as a `Map` or +`MultiValueMap`, without a parameter name specified in the annotation, +then the map is populated with the multipart files for each given parameter name. + +NOTE: With Servlet 3.0 multipart parsing, you may also declare `javax.servlet.http.Part` +instead of Spring's `MultipartFile`, as a method argument or collection value type. You can also use multipart content as part of data binding to a <>. For example, the form field @@ -2604,7 +2613,6 @@ class MyForm { private MultipartFile file; // ... - } @Controller @@ -2612,16 +2620,13 @@ public class FileUploadController { @PostMapping("/form") public String handleFormUpload(MyForm form, BindingResult errors) { - if (!form.getFile().isEmpty()) { byte[] bytes = form.getFile().getBytes(); // store the bytes somewhere return "redirect:uploadSuccess"; } - return "redirect:uploadFailure"; } - } ---- ====