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.test.web.servlet.request;
018
019import java.io.ByteArrayInputStream;
020import java.io.IOException;
021import java.io.InputStream;
022import java.io.UnsupportedEncodingException;
023import java.net.URI;
024import java.nio.charset.Charset;
025import java.security.Principal;
026import java.util.ArrayList;
027import java.util.Arrays;
028import java.util.LinkedHashMap;
029import java.util.List;
030import java.util.Locale;
031import java.util.Map;
032import java.util.Map.Entry;
033import javax.servlet.ServletContext;
034import javax.servlet.ServletRequest;
035import javax.servlet.http.Cookie;
036
037import org.springframework.beans.Mergeable;
038import org.springframework.beans.factory.NoSuchBeanDefinitionException;
039import org.springframework.http.HttpHeaders;
040import org.springframework.http.HttpInputMessage;
041import org.springframework.http.HttpMethod;
042import org.springframework.http.MediaType;
043import org.springframework.http.converter.FormHttpMessageConverter;
044import org.springframework.mock.web.MockHttpServletRequest;
045import org.springframework.mock.web.MockHttpServletResponse;
046import org.springframework.mock.web.MockHttpSession;
047import org.springframework.test.web.servlet.MockMvc;
048import org.springframework.util.Assert;
049import org.springframework.util.LinkedMultiValueMap;
050import org.springframework.util.MultiValueMap;
051import org.springframework.util.ObjectUtils;
052import org.springframework.util.StringUtils;
053import org.springframework.web.context.WebApplicationContext;
054import org.springframework.web.context.support.WebApplicationContextUtils;
055import org.springframework.web.servlet.DispatcherServlet;
056import org.springframework.web.servlet.FlashMap;
057import org.springframework.web.servlet.FlashMapManager;
058import org.springframework.web.servlet.support.SessionFlashMapManager;
059import org.springframework.web.util.UriComponentsBuilder;
060import org.springframework.web.util.UriUtils;
061import org.springframework.web.util.UrlPathHelper;
062
063/**
064 * Default builder for {@link MockHttpServletRequest} required as input to
065 * perform requests in {@link MockMvc}.
066 *
067 * <p>Application tests will typically access this builder through the static
068 * factory methods in {@link MockMvcRequestBuilders}.
069 *
070 * <p>This class is not open for extension. To apply custom initialization to
071 * the created {@code MockHttpServletRequest}, please use the
072 * {@link #with(RequestPostProcessor)} extension point.
073 *
074 * @author Rossen Stoyanchev
075 * @author Juergen Hoeller
076 * @author Arjen Poutsma
077 * @author Sam Brannen
078 * @author Kamill Sokol
079 * @since 3.2
080 */
081public class MockHttpServletRequestBuilder
082                implements ConfigurableSmartRequestBuilder<MockHttpServletRequestBuilder>, Mergeable {
083
084        private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
085
086
087        private final String method;
088
089        private final URI url;
090
091        private String contextPath = "";
092
093        private String servletPath = "";
094
095        private String pathInfo = "";
096
097        private Boolean secure;
098
099        private Principal principal;
100
101        private MockHttpSession session;
102
103        private String characterEncoding;
104
105        private byte[] content;
106
107        private String contentType;
108
109        private final MultiValueMap<String, Object> headers = new LinkedMultiValueMap<String, Object>();
110
111        private final MultiValueMap<String, String> parameters = new LinkedMultiValueMap<String, String>();
112
113        private final List<Cookie> cookies = new ArrayList<Cookie>();
114
115        private final List<Locale> locales = new ArrayList<Locale>();
116
117        private final Map<String, Object> requestAttributes = new LinkedHashMap<String, Object>();
118
119        private final Map<String, Object> sessionAttributes = new LinkedHashMap<String, Object>();
120
121        private final Map<String, Object> flashAttributes = new LinkedHashMap<String, Object>();
122
123        private final List<RequestPostProcessor> postProcessors = new ArrayList<RequestPostProcessor>();
124
125
126        /**
127         * Package private constructor. To get an instance, use static factory
128         * methods in {@link MockMvcRequestBuilders}.
129         * <p>Although this class cannot be extended, additional ways to initialize
130         * the {@code MockHttpServletRequest} can be plugged in via
131         * {@link #with(RequestPostProcessor)}.
132         * @param httpMethod the HTTP method (GET, POST, etc)
133         * @param url a URL template; the resulting URL will be encoded
134         * @param vars zero or more URI variables
135         */
136        MockHttpServletRequestBuilder(HttpMethod httpMethod, String url, Object... vars) {
137                this(httpMethod.name(), UriComponentsBuilder.fromUriString(url).buildAndExpand(vars).encode().toUri());
138        }
139
140        /**
141         * Alternative to {@link #MockHttpServletRequestBuilder(HttpMethod, String, Object...)}
142         * with a pre-built URI.
143         * @param httpMethod the HTTP method (GET, POST, etc)
144         * @param url the URL
145         * @since 4.0.3
146         */
147        MockHttpServletRequestBuilder(HttpMethod httpMethod, URI url) {
148                this(httpMethod.name(), url);
149        }
150
151        /**
152         * Alternative constructor for custom HTTP methods.
153         * @param httpMethod the HTTP method (GET, POST, etc)
154         * @param url the URL
155         * @since 4.3
156         */
157        MockHttpServletRequestBuilder(String httpMethod, URI url) {
158                Assert.notNull(httpMethod, "'httpMethod' is required");
159                Assert.notNull(url, "'url' is required");
160                this.method = httpMethod;
161                this.url = url;
162        }
163
164
165        /**
166         * Specify the portion of the requestURI that represents the context path.
167         * The context path, if specified, must match to the start of the request URI.
168         * <p>In most cases, tests can be written by omitting the context path from
169         * the requestURI. This is because most applications don't actually depend
170         * on the name under which they're deployed. If specified here, the context
171         * path must start with a "/" and must not end with a "/".
172         * @see javax.servlet.http.HttpServletRequest#getContextPath()
173         */
174        public MockHttpServletRequestBuilder contextPath(String contextPath) {
175                if (StringUtils.hasText(contextPath)) {
176                        Assert.isTrue(contextPath.startsWith("/"), "Context path must start with a '/'");
177                        Assert.isTrue(!contextPath.endsWith("/"), "Context path must not end with a '/'");
178                }
179                this.contextPath = (contextPath != null ? contextPath : "");
180                return this;
181        }
182
183        /**
184         * Specify the portion of the requestURI that represents the path to which
185         * the Servlet is mapped. This is typically a portion of the requestURI
186         * after the context path.
187         * <p>In most cases, tests can be written by omitting the servlet path from
188         * the requestURI. This is because most applications don't actually depend
189         * on the prefix to which a servlet is mapped. For example if a Servlet is
190         * mapped to {@code "/main/*"}, tests can be written with the requestURI
191         * {@code "/accounts/1"} as opposed to {@code "/main/accounts/1"}.
192         * If specified here, the servletPath must start with a "/" and must not
193         * end with a "/".
194         * @see javax.servlet.http.HttpServletRequest#getServletPath()
195         */
196        public MockHttpServletRequestBuilder servletPath(String servletPath) {
197                if (StringUtils.hasText(servletPath)) {
198                        Assert.isTrue(servletPath.startsWith("/"), "Servlet path must start with a '/'");
199                        Assert.isTrue(!servletPath.endsWith("/"), "Servlet path must not end with a '/'");
200                }
201                this.servletPath = (servletPath != null ? servletPath : "");
202                return this;
203        }
204
205        /**
206         * Specify the portion of the requestURI that represents the pathInfo.
207         * <p>If left unspecified (recommended), the pathInfo will be automatically derived
208         * by removing the contextPath and the servletPath from the requestURI and using any
209         * remaining part. If specified here, the pathInfo must start with a "/".
210         * <p>If specified, the pathInfo will be used as-is.
211         * @see javax.servlet.http.HttpServletRequest#getPathInfo()
212         */
213        public MockHttpServletRequestBuilder pathInfo(String pathInfo) {
214                if (StringUtils.hasText(pathInfo)) {
215                        Assert.isTrue(pathInfo.startsWith("/"), "Path info must start with a '/'");
216                }
217                this.pathInfo = pathInfo;
218                return this;
219        }
220
221        /**
222         * Set the secure property of the {@link ServletRequest} indicating use of a
223         * secure channel, such as HTTPS.
224         * @param secure whether the request is using a secure channel
225         */
226        public MockHttpServletRequestBuilder secure(boolean secure){
227                this.secure = secure;
228                return this;
229        }
230
231        /**
232         * Set the character encoding of the request.
233         * @param encoding the character encoding
234         */
235        public MockHttpServletRequestBuilder characterEncoding(String encoding) {
236                this.characterEncoding = encoding;
237                return this;
238        }
239
240        /**
241         * Set the request body.
242         * @param content the body content
243         */
244        public MockHttpServletRequestBuilder content(byte[] content) {
245                this.content = content;
246                return this;
247        }
248
249        /**
250         * Set the request body as a UTF-8 String.
251         * @param content the body content
252         */
253        public MockHttpServletRequestBuilder content(String content) {
254                this.content = content.getBytes(UTF8_CHARSET);
255                return this;
256        }
257
258        /**
259         * Set the 'Content-Type' header of the request.
260         * @param contentType the content type
261         */
262        public MockHttpServletRequestBuilder contentType(MediaType contentType) {
263                Assert.notNull(contentType, "'contentType' must not be null");
264                this.contentType = contentType.toString();
265                return this;
266        }
267
268        /**
269         * Set the 'Content-Type' header of the request.
270         * @param contentType the content type
271         * @since 4.1.2
272         */
273        public MockHttpServletRequestBuilder contentType(String contentType) {
274                this.contentType = MediaType.parseMediaType(contentType).toString();
275                return this;
276        }
277
278        /**
279         * Set the 'Accept' header to the given media type(s).
280         * @param mediaTypes one or more media types
281         */
282        public MockHttpServletRequestBuilder accept(MediaType... mediaTypes) {
283                Assert.notEmpty(mediaTypes, "'mediaTypes' must not be empty");
284                this.headers.set("Accept", MediaType.toString(Arrays.asList(mediaTypes)));
285                return this;
286        }
287
288        /**
289         * Set the 'Accept' header to the given media type(s).
290         * @param mediaTypes one or more media types
291         */
292        public MockHttpServletRequestBuilder accept(String... mediaTypes) {
293                Assert.notEmpty(mediaTypes, "'mediaTypes' must not be empty");
294                List<MediaType> result = new ArrayList<MediaType>(mediaTypes.length);
295                for (String mediaType : mediaTypes) {
296                        result.add(MediaType.parseMediaType(mediaType));
297                }
298                this.headers.set("Accept", MediaType.toString(result));
299                return this;
300        }
301
302        /**
303         * Add a header to the request. Values are always added.
304         * @param name the header name
305         * @param values one or more header values
306         */
307        public MockHttpServletRequestBuilder header(String name, Object... values) {
308                addToMultiValueMap(this.headers, name, values);
309                return this;
310        }
311
312        /**
313         * Add all headers to the request. Values are always added.
314         * @param httpHeaders the headers and values to add
315         */
316        public MockHttpServletRequestBuilder headers(HttpHeaders httpHeaders) {
317                for (String name : httpHeaders.keySet()) {
318                        Object[] values = ObjectUtils.toObjectArray(httpHeaders.get(name).toArray());
319                        addToMultiValueMap(this.headers, name, values);
320                }
321                return this;
322        }
323
324        /**
325         * Add a request parameter to the {@link MockHttpServletRequest}.
326         * <p>If called more than once, new values get added to existing ones.
327         * @param name the parameter name
328         * @param values one or more values
329         */
330        public MockHttpServletRequestBuilder param(String name, String... values) {
331                addToMultiValueMap(this.parameters, name, values);
332                return this;
333        }
334
335        /**
336         * Add a map of request parameters to the {@link MockHttpServletRequest},
337         * for example when testing a form submission.
338         * <p>If called more than once, new values get added to existing ones.
339         * @param params the parameters to add
340         * @since 4.2.4
341         */
342        public MockHttpServletRequestBuilder params(MultiValueMap<String, String> params) {
343                for (String name : params.keySet()) {
344                        for (String value : params.get(name)) {
345                                this.parameters.add(name, value);
346                        }
347                }
348                return this;
349        }
350
351        /**
352         * Add the given cookies to the request. Cookies are always added.
353         * @param cookies the cookies to add
354         */
355        public MockHttpServletRequestBuilder cookie(Cookie... cookies) {
356                Assert.notEmpty(cookies, "'cookies' must not be empty");
357                this.cookies.addAll(Arrays.asList(cookies));
358                return this;
359        }
360
361        /**
362         * Add the specified locales as preferred request locales.
363         * @param locales the locales to add
364         * @since 4.3.6
365         * @see #locale(Locale)
366         */
367        public MockHttpServletRequestBuilder locale(Locale... locales) {
368                Assert.notEmpty(locales, "'locales' must not be empty");
369                this.locales.addAll(Arrays.asList(locales));
370                return this;
371        }
372
373        /**
374         * Set the locale of the request, overriding any previous locales.
375         * @param locale the locale, or {@code null} to reset it
376         * @see #locale(Locale...)
377         */
378        public MockHttpServletRequestBuilder locale(Locale locale) {
379                this.locales.clear();
380                if (locale != null) {
381                        this.locales.add(locale);
382                }
383                return this;
384        }
385
386        /**
387         * Set a request attribute.
388         * @param name the attribute name
389         * @param value the attribute value
390         */
391        public MockHttpServletRequestBuilder requestAttr(String name, Object value) {
392                addToMap(this.requestAttributes, name, value);
393                return this;
394        }
395
396        /**
397         * Set a session attribute.
398         * @param name the session attribute name
399         * @param value the session attribute value
400         */
401        public MockHttpServletRequestBuilder sessionAttr(String name, Object value) {
402                addToMap(this.sessionAttributes, name, value);
403                return this;
404        }
405
406        /**
407         * Set session attributes.
408         * @param sessionAttributes the session attributes
409         */
410        public MockHttpServletRequestBuilder sessionAttrs(Map<String, Object> sessionAttributes) {
411                Assert.notEmpty(sessionAttributes, "'sessionAttributes' must not be empty");
412                for (String name : sessionAttributes.keySet()) {
413                        sessionAttr(name, sessionAttributes.get(name));
414                }
415                return this;
416        }
417
418        /**
419         * Set an "input" flash attribute.
420         * @param name the flash attribute name
421         * @param value the flash attribute value
422         */
423        public MockHttpServletRequestBuilder flashAttr(String name, Object value) {
424                addToMap(this.flashAttributes, name, value);
425                return this;
426        }
427
428        /**
429         * Set flash attributes.
430         * @param flashAttributes the flash attributes
431         */
432        public MockHttpServletRequestBuilder flashAttrs(Map<String, Object> flashAttributes) {
433                Assert.notEmpty(flashAttributes, "'flashAttributes' must not be empty");
434                for (String name : flashAttributes.keySet()) {
435                        flashAttr(name, flashAttributes.get(name));
436                }
437                return this;
438        }
439
440        /**
441         * Set the HTTP session to use, possibly re-used across requests.
442         * <p>Individual attributes provided via {@link #sessionAttr(String, Object)}
443         * override the content of the session provided here.
444         * @param session the HTTP session
445         */
446        public MockHttpServletRequestBuilder session(MockHttpSession session) {
447                Assert.notNull(session, "'session' must not be null");
448                this.session = session;
449                return this;
450        }
451
452        /**
453         * Set the principal of the request.
454         * @param principal the principal
455         */
456        public MockHttpServletRequestBuilder principal(Principal principal) {
457                Assert.notNull(principal, "'principal' must not be null");
458                this.principal = principal;
459                return this;
460        }
461
462        /**
463         * An extension point for further initialization of {@link MockHttpServletRequest}
464         * in ways not built directly into the {@code MockHttpServletRequestBuilder}.
465         * Implementation of this interface can have builder-style methods themselves
466         * and be made accessible through static factory methods.
467         * @param postProcessor a post-processor to add
468         */
469        @Override
470        public MockHttpServletRequestBuilder with(RequestPostProcessor postProcessor) {
471                Assert.notNull(postProcessor, "postProcessor is required");
472                this.postProcessors.add(postProcessor);
473                return this;
474        }
475
476
477        /**
478         * {@inheritDoc}
479         * @return always returns {@code true}.
480         */
481        @Override
482        public boolean isMergeEnabled() {
483                return true;
484        }
485
486        /**
487         * Merges the properties of the "parent" RequestBuilder accepting values
488         * only if not already set in "this" instance.
489         * @param parent the parent {@code RequestBuilder} to inherit properties from
490         * @return the result of the merge
491         */
492        @Override
493        public Object merge(Object parent) {
494                if (parent == null) {
495                        return this;
496                }
497                if (!(parent instanceof MockHttpServletRequestBuilder)) {
498                        throw new IllegalArgumentException("Cannot merge with [" + parent.getClass().getName() + "]");
499                }
500                MockHttpServletRequestBuilder parentBuilder = (MockHttpServletRequestBuilder) parent;
501
502                if (!StringUtils.hasText(this.contextPath)) {
503                        this.contextPath = parentBuilder.contextPath;
504                }
505                if (!StringUtils.hasText(this.servletPath)) {
506                        this.servletPath = parentBuilder.servletPath;
507                }
508                if ("".equals(this.pathInfo)) {
509                        this.pathInfo = parentBuilder.pathInfo;
510                }
511
512                if (this.secure == null) {
513                        this.secure = parentBuilder.secure;
514                }
515                if (this.principal == null) {
516                        this.principal = parentBuilder.principal;
517                }
518                if (this.session == null) {
519                        this.session = parentBuilder.session;
520                }
521
522                if (this.characterEncoding == null) {
523                        this.characterEncoding = parentBuilder.characterEncoding;
524                }
525                if (this.content == null) {
526                        this.content = parentBuilder.content;
527                }
528                if (this.contentType == null) {
529                        this.contentType = parentBuilder.contentType;
530                }
531
532                for (String headerName : parentBuilder.headers.keySet()) {
533                        if (!this.headers.containsKey(headerName)) {
534                                this.headers.put(headerName, parentBuilder.headers.get(headerName));
535                        }
536                }
537                for (String paramName : parentBuilder.parameters.keySet()) {
538                        if (!this.parameters.containsKey(paramName)) {
539                                this.parameters.put(paramName, parentBuilder.parameters.get(paramName));
540                        }
541                }
542                for (Cookie cookie : parentBuilder.cookies) {
543                        if (!containsCookie(cookie)) {
544                                this.cookies.add(cookie);
545                        }
546                }
547                for (Locale locale : parentBuilder.locales) {
548                        if (!this.locales.contains(locale)) {
549                                this.locales.add(locale);
550                        }
551                }
552
553                for (String attributeName : parentBuilder.requestAttributes.keySet()) {
554                        if (!this.requestAttributes.containsKey(attributeName)) {
555                                this.requestAttributes.put(attributeName, parentBuilder.requestAttributes.get(attributeName));
556                        }
557                }
558                for (String attributeName : parentBuilder.sessionAttributes.keySet()) {
559                        if (!this.sessionAttributes.containsKey(attributeName)) {
560                                this.sessionAttributes.put(attributeName, parentBuilder.sessionAttributes.get(attributeName));
561                        }
562                }
563                for (String attributeName : parentBuilder.flashAttributes.keySet()) {
564                        if (!this.flashAttributes.containsKey(attributeName)) {
565                                this.flashAttributes.put(attributeName, parentBuilder.flashAttributes.get(attributeName));
566                        }
567                }
568
569                this.postProcessors.addAll(0, parentBuilder.postProcessors);
570
571                return this;
572        }
573
574        private boolean containsCookie(Cookie cookie) {
575                for (Cookie cookieToCheck : this.cookies) {
576                        if (ObjectUtils.nullSafeEquals(cookieToCheck.getName(), cookie.getName())) {
577                                return true;
578                        }
579                }
580                return false;
581        }
582
583        /**
584         * Build a {@link MockHttpServletRequest}.
585         */
586        @Override
587        public final MockHttpServletRequest buildRequest(ServletContext servletContext) {
588                MockHttpServletRequest request = createServletRequest(servletContext);
589
590                request.setAsyncSupported(true);
591                request.setMethod(this.method);
592
593                String requestUri = this.url.getRawPath();
594                request.setRequestURI(requestUri);
595
596                if (this.url.getScheme() != null) {
597                        request.setScheme(this.url.getScheme());
598                }
599                if (this.url.getHost() != null) {
600                        request.setServerName(this.url.getHost());
601                }
602                if (this.url.getPort() != -1) {
603                        request.setServerPort(this.url.getPort());
604                }
605
606                updatePathRequestProperties(request, requestUri);
607
608                if (this.secure != null) {
609                        request.setSecure(this.secure);
610                }
611                if (this.principal != null) {
612                        request.setUserPrincipal(this.principal);
613                }
614                if (this.session != null) {
615                        request.setSession(this.session);
616                }
617
618                request.setCharacterEncoding(this.characterEncoding);
619                request.setContent(this.content);
620                request.setContentType(this.contentType);
621
622                for (String name : this.headers.keySet()) {
623                        for (Object value : this.headers.get(name)) {
624                                request.addHeader(name, value);
625                        }
626                }
627
628                if (this.url.getRawQuery() != null) {
629                        request.setQueryString(this.url.getRawQuery());
630                }
631                addRequestParams(request, UriComponentsBuilder.fromUri(this.url).build().getQueryParams());
632
633                for (String name : this.parameters.keySet()) {
634                        for (String value : this.parameters.get(name)) {
635                                request.addParameter(name, value);
636                        }
637                }
638
639                if (this.content != null && this.content.length > 0) {
640                        String requestContentType = request.getContentType();
641                        if (requestContentType != null) {
642                                MediaType mediaType = MediaType.parseMediaType(requestContentType);
643                                if (MediaType.APPLICATION_FORM_URLENCODED.includes(mediaType)) {
644                                        addRequestParams(request, parseFormData(mediaType));
645                                }
646                        }
647                }
648
649                if (!ObjectUtils.isEmpty(this.cookies)) {
650                        request.setCookies(this.cookies.toArray(new Cookie[this.cookies.size()]));
651                }
652                if (!ObjectUtils.isEmpty(this.locales)) {
653                        request.setPreferredLocales(this.locales);
654                }
655
656                for (String name : this.requestAttributes.keySet()) {
657                        request.setAttribute(name, this.requestAttributes.get(name));
658                }
659                for (String name : this.sessionAttributes.keySet()) {
660                        request.getSession().setAttribute(name, this.sessionAttributes.get(name));
661                }
662
663                FlashMap flashMap = new FlashMap();
664                flashMap.putAll(this.flashAttributes);
665                FlashMapManager flashMapManager = getFlashMapManager(request);
666                flashMapManager.saveOutputFlashMap(flashMap, request, new MockHttpServletResponse());
667
668                return request;
669        }
670
671        /**
672         * Create a new {@link MockHttpServletRequest} based on the supplied
673         * {@code ServletContext}.
674         * <p>Can be overridden in subclasses.
675         */
676        protected MockHttpServletRequest createServletRequest(ServletContext servletContext) {
677                return new MockHttpServletRequest(servletContext);
678        }
679
680        /**
681         * Update the contextPath, servletPath, and pathInfo of the request.
682         */
683        private void updatePathRequestProperties(MockHttpServletRequest request, String requestUri) {
684                if (!requestUri.startsWith(this.contextPath)) {
685                        throw new IllegalArgumentException(
686                                        "Request URI [" + requestUri + "] does not start with context path [" + this.contextPath + "]");
687                }
688                request.setContextPath(this.contextPath);
689                request.setServletPath(this.servletPath);
690
691                if ("".equals(this.pathInfo)) {
692                        if (!requestUri.startsWith(this.contextPath + this.servletPath)) {
693                                throw new IllegalArgumentException(
694                                                "Invalid servlet path [" + this.servletPath + "] for request URI [" + requestUri + "]");
695                        }
696                        String extraPath = requestUri.substring(this.contextPath.length() + this.servletPath.length());
697                        this.pathInfo = (StringUtils.hasText(extraPath) ?
698                                        UrlPathHelper.defaultInstance.decodeRequestString(request, extraPath) : null);
699                }
700                request.setPathInfo(this.pathInfo);
701        }
702
703        private void addRequestParams(MockHttpServletRequest request, MultiValueMap<String, String> map) {
704                try {
705                        for (Entry<String, List<String>> entry : map.entrySet()) {
706                                for (String value : entry.getValue()) {
707                                        value = (value != null) ? UriUtils.decode(value, "UTF-8") : null;
708                                        request.addParameter(UriUtils.decode(entry.getKey(), "UTF-8"), value);
709                                }
710                        }
711                }
712                catch (UnsupportedEncodingException ex) {
713                        // shouldn't happen
714                }
715        }
716
717        private MultiValueMap<String, String> parseFormData(final MediaType mediaType) {
718                HttpInputMessage message = new HttpInputMessage() {
719                        @Override
720                        public InputStream getBody() throws IOException {
721                                return new ByteArrayInputStream(content);
722                        }
723                        @Override
724                        public HttpHeaders getHeaders() {
725                                HttpHeaders headers = new HttpHeaders();
726                                headers.setContentType(mediaType);
727                                return headers;
728                        }
729                };
730
731                try {
732                        return new FormHttpMessageConverter().read(null, message);
733                }
734                catch (IOException ex) {
735                        throw new IllegalStateException("Failed to parse form data in request body", ex);
736                }
737        }
738
739        private FlashMapManager getFlashMapManager(MockHttpServletRequest request) {
740                FlashMapManager flashMapManager = null;
741                try {
742                        ServletContext servletContext = request.getServletContext();
743                        WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
744                        flashMapManager = wac.getBean(DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class);
745                }
746                catch (IllegalStateException ex) {
747                        // ignore
748                }
749                catch (NoSuchBeanDefinitionException ex) {
750                        // ignore
751                }
752                return (flashMapManager != null ? flashMapManager : new SessionFlashMapManager());
753        }
754
755        @Override
756        public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
757                for (RequestPostProcessor postProcessor : this.postProcessors) {
758                        request = postProcessor.postProcessRequest(request);
759                        if (request == null) {
760                                throw new IllegalStateException(
761                                                "Post-processor [" + postProcessor.getClass().getName() + "] returned null");
762                        }
763                }
764                return request;
765        }
766
767
768        private static void addToMap(Map<String, Object> map, String name, Object value) {
769                Assert.hasLength(name, "'name' must not be empty");
770                Assert.notNull(value, "'value' must not be null");
771                map.put(name, value);
772        }
773
774        private static <T> void addToMultiValueMap(MultiValueMap<String, T> map, String name, T[] values) {
775                Assert.hasLength(name, "'name' must not be empty");
776                Assert.notEmpty(values, "'values' must not be empty");
777                for (T value : values) {
778                        map.add(name, value);
779                }
780        }
781
782}