View Javadoc
1   /*
2    * Copyright 2002-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.mock.web.test;
18  
19  import java.io.BufferedReader;
20  import java.io.ByteArrayInputStream;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.InputStreamReader;
24  import java.io.Reader;
25  import java.io.UnsupportedEncodingException;
26  import java.security.Principal;
27  import java.util.Collection;
28  import java.util.Collections;
29  import java.util.Date;
30  import java.util.Enumeration;
31  import java.util.HashSet;
32  import java.util.LinkedHashMap;
33  import java.util.LinkedHashSet;
34  import java.util.LinkedList;
35  import java.util.List;
36  import java.util.Locale;
37  import java.util.Map;
38  import java.util.Set;
39  import javax.servlet.AsyncContext;
40  import javax.servlet.DispatcherType;
41  import javax.servlet.RequestDispatcher;
42  import javax.servlet.ServletContext;
43  import javax.servlet.ServletException;
44  import javax.servlet.ServletInputStream;
45  import javax.servlet.ServletRequest;
46  import javax.servlet.ServletResponse;
47  import javax.servlet.http.Cookie;
48  import javax.servlet.http.HttpServletRequest;
49  import javax.servlet.http.HttpServletResponse;
50  import javax.servlet.http.HttpSession;
51  import javax.servlet.http.Part;
52  
53  import org.springframework.util.Assert;
54  import org.springframework.util.LinkedCaseInsensitiveMap;
55  import org.springframework.util.LinkedMultiValueMap;
56  import org.springframework.util.MultiValueMap;
57  import org.springframework.util.StringUtils;
58  
59  /**
60   * Mock implementation of the {@link javax.servlet.http.HttpServletRequest} interface.
61   *
62   * <p>As of Spring 4.0, this set of mocks is designed on a Servlet 3.0 baseline.
63   *
64   * @author Juergen Hoeller
65   * @author Rod Johnson
66   * @author Rick Evans
67   * @author Mark Fisher
68   * @author Chris Beams
69   * @author Sam Brannen
70   * @author Brian Clozel
71   * @since 1.0.2
72   */
73  public class MockHttpServletRequest implements HttpServletRequest {
74  
75  	/**
76  	 * The default protocol: 'http'.
77  	 */
78  	public static final String DEFAULT_PROTOCOL = "http";
79  
80  	/**
81  	 * The default server address: '127.0.0.1'.
82  	 */
83  	public static final String DEFAULT_SERVER_ADDR = "127.0.0.1";
84  
85  	/**
86  	 * The default server name: 'localhost'.
87  	 */
88  	public static final String DEFAULT_SERVER_NAME = "localhost";
89  
90  	/**
91  	 * The default server port: '80'.
92  	 */
93  	public static final int DEFAULT_SERVER_PORT = 80;
94  
95  	/**
96  	 * The default remote address: '127.0.0.1'.
97  	 */
98  	public static final String DEFAULT_REMOTE_ADDR = "127.0.0.1";
99  
100 	/**
101 	 * The default remote host: 'localhost'.
102 	 */
103 	public static final String DEFAULT_REMOTE_HOST = "localhost";
104 
105 	private static final String CONTENT_TYPE_HEADER = "Content-Type";
106 
107 	private static final String CHARSET_PREFIX = "charset=";
108 
109 	private static final ServletInputStream EMPTY_SERVLET_INPUT_STREAM =
110 			new DelegatingServletInputStream(new ByteArrayInputStream(new byte[0]));
111 
112 
113 	private boolean active = true;
114 
115 
116 	// ---------------------------------------------------------------------
117 	// ServletRequest properties
118 	// ---------------------------------------------------------------------
119 
120 	private final Map<String, Object> attributes = new LinkedHashMap<String, Object>();
121 
122 	private String characterEncoding;
123 
124 	private byte[] content;
125 
126 	private String contentType;
127 
128 	private final Map<String, String[]> parameters = new LinkedHashMap<String, String[]>(16);
129 
130 	private String protocol = DEFAULT_PROTOCOL;
131 
132 	private String scheme = DEFAULT_PROTOCOL;
133 
134 	private String serverName = DEFAULT_SERVER_NAME;
135 
136 	private int serverPort = DEFAULT_SERVER_PORT;
137 
138 	private String remoteAddr = DEFAULT_REMOTE_ADDR;
139 
140 	private String remoteHost = DEFAULT_REMOTE_HOST;
141 
142 	/** List of locales in descending order */
143 	private final List<Locale> locales = new LinkedList<Locale>();
144 
145 	private boolean secure = false;
146 
147 	private final ServletContext servletContext;
148 
149 	private int remotePort = DEFAULT_SERVER_PORT;
150 
151 	private String localName = DEFAULT_SERVER_NAME;
152 
153 	private String localAddr = DEFAULT_SERVER_ADDR;
154 
155 	private int localPort = DEFAULT_SERVER_PORT;
156 
157 	private boolean asyncStarted = false;
158 
159 	private boolean asyncSupported = false;
160 
161 	private MockAsyncContext asyncContext;
162 
163 	private DispatcherType dispatcherType = DispatcherType.REQUEST;
164 
165 
166 	// ---------------------------------------------------------------------
167 	// HttpServletRequest properties
168 	// ---------------------------------------------------------------------
169 
170 	private String authType;
171 
172 	private Cookie[] cookies;
173 
174 	private final Map<String, HeaderValueHolder> headers = new LinkedCaseInsensitiveMap<HeaderValueHolder>();
175 
176 	private String method;
177 
178 	private String pathInfo;
179 
180 	private String contextPath = "";
181 
182 	private String queryString;
183 
184 	private String remoteUser;
185 
186 	private final Set<String> userRoles = new HashSet<String>();
187 
188 	private Principal userPrincipal;
189 
190 	private String requestedSessionId;
191 
192 	private String requestURI;
193 
194 	private String servletPath = "";
195 
196 	private HttpSession session;
197 
198 	private boolean requestedSessionIdValid = true;
199 
200 	private boolean requestedSessionIdFromCookie = true;
201 
202 	private boolean requestedSessionIdFromURL = false;
203 
204 	private final MultiValueMap<String, Part> parts = new LinkedMultiValueMap<String, Part>();
205 
206 
207 	// ---------------------------------------------------------------------
208 	// Constructors
209 	// ---------------------------------------------------------------------
210 
211 	/**
212 	 * Create a new {@code MockHttpServletRequest} with a default
213 	 * {@link MockServletContext}.
214 	 * @see #MockHttpServletRequest(ServletContext, String, String)
215 	 */
216 	public MockHttpServletRequest() {
217 		this(null, "", "");
218 	}
219 
220 	/**
221 	 * Create a new {@code MockHttpServletRequest} with a default
222 	 * {@link MockServletContext}.
223 	 * @param method the request method (may be {@code null})
224 	 * @param requestURI the request URI (may be {@code null})
225 	 * @see #setMethod
226 	 * @see #setRequestURI
227 	 * @see #MockHttpServletRequest(ServletContext, String, String)
228 	 */
229 	public MockHttpServletRequest(String method, String requestURI) {
230 		this(null, method, requestURI);
231 	}
232 
233 	/**
234 	 * Create a new {@code MockHttpServletRequest} with the supplied {@link ServletContext}.
235 	 * @param servletContext the ServletContext that the request runs in
236 	 * (may be {@code null} to use a default {@link MockServletContext})
237 	 * @see #MockHttpServletRequest(ServletContext, String, String)
238 	 */
239 	public MockHttpServletRequest(ServletContext servletContext) {
240 		this(servletContext, "", "");
241 	}
242 
243 	/**
244 	 * Create a new {@code MockHttpServletRequest} with the supplied {@link ServletContext},
245 	 * {@code method}, and {@code requestURI}.
246 	 * <p>The preferred locale will be set to {@link Locale#ENGLISH}.
247 	 * @param servletContext the ServletContext that the request runs in (may be
248 	 * {@code null} to use a default {@link MockServletContext})
249 	 * @param method the request method (may be {@code null})
250 	 * @param requestURI the request URI (may be {@code null})
251 	 * @see #setMethod
252 	 * @see #setRequestURI
253 	 * @see #setPreferredLocales
254 	 * @see MockServletContext
255 	 */
256 	public MockHttpServletRequest(ServletContext servletContext, String method, String requestURI) {
257 		this.servletContext = (servletContext != null ? servletContext : new MockServletContext());
258 		this.method = method;
259 		this.requestURI = requestURI;
260 		this.locales.add(Locale.ENGLISH);
261 	}
262 
263 
264 	// ---------------------------------------------------------------------
265 	// Lifecycle methods
266 	// ---------------------------------------------------------------------
267 
268 	/**
269 	 * Return the ServletContext that this request is associated with. (Not
270 	 * available in the standard HttpServletRequest interface for some reason.)
271 	 */
272 	@Override
273 	public ServletContext getServletContext() {
274 		return this.servletContext;
275 	}
276 
277 	/**
278 	 * Return whether this request is still active (that is, not completed yet).
279 	 */
280 	public boolean isActive() {
281 		return this.active;
282 	}
283 
284 	/**
285 	 * Mark this request as completed, keeping its state.
286 	 */
287 	public void close() {
288 		this.active = false;
289 	}
290 
291 	/**
292 	 * Invalidate this request, clearing its state.
293 	 */
294 	public void invalidate() {
295 		close();
296 		clearAttributes();
297 	}
298 
299 	/**
300 	 * Check whether this request is still active (that is, not completed yet),
301 	 * throwing an IllegalStateException if not active anymore.
302 	 */
303 	protected void checkActive() throws IllegalStateException {
304 		if (!this.active) {
305 			throw new IllegalStateException("Request is not active anymore");
306 		}
307 	}
308 
309 
310 	// ---------------------------------------------------------------------
311 	// ServletRequest interface
312 	// ---------------------------------------------------------------------
313 
314 	@Override
315 	public Object getAttribute(String name) {
316 		checkActive();
317 		return this.attributes.get(name);
318 	}
319 
320 	@Override
321 	public Enumeration<String> getAttributeNames() {
322 		checkActive();
323 		return Collections.enumeration(new LinkedHashSet<String>(this.attributes.keySet()));
324 	}
325 
326 	@Override
327 	public String getCharacterEncoding() {
328 		return this.characterEncoding;
329 	}
330 
331 	@Override
332 	public void setCharacterEncoding(String characterEncoding) {
333 		this.characterEncoding = characterEncoding;
334 		updateContentTypeHeader();
335 	}
336 
337 	private void updateContentTypeHeader() {
338 		if (StringUtils.hasLength(this.contentType)) {
339 			StringBuilder sb = new StringBuilder(this.contentType);
340 			if (!this.contentType.toLowerCase().contains(CHARSET_PREFIX) &&
341 					StringUtils.hasLength(this.characterEncoding)) {
342 				sb.append(";").append(CHARSET_PREFIX).append(this.characterEncoding);
343 			}
344 			doAddHeaderValue(CONTENT_TYPE_HEADER, sb.toString(), true);
345 		}
346 	}
347 
348 	public void setContent(byte[] content) {
349 		this.content = content;
350 	}
351 
352 	@Override
353 	public int getContentLength() {
354 		return (this.content != null ? this.content.length : -1);
355 	}
356 
357 	public long getContentLengthLong() {
358 		return getContentLength();
359 	}
360 
361 	public void setContentType(String contentType) {
362 		this.contentType = contentType;
363 		if (contentType != null) {
364 			int charsetIndex = contentType.toLowerCase().indexOf(CHARSET_PREFIX);
365 			if (charsetIndex != -1) {
366 				this.characterEncoding = contentType.substring(charsetIndex + CHARSET_PREFIX.length());
367 			}
368 			updateContentTypeHeader();
369 		}
370 	}
371 
372 	@Override
373 	public String getContentType() {
374 		return this.contentType;
375 	}
376 
377 	@Override
378 	public ServletInputStream getInputStream() {
379 		if (this.content != null) {
380 			return new DelegatingServletInputStream(new ByteArrayInputStream(this.content));
381 		}
382 		else {
383 			return EMPTY_SERVLET_INPUT_STREAM;
384 		}
385 	}
386 
387 	/**
388 	 * Set a single value for the specified HTTP parameter.
389 	 * <p>If there are already one or more values registered for the given
390 	 * parameter name, they will be replaced.
391 	 */
392 	public void setParameter(String name, String value) {
393 		setParameter(name, new String[] {value});
394 	}
395 
396 	/**
397 	 * Set an array of values for the specified HTTP parameter.
398 	 * <p>If there are already one or more values registered for the given
399 	 * parameter name, they will be replaced.
400 	 */
401 	public void setParameter(String name, String[] values) {
402 		Assert.notNull(name, "Parameter name must not be null");
403 		this.parameters.put(name, values);
404 	}
405 
406 	/**
407 	 * Sets all provided parameters <strong>replacing</strong> any existing
408 	 * values for the provided parameter names. To add without replacing
409 	 * existing values, use {@link #addParameters(java.util.Map)}.
410 	 */
411 	@SuppressWarnings("rawtypes")
412 	public void setParameters(Map params) {
413 		Assert.notNull(params, "Parameter map must not be null");
414 		for (Object key : params.keySet()) {
415 			Assert.isInstanceOf(String.class, key,
416 					"Parameter map key must be of type [" + String.class.getName() + "]");
417 			Object value = params.get(key);
418 			if (value instanceof String) {
419 				this.setParameter((String) key, (String) value);
420 			}
421 			else if (value instanceof String[]) {
422 				this.setParameter((String) key, (String[]) value);
423 			}
424 			else {
425 				throw new IllegalArgumentException(
426 						"Parameter map value must be single value " + " or array of type [" + String.class.getName() + "]");
427 			}
428 		}
429 	}
430 
431 	/**
432 	 * Add a single value for the specified HTTP parameter.
433 	 * <p>If there are already one or more values registered for the given
434 	 * parameter name, the given value will be added to the end of the list.
435 	 */
436 	public void addParameter(String name, String value) {
437 		addParameter(name, new String[] {value});
438 	}
439 
440 	/**
441 	 * Add an array of values for the specified HTTP parameter.
442 	 * <p>If there are already one or more values registered for the given
443 	 * parameter name, the given values will be added to the end of the list.
444 	 */
445 	public void addParameter(String name, String[] values) {
446 		Assert.notNull(name, "Parameter name must not be null");
447 		String[] oldArr = this.parameters.get(name);
448 		if (oldArr != null) {
449 			String[] newArr = new String[oldArr.length + values.length];
450 			System.arraycopy(oldArr, 0, newArr, 0, oldArr.length);
451 			System.arraycopy(values, 0, newArr, oldArr.length, values.length);
452 			this.parameters.put(name, newArr);
453 		}
454 		else {
455 			this.parameters.put(name, values);
456 		}
457 	}
458 
459 	/**
460 	 * Adds all provided parameters <strong>without</strong> replacing any
461 	 * existing values. To replace existing values, use
462 	 * {@link #setParameters(java.util.Map)}.
463 	 */
464 	@SuppressWarnings("rawtypes")
465 	public void addParameters(Map params) {
466 		Assert.notNull(params, "Parameter map must not be null");
467 		for (Object key : params.keySet()) {
468 			Assert.isInstanceOf(String.class, key,
469 					"Parameter map key must be of type [" + String.class.getName() + "]");
470 			Object value = params.get(key);
471 			if (value instanceof String) {
472 				this.addParameter((String) key, (String) value);
473 			}
474 			else if (value instanceof String[]) {
475 				this.addParameter((String) key, (String[]) value);
476 			}
477 			else {
478 				throw new IllegalArgumentException("Parameter map value must be single value " +
479 						" or array of type [" + String.class.getName() + "]");
480 			}
481 		}
482 	}
483 
484 	/**
485 	 * Remove already registered values for the specified HTTP parameter, if any.
486 	 */
487 	public void removeParameter(String name) {
488 		Assert.notNull(name, "Parameter name must not be null");
489 		this.parameters.remove(name);
490 	}
491 
492 	/**
493 	 * Removes all existing parameters.
494 	 */
495 	public void removeAllParameters() {
496 		this.parameters.clear();
497 	}
498 
499 	@Override
500 	public String getParameter(String name) {
501 		String[] arr = (name != null ? this.parameters.get(name) : null);
502 		return (arr != null && arr.length > 0 ? arr[0] : null);
503 	}
504 
505 	@Override
506 	public Enumeration<String> getParameterNames() {
507 		return Collections.enumeration(this.parameters.keySet());
508 	}
509 
510 	@Override
511 	public String[] getParameterValues(String name) {
512 		return (name != null ? this.parameters.get(name) : null);
513 	}
514 
515 	@Override
516 	public Map<String, String[]> getParameterMap() {
517 		return Collections.unmodifiableMap(this.parameters);
518 	}
519 
520 	public void setProtocol(String protocol) {
521 		this.protocol = protocol;
522 	}
523 
524 	@Override
525 	public String getProtocol() {
526 		return this.protocol;
527 	}
528 
529 	public void setScheme(String scheme) {
530 		this.scheme = scheme;
531 	}
532 
533 	@Override
534 	public String getScheme() {
535 		return this.scheme;
536 	}
537 
538 	public void setServerName(String serverName) {
539 		this.serverName = serverName;
540 	}
541 
542 	@Override
543 	public String getServerName() {
544 		return this.serverName;
545 	}
546 
547 	public void setServerPort(int serverPort) {
548 		this.serverPort = serverPort;
549 	}
550 
551 	@Override
552 	public int getServerPort() {
553 		return this.serverPort;
554 	}
555 
556 	@Override
557 	public BufferedReader getReader() throws UnsupportedEncodingException {
558 		if (this.content != null) {
559 			InputStream sourceStream = new ByteArrayInputStream(this.content);
560 			Reader sourceReader = (this.characterEncoding != null) ?
561 					new InputStreamReader(sourceStream, this.characterEncoding) : new InputStreamReader(sourceStream);
562 			return new BufferedReader(sourceReader);
563 		}
564 		else {
565 			return null;
566 		}
567 	}
568 
569 	public void setRemoteAddr(String remoteAddr) {
570 		this.remoteAddr = remoteAddr;
571 	}
572 
573 	@Override
574 	public String getRemoteAddr() {
575 		return this.remoteAddr;
576 	}
577 
578 	public void setRemoteHost(String remoteHost) {
579 		this.remoteHost = remoteHost;
580 	}
581 
582 	@Override
583 	public String getRemoteHost() {
584 		return this.remoteHost;
585 	}
586 
587 	@Override
588 	public void setAttribute(String name, Object value) {
589 		checkActive();
590 		Assert.notNull(name, "Attribute name must not be null");
591 		if (value != null) {
592 			this.attributes.put(name, value);
593 		}
594 		else {
595 			this.attributes.remove(name);
596 		}
597 	}
598 
599 	@Override
600 	public void removeAttribute(String name) {
601 		checkActive();
602 		Assert.notNull(name, "Attribute name must not be null");
603 		this.attributes.remove(name);
604 	}
605 
606 	/**
607 	 * Clear all of this request's attributes.
608 	 */
609 	public void clearAttributes() {
610 		this.attributes.clear();
611 	}
612 
613 	/**
614 	 * Add a new preferred locale, before any existing locales.
615 	 * @see #setPreferredLocales
616 	 */
617 	public void addPreferredLocale(Locale locale) {
618 		Assert.notNull(locale, "Locale must not be null");
619 		this.locales.add(0, locale);
620 	}
621 
622 	/**
623 	 * Set the list of preferred locales, in descending order, effectively replacing
624 	 * any existing locales.
625 	 * @see #addPreferredLocale
626 	 * @since 3.2
627 	 */
628 	public void setPreferredLocales(List<Locale> locales) {
629 		Assert.notEmpty(locales, "Locale list must not be empty");
630 		this.locales.clear();
631 		this.locales.addAll(locales);
632 	}
633 
634 	@Override
635 	public Locale getLocale() {
636 		return this.locales.get(0);
637 	}
638 
639 	@Override
640 	public Enumeration<Locale> getLocales() {
641 		return Collections.enumeration(this.locales);
642 	}
643 
644 	public void setSecure(boolean secure) {
645 		this.secure = secure;
646 	}
647 
648 	@Override
649 	public boolean isSecure() {
650 		return this.secure;
651 	}
652 
653 	@Override
654 	public RequestDispatcher getRequestDispatcher(String path) {
655 		return new MockRequestDispatcher(path);
656 	}
657 
658 	@Override
659 	public String getRealPath(String path) {
660 		return this.servletContext.getRealPath(path);
661 	}
662 
663 	public void setRemotePort(int remotePort) {
664 		this.remotePort = remotePort;
665 	}
666 
667 	@Override
668 	public int getRemotePort() {
669 		return this.remotePort;
670 	}
671 
672 	public void setLocalName(String localName) {
673 		this.localName = localName;
674 	}
675 
676 	@Override
677 	public String getLocalName() {
678 		return this.localName;
679 	}
680 
681 	public void setLocalAddr(String localAddr) {
682 		this.localAddr = localAddr;
683 	}
684 
685 	@Override
686 	public String getLocalAddr() {
687 		return this.localAddr;
688 	}
689 
690 	public void setLocalPort(int localPort) {
691 		this.localPort = localPort;
692 	}
693 
694 	@Override
695 	public int getLocalPort() {
696 		return this.localPort;
697 	}
698 
699 	@Override
700 	public AsyncContext startAsync() {
701 		return startAsync(this, null);
702 	}
703 
704 	@Override
705 	public AsyncContext startAsync(ServletRequest request, ServletResponse response) {
706 		if (!this.asyncSupported) {
707 			throw new IllegalStateException("Async not supported");
708 		}
709 		this.asyncStarted = true;
710 		this.asyncContext = new MockAsyncContext(request, response);
711 		return this.asyncContext;
712 	}
713 
714 	public void setAsyncStarted(boolean asyncStarted) {
715 		this.asyncStarted = asyncStarted;
716 	}
717 
718 	@Override
719 	public boolean isAsyncStarted() {
720 		return this.asyncStarted;
721 	}
722 
723 	public void setAsyncSupported(boolean asyncSupported) {
724 		this.asyncSupported = asyncSupported;
725 	}
726 
727 	@Override
728 	public boolean isAsyncSupported() {
729 		return this.asyncSupported;
730 	}
731 
732 	public void setAsyncContext(MockAsyncContext asyncContext) {
733 		this.asyncContext = asyncContext;
734 	}
735 
736 	@Override
737 	public AsyncContext getAsyncContext() {
738 		return this.asyncContext;
739 	}
740 
741 	public void setDispatcherType(DispatcherType dispatcherType) {
742 		this.dispatcherType = dispatcherType;
743 	}
744 
745 	@Override
746 	public DispatcherType getDispatcherType() {
747 		return this.dispatcherType;
748 	}
749 
750 
751 	// ---------------------------------------------------------------------
752 	// HttpServletRequest interface
753 	// ---------------------------------------------------------------------
754 
755 	public void setAuthType(String authType) {
756 		this.authType = authType;
757 	}
758 
759 	@Override
760 	public String getAuthType() {
761 		return this.authType;
762 	}
763 
764 	public void setCookies(Cookie... cookies) {
765 		this.cookies = cookies;
766 	}
767 
768 	@Override
769 	public Cookie[] getCookies() {
770 		return this.cookies;
771 	}
772 
773 	/**
774 	 * Add a header entry for the given name.
775 	 * <p>If there was no entry for that header name before, the value will be used
776 	 * as-is. In case of an existing entry, a String array will be created,
777 	 * adding the given value (more specifically, its toString representation)
778 	 * as further element.
779 	 * <p>Multiple values can only be stored as list of Strings, following the
780 	 * Servlet spec (see {@code getHeaders} accessor). As alternative to
781 	 * repeated {@code addHeader} calls for individual elements, you can
782 	 * use a single call with an entire array or Collection of values as
783 	 * parameter.
784 	 * @see #getHeaderNames
785 	 * @see #getHeader
786 	 * @see #getHeaders
787 	 * @see #getDateHeader
788 	 * @see #getIntHeader
789 	 */
790 	public void addHeader(String name, Object value) {
791 		if (CONTENT_TYPE_HEADER.equalsIgnoreCase(name)) {
792 			setContentType((String) value);
793 			return;
794 		}
795 		doAddHeaderValue(name, value, false);
796 	}
797 
798 	@SuppressWarnings("rawtypes")
799 	private void doAddHeaderValue(String name, Object value, boolean replace) {
800 		HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
801 		Assert.notNull(value, "Header value must not be null");
802 		if (header == null || replace) {
803 			header = new HeaderValueHolder();
804 			this.headers.put(name, header);
805 		}
806 		if (value instanceof Collection) {
807 			header.addValues((Collection) value);
808 		}
809 		else if (value.getClass().isArray()) {
810 			header.addValueArray(value);
811 		}
812 		else {
813 			header.addValue(value);
814 		}
815 	}
816 
817 	@Override
818 	public long getDateHeader(String name) {
819 		HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
820 		Object value = (header != null ? header.getValue() : null);
821 		if (value instanceof Date) {
822 			return ((Date) value).getTime();
823 		}
824 		else if (value instanceof Number) {
825 			return ((Number) value).longValue();
826 		}
827 		else if (value != null) {
828 			throw new IllegalArgumentException(
829 					"Value for header '" + name + "' is neither a Date nor a Number: " + value);
830 		}
831 		else {
832 			return -1L;
833 		}
834 	}
835 
836 	@Override
837 	public String getHeader(String name) {
838 		HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
839 		return (header != null ? header.getStringValue() : null);
840 	}
841 
842 	@Override
843 	public Enumeration<String> getHeaders(String name) {
844 		HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
845 		return Collections.enumeration(header != null ? header.getStringValues() : new LinkedList<String>());
846 	}
847 
848 	@Override
849 	public Enumeration<String> getHeaderNames() {
850 		return Collections.enumeration(this.headers.keySet());
851 	}
852 
853 	@Override
854 	public int getIntHeader(String name) {
855 		HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
856 		Object value = (header != null ? header.getValue() : null);
857 		if (value instanceof Number) {
858 			return ((Number) value).intValue();
859 		}
860 		else if (value instanceof String) {
861 			return Integer.parseInt((String) value);
862 		}
863 		else if (value != null) {
864 			throw new NumberFormatException("Value for header '" + name + "' is not a Number: " + value);
865 		}
866 		else {
867 			return -1;
868 		}
869 	}
870 
871 	public void setMethod(String method) {
872 		this.method = method;
873 	}
874 
875 	@Override
876 	public String getMethod() {
877 		return this.method;
878 	}
879 
880 	public void setPathInfo(String pathInfo) {
881 		this.pathInfo = pathInfo;
882 	}
883 
884 	@Override
885 	public String getPathInfo() {
886 		return this.pathInfo;
887 	}
888 
889 	@Override
890 	public String getPathTranslated() {
891 		return (this.pathInfo != null ? getRealPath(this.pathInfo) : null);
892 	}
893 
894 	public void setContextPath(String contextPath) {
895 		this.contextPath = contextPath;
896 	}
897 
898 	@Override
899 	public String getContextPath() {
900 		return this.contextPath;
901 	}
902 
903 	public void setQueryString(String queryString) {
904 		this.queryString = queryString;
905 	}
906 
907 	@Override
908 	public String getQueryString() {
909 		return this.queryString;
910 	}
911 
912 	public void setRemoteUser(String remoteUser) {
913 		this.remoteUser = remoteUser;
914 	}
915 
916 	@Override
917 	public String getRemoteUser() {
918 		return this.remoteUser;
919 	}
920 
921 	public void addUserRole(String role) {
922 		this.userRoles.add(role);
923 	}
924 
925 	@Override
926 	public boolean isUserInRole(String role) {
927 		return (this.userRoles.contains(role) || (this.servletContext instanceof MockServletContext &&
928 				((MockServletContext) this.servletContext).getDeclaredRoles().contains(role)));
929 	}
930 
931 	public void setUserPrincipal(Principal userPrincipal) {
932 		this.userPrincipal = userPrincipal;
933 	}
934 
935 	@Override
936 	public Principal getUserPrincipal() {
937 		return this.userPrincipal;
938 	}
939 
940 	public void setRequestedSessionId(String requestedSessionId) {
941 		this.requestedSessionId = requestedSessionId;
942 	}
943 
944 	@Override
945 	public String getRequestedSessionId() {
946 		return this.requestedSessionId;
947 	}
948 
949 	public void setRequestURI(String requestURI) {
950 		this.requestURI = requestURI;
951 	}
952 
953 	@Override
954 	public String getRequestURI() {
955 		return this.requestURI;
956 	}
957 
958 	@Override
959 	public StringBuffer getRequestURL() {
960 		StringBuffer url = new StringBuffer(this.scheme).append("://").append(this.serverName);
961 
962 		if (this.serverPort > 0
963 				&& (("http".equalsIgnoreCase(scheme) && this.serverPort != 80) || ("https".equalsIgnoreCase(scheme) && this.serverPort != 443))) {
964 			url.append(':').append(this.serverPort);
965 		}
966 
967 		if (StringUtils.hasText(getRequestURI())) {
968 			url.append(getRequestURI());
969 		}
970 
971 		return url;
972 	}
973 
974 	public void setServletPath(String servletPath) {
975 		this.servletPath = servletPath;
976 	}
977 
978 	@Override
979 	public String getServletPath() {
980 		return this.servletPath;
981 	}
982 
983 	public void setSession(HttpSession session) {
984 		this.session = session;
985 		if (session instanceof MockHttpSession) {
986 			MockHttpSession mockSession = ((MockHttpSession) session);
987 			mockSession.access();
988 		}
989 	}
990 
991 	@Override
992 	public HttpSession getSession(boolean create) {
993 		checkActive();
994 		// Reset session if invalidated.
995 		if (this.session instanceof MockHttpSession && ((MockHttpSession) this.session).isInvalid()) {
996 			this.session = null;
997 		}
998 		// Create new session if necessary.
999 		if (this.session == null && create) {
1000 			this.session = new MockHttpSession(this.servletContext);
1001 		}
1002 		return this.session;
1003 	}
1004 
1005 	@Override
1006 	public HttpSession getSession() {
1007 		return getSession(true);
1008 	}
1009 
1010 	/**
1011 	 * The implementation of this (Servlet 3.1+) method calls
1012 	 * {@link MockHttpSession#changeSessionId()} if the session is a mock session.
1013 	 * Otherwise it simply returns the current session id.
1014 	 * @since 4.0.3
1015 	 */
1016 	public String changeSessionId() {
1017 		Assert.isTrue(this.session != null, "The request does not have a session");
1018 		if (this.session instanceof MockHttpSession) {
1019 			return ((MockHttpSession) session).changeSessionId();
1020 		}
1021 		return this.session.getId();
1022 	}
1023 
1024 	public void setRequestedSessionIdValid(boolean requestedSessionIdValid) {
1025 		this.requestedSessionIdValid = requestedSessionIdValid;
1026 	}
1027 
1028 	@Override
1029 	public boolean isRequestedSessionIdValid() {
1030 		return this.requestedSessionIdValid;
1031 	}
1032 
1033 	public void setRequestedSessionIdFromCookie(boolean requestedSessionIdFromCookie) {
1034 		this.requestedSessionIdFromCookie = requestedSessionIdFromCookie;
1035 	}
1036 
1037 	@Override
1038 	public boolean isRequestedSessionIdFromCookie() {
1039 		return this.requestedSessionIdFromCookie;
1040 	}
1041 
1042 	public void setRequestedSessionIdFromURL(boolean requestedSessionIdFromURL) {
1043 		this.requestedSessionIdFromURL = requestedSessionIdFromURL;
1044 	}
1045 
1046 	@Override
1047 	public boolean isRequestedSessionIdFromURL() {
1048 		return this.requestedSessionIdFromURL;
1049 	}
1050 
1051 	@Override
1052 	public boolean isRequestedSessionIdFromUrl() {
1053 		return isRequestedSessionIdFromURL();
1054 	}
1055 
1056 	@Override
1057 	public boolean authenticate(HttpServletResponse response) throws IOException, ServletException {
1058 		throw new UnsupportedOperationException();
1059 	}
1060 
1061 	@Override
1062 	public void login(String username, String password) throws ServletException {
1063 		throw new UnsupportedOperationException();
1064 	}
1065 
1066 	@Override
1067 	public void logout() throws ServletException {
1068 		this.userPrincipal = null;
1069 		this.remoteUser = null;
1070 		this.authType = null;
1071 	}
1072 
1073 	public void addPart(Part part) {
1074 		this.parts.add(part.getName(), part);
1075 	}
1076 
1077 	@Override
1078 	public Part getPart(String name) throws IOException, IllegalStateException, ServletException {
1079 		return this.parts.getFirst(name);
1080 	}
1081 
1082 	@Override
1083 	public Collection<Part> getParts() throws IOException, IllegalStateException, ServletException {
1084 		List<Part> result = new LinkedList<Part>();
1085 		for(List<Part> list : this.parts.values()) {
1086 			result.addAll(list);
1087 		}
1088 		return result;
1089 	}
1090 
1091 }