001: /*
002: * Copyright 2002-2007 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: * http://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:
017: package org.springframework.mock.web;
018:
019: import java.io.ByteArrayOutputStream;
020: import java.io.IOException;
021: import java.io.OutputStream;
022: import java.io.OutputStreamWriter;
023: import java.io.PrintWriter;
024: import java.io.UnsupportedEncodingException;
025: import java.io.Writer;
026: import java.util.ArrayList;
027: import java.util.Collections;
028: import java.util.HashMap;
029: import java.util.Iterator;
030: import java.util.List;
031: import java.util.Locale;
032: import java.util.Map;
033: import java.util.Set;
034:
035: import javax.servlet.ServletOutputStream;
036: import javax.servlet.http.Cookie;
037: import javax.servlet.http.HttpServletResponse;
038:
039: import org.springframework.util.Assert;
040: import org.springframework.web.util.WebUtils;
041:
042: /**
043: * Mock implementation of the {@link javax.servlet.http.HttpServletResponse}
044: * interface. Supports the Servlet 2.4 API level.
045: *
046: * <p>Used for testing the web framework; also useful for testing
047: * application controllers.
048: *
049: * @author Juergen Hoeller
050: * @author Rod Johnson
051: * @since 1.0.2
052: */
053: public class MockHttpServletResponse implements HttpServletResponse {
054:
055: public static final int DEFAULT_SERVER_PORT = 80;
056:
057: private static final String CHARSET_PREFIX = "charset=";
058:
059: //---------------------------------------------------------------------
060: // ServletResponse properties
061: //---------------------------------------------------------------------
062:
063: private boolean outputStreamAccessAllowed = true;
064:
065: private boolean writerAccessAllowed = true;
066:
067: private String characterEncoding = WebUtils.DEFAULT_CHARACTER_ENCODING;
068:
069: private final ByteArrayOutputStream content = new ByteArrayOutputStream();
070:
071: private final ServletOutputStream outputStream = new ResponseServletOutputStream(
072: this .content);
073:
074: private PrintWriter writer;
075:
076: private int contentLength = 0;
077:
078: private String contentType;
079:
080: private int bufferSize = 4096;
081:
082: private boolean committed;
083:
084: private Locale locale = Locale.getDefault();
085:
086: //---------------------------------------------------------------------
087: // HttpServletResponse properties
088: //---------------------------------------------------------------------
089:
090: private final List cookies = new ArrayList();
091:
092: /**
093: * The key is the lowercase header name; the value is a {@link HeaderValueHolder} object.
094: */
095: private final Map headers = new HashMap();
096:
097: private int status = HttpServletResponse.SC_OK;
098:
099: private String errorMessage;
100:
101: private String redirectedUrl;
102:
103: private String forwardedUrl;
104:
105: private String includedUrl;
106:
107: //---------------------------------------------------------------------
108: // ServletResponse interface
109: //---------------------------------------------------------------------
110:
111: /**
112: * Set whether {@link #getOutputStream()} access is allowed.
113: * <p>Default is <code>true</code>.
114: */
115: public void setOutputStreamAccessAllowed(
116: boolean outputStreamAccessAllowed) {
117: this .outputStreamAccessAllowed = outputStreamAccessAllowed;
118: }
119:
120: /**
121: * Return whether {@link #getOutputStream()} access is allowed.
122: */
123: public boolean isOutputStreamAccessAllowed() {
124: return this .outputStreamAccessAllowed;
125: }
126:
127: /**
128: * Set whether {@link #getWriter()} access is allowed.
129: * <p>Default is <code>true</code>.
130: */
131: public void setWriterAccessAllowed(boolean writerAccessAllowed) {
132: this .writerAccessAllowed = writerAccessAllowed;
133: }
134:
135: /**
136: * Return whether {@link #getOutputStream()} access is allowed.
137: */
138: public boolean isWriterAccessAllowed() {
139: return this .writerAccessAllowed;
140: }
141:
142: public void setCharacterEncoding(String characterEncoding) {
143: this .characterEncoding = characterEncoding;
144: }
145:
146: public String getCharacterEncoding() {
147: return this .characterEncoding;
148: }
149:
150: public ServletOutputStream getOutputStream() {
151: if (!this .outputStreamAccessAllowed) {
152: throw new IllegalStateException(
153: "OutputStream access not allowed");
154: }
155: return this .outputStream;
156: }
157:
158: public PrintWriter getWriter() throws UnsupportedEncodingException {
159: if (!this .writerAccessAllowed) {
160: throw new IllegalStateException("Writer access not allowed");
161: }
162: if (this .writer == null) {
163: Writer targetWriter = (this .characterEncoding != null ? new OutputStreamWriter(
164: this .content, this .characterEncoding)
165: : new OutputStreamWriter(this .content));
166: this .writer = new ResponsePrintWriter(targetWriter);
167: }
168: return this .writer;
169: }
170:
171: public byte[] getContentAsByteArray() {
172: flushBuffer();
173: return this .content.toByteArray();
174: }
175:
176: public String getContentAsString()
177: throws UnsupportedEncodingException {
178: flushBuffer();
179: return (this .characterEncoding != null) ? this .content
180: .toString(this .characterEncoding) : this .content
181: .toString();
182: }
183:
184: public void setContentLength(int contentLength) {
185: this .contentLength = contentLength;
186: }
187:
188: public int getContentLength() {
189: return this .contentLength;
190: }
191:
192: public void setContentType(String contentType) {
193: this .contentType = contentType;
194: if (contentType != null) {
195: int charsetIndex = contentType.toLowerCase().indexOf(
196: CHARSET_PREFIX);
197: if (charsetIndex != -1) {
198: String encoding = contentType.substring(charsetIndex
199: + CHARSET_PREFIX.length());
200: setCharacterEncoding(encoding);
201: }
202: }
203: }
204:
205: public String getContentType() {
206: return this .contentType;
207: }
208:
209: public void setBufferSize(int bufferSize) {
210: this .bufferSize = bufferSize;
211: }
212:
213: public int getBufferSize() {
214: return this .bufferSize;
215: }
216:
217: public void flushBuffer() {
218: setCommitted(true);
219: }
220:
221: public void resetBuffer() {
222: if (isCommitted()) {
223: throw new IllegalStateException(
224: "Cannot reset buffer - response is already committed");
225: }
226: this .content.reset();
227: }
228:
229: private void setCommittedIfBufferSizeExceeded() {
230: int bufSize = getBufferSize();
231: if (bufSize > 0 && this .content.size() > bufSize) {
232: setCommitted(true);
233: }
234: }
235:
236: public void setCommitted(boolean committed) {
237: this .committed = committed;
238: }
239:
240: public boolean isCommitted() {
241: return this .committed;
242: }
243:
244: public void reset() {
245: resetBuffer();
246: this .characterEncoding = null;
247: this .contentLength = 0;
248: this .contentType = null;
249: this .locale = null;
250: this .cookies.clear();
251: this .headers.clear();
252: this .status = HttpServletResponse.SC_OK;
253: this .errorMessage = null;
254: }
255:
256: public void setLocale(Locale locale) {
257: this .locale = locale;
258: }
259:
260: public Locale getLocale() {
261: return this .locale;
262: }
263:
264: //---------------------------------------------------------------------
265: // HttpServletResponse interface
266: //---------------------------------------------------------------------
267:
268: public void addCookie(Cookie cookie) {
269: Assert.notNull(cookie, "Cookie must not be null");
270: this .cookies.add(cookie);
271: }
272:
273: public Cookie[] getCookies() {
274: return (Cookie[]) this .cookies.toArray(new Cookie[this .cookies
275: .size()]);
276: }
277:
278: public Cookie getCookie(String name) {
279: Assert.notNull(name, "Cookie name must not be null");
280: for (Iterator it = this .cookies.iterator(); it.hasNext();) {
281: Cookie cookie = (Cookie) it.next();
282: if (name.equals(cookie.getName())) {
283: return cookie;
284: }
285: }
286: return null;
287: }
288:
289: public boolean containsHeader(String name) {
290: return (HeaderValueHolder.getByName(this .headers, name) != null);
291: }
292:
293: /**
294: * Return the names of all specified headers as a Set of Strings.
295: * @return the <code>Set</code> of header name <code>Strings</code>, or an empty <code>Set</code> if none
296: */
297: public Set getHeaderNames() {
298: return this .headers.keySet();
299: }
300:
301: /**
302: * Return the primary value for the given header, if any.
303: * <p>Will return the first value in case of multiple values.
304: * @param name the name of the header
305: * @return the associated header value, or <code>null<code> if none
306: */
307: public Object getHeader(String name) {
308: HeaderValueHolder header = HeaderValueHolder.getByName(
309: this .headers, name);
310: return (header != null ? header.getValue() : null);
311: }
312:
313: /**
314: * Return all values for the given header as a List of value objects.
315: * @param name the name of the header
316: * @return the associated header values, or an empty List if none
317: */
318: public List getHeaders(String name) {
319: HeaderValueHolder header = HeaderValueHolder.getByName(
320: this .headers, name);
321: return (header != null ? header.getValues()
322: : Collections.EMPTY_LIST);
323: }
324:
325: public String encodeURL(String url) {
326: return url;
327: }
328:
329: public String encodeRedirectURL(String url) {
330: return url;
331: }
332:
333: public String encodeUrl(String url) {
334: return url;
335: }
336:
337: public String encodeRedirectUrl(String url) {
338: return url;
339: }
340:
341: public void sendError(int status, String errorMessage)
342: throws IOException {
343: if (isCommitted()) {
344: throw new IllegalStateException(
345: "Cannot set error status - response is already committed");
346: }
347: this .status = status;
348: this .errorMessage = errorMessage;
349: setCommitted(true);
350: }
351:
352: public void sendError(int status) throws IOException {
353: if (isCommitted()) {
354: throw new IllegalStateException(
355: "Cannot set error status - response is already committed");
356: }
357: this .status = status;
358: setCommitted(true);
359: }
360:
361: public void sendRedirect(String url) throws IOException {
362: if (isCommitted()) {
363: throw new IllegalStateException(
364: "Cannot send redirect - response is already committed");
365: }
366: Assert.notNull(url, "Redirect URL must not be null");
367: this .redirectedUrl = url;
368: setCommitted(true);
369: }
370:
371: public String getRedirectedUrl() {
372: return this .redirectedUrl;
373: }
374:
375: public void setDateHeader(String name, long value) {
376: setHeaderValue(name, new Long(value));
377: }
378:
379: public void addDateHeader(String name, long value) {
380: addHeaderValue(name, new Long(value));
381: }
382:
383: public void setHeader(String name, String value) {
384: setHeaderValue(name, value);
385: }
386:
387: public void addHeader(String name, String value) {
388: addHeaderValue(name, value);
389: }
390:
391: public void setIntHeader(String name, int value) {
392: setHeaderValue(name, new Integer(value));
393: }
394:
395: public void addIntHeader(String name, int value) {
396: addHeaderValue(name, new Integer(value));
397: }
398:
399: private void setHeaderValue(String name, Object value) {
400: doAddHeaderValue(name, value, true);
401: }
402:
403: private void addHeaderValue(String name, Object value) {
404: doAddHeaderValue(name, value, false);
405: }
406:
407: private void doAddHeaderValue(String name, Object value,
408: boolean replace) {
409: HeaderValueHolder header = HeaderValueHolder.getByName(
410: this .headers, name);
411: Assert.notNull(value, "Header value must not be null");
412: if (header == null) {
413: header = new HeaderValueHolder();
414: this .headers.put(name, header);
415: }
416: if (replace) {
417: header.setValue(value);
418: } else {
419: header.addValue(value);
420: }
421: }
422:
423: public void setStatus(int status) {
424: this .status = status;
425: }
426:
427: public void setStatus(int status, String errorMessage) {
428: this .status = status;
429: this .errorMessage = errorMessage;
430: }
431:
432: public int getStatus() {
433: return this .status;
434: }
435:
436: public String getErrorMessage() {
437: return this .errorMessage;
438: }
439:
440: //---------------------------------------------------------------------
441: // Methods for MockRequestDispatcher
442: //---------------------------------------------------------------------
443:
444: public void setForwardedUrl(String forwardedUrl) {
445: this .forwardedUrl = forwardedUrl;
446: }
447:
448: public String getForwardedUrl() {
449: return this .forwardedUrl;
450: }
451:
452: public void setIncludedUrl(String includedUrl) {
453: this .includedUrl = includedUrl;
454: }
455:
456: public String getIncludedUrl() {
457: return this .includedUrl;
458: }
459:
460: /**
461: * Inner class that adapts the ServletOutputStream to mark the
462: * response as committed once the buffer size is exceeded.
463: */
464: private class ResponseServletOutputStream extends
465: DelegatingServletOutputStream {
466:
467: public ResponseServletOutputStream(OutputStream out) {
468: super (out);
469: }
470:
471: public void write(int b) throws IOException {
472: super .write(b);
473: super .flush();
474: setCommittedIfBufferSizeExceeded();
475: }
476:
477: public void flush() throws IOException {
478: super .flush();
479: setCommitted(true);
480: }
481: }
482:
483: /**
484: * Inner class that adapts the PrintWriter to mark the
485: * response as committed once the buffer size is exceeded.
486: */
487: private class ResponsePrintWriter extends PrintWriter {
488:
489: public ResponsePrintWriter(Writer out) {
490: super (out, true);
491: }
492:
493: public void write(char buf[], int off, int len) {
494: super .write(buf, off, len);
495: super .flush();
496: setCommittedIfBufferSizeExceeded();
497: }
498:
499: public void write(String s, int off, int len) {
500: super .write(s, off, len);
501: super .flush();
502: setCommittedIfBufferSizeExceeded();
503: }
504:
505: public void flush() {
506: super .flush();
507: setCommitted(true);
508: }
509: }
510:
511: }
|