001: package com.meterware.servletunit;
002:
003: /********************************************************************************************************************
004: * $Id: ServletUnitHttpResponse.java,v 1.22 2006/03/24 19:59:12 russgold Exp $
005: *
006: * Copyright (c) 2000-2004,2006, Russell Gold
007: *
008: * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
009: * documentation files (the "Software"), to deal in the Software without restriction, including without limitation
010: * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
011: * to permit persons to whom the Software is furnished to do so, subject to the following conditions:
012: *
013: * The above copyright notice and this permission notice shall be included in all copies or substantial portions
014: * of the Software.
015: *
016: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
017: * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
018: * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
019: * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
020: * DEALINGS IN THE SOFTWARE.
021: *
022: *******************************************************************************************************************/
023: import com.meterware.httpunit.HttpUnitUtils;
024:
025: import java.io.ByteArrayOutputStream;
026: import java.io.IOException;
027: import java.io.OutputStreamWriter;
028: import java.io.PrintWriter;
029: import java.io.UnsupportedEncodingException;
030:
031: import java.util.*;
032: import java.text.SimpleDateFormat;
033:
034: import javax.servlet.ServletOutputStream;
035: import javax.servlet.http.Cookie;
036: import javax.servlet.http.HttpServletResponse;
037:
038: class ServletUnitHttpResponse implements HttpServletResponse {
039:
040: // rfc1123-date is "Sun, 06 Nov 1994 08:49:37 GMT"
041: private static final String RFC1123_DATE_SPEC = "EEE, dd MMM yyyy hh:mm:ss z";
042: private boolean _committed;
043: private Locale _locale = Locale.getDefault();
044:
045: private static final Hashtable ENCODING_MAP = new Hashtable();
046:
047: /**
048: * @deprecated Use encodeURL(String url)
049: */
050: public String encodeUrl(String url) {
051: return encodeURL(url);
052: }
053:
054: /**
055: * Adds the specified cookie to the response. It can be called
056: * multiple times to set more than one cookie.
057: */
058: public void addCookie(Cookie cookie) {
059: _cookies.addElement(cookie);
060: }
061:
062: /**
063: * Checks whether the response message header has a field with
064: * the specified name.
065: */
066: public boolean containsHeader(String name) {
067: return _headers.containsKey(name.toUpperCase());
068: }
069:
070: /**
071: * @deprecated Use encodeRedirectURL(String url)
072: **/
073: public String encodeRedirectUrl(String url) {
074: return encodeRedirectURL(url);
075: }
076:
077: /**
078: * Encodes the specified URL by including the session ID in it,
079: * or, if encoding is not needed, returns the URL unchanged.
080: * The implementation of this method should include the logic to
081: * determine whether the session ID needs to be encoded in the URL.
082: * For example, if the browser supports cookies, or session
083: * tracking is turned off, URL encoding is unnecessary.
084: **/
085: public String encodeURL(String url) {
086: return url;
087: }
088:
089: /**
090: * Encodes the specified URL for use in the
091: * <code>sendRedirect</code> method or, if encoding is not needed,
092: * returns the URL unchanged. The implementation of this method
093: * should include the logic to determine whether the session ID
094: * needs to be encoded in the URL. Because the rules for making
095: * this determination differ from those used to decide whether to
096: * encode a normal link, this method is seperate from the
097: * <code>encodeUrl</code> method.
098: **/
099: public String encodeRedirectURL(String url) {
100: return url;
101: }
102:
103: /**
104: * Sends a temporary redirect response to the client using the
105: * specified redirect location URL. The URL must be absolute (for
106: * example, <code><em>https://hostname/path/file.html</em></code>).
107: * Relative URLs are not permitted here.
108: */
109: public void sendRedirect(String location) throws IOException {
110: setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
111: setHeader("Location", location);
112: }
113:
114: /**
115: * Sends an error response to the client using the specified status
116: * code and descriptive message. If setStatus has previously been
117: * called, it is reset to the error status code. The message is
118: * sent as the body of an HTML page, which is returned to the user
119: * to describe the problem. The page is sent with a default HTML
120: * header; the message is enclosed in simple body tags
121: * (<body></body>).
122: **/
123: public void sendError(int sc) throws IOException {
124: sendError(sc, "");
125: }
126:
127: /**
128: * Sends an error response to the client using the specified status
129: * code and descriptive message. If setStatus has previously been
130: * called, it is reset to the error status code. The message is
131: * sent as the body of an HTML page, which is returned to the user
132: * to describe the problem. The page is sent with a default HTML
133: * header; the message is enclosed in simple body tags
134: * (<body></body>).
135: **/
136: public void sendError(int sc, String msg) throws IOException {
137: setStatus(sc);
138: _statusMessage = msg;
139:
140: _writer = null;
141: _servletStream = null;
142:
143: setContentType("text/html");
144: getWriter().println(
145: "<html><head><title>" + msg + "</title></head><body>"
146: + msg + "</body></html>");
147: }
148:
149: /**
150: * Sets the status code for this response. This method is used to
151: * set the return status code when there is no error (for example,
152: * for the status codes SC_OK or SC_MOVED_TEMPORARILY). If there
153: * is an error, the <code>sendError</code> method should be used
154: * instead.
155: **/
156: public void setStatus(int sc) {
157: _status = sc;
158: }
159:
160: /**
161: * @deprecated As of version 2.1, due to ambiguous meaning of the message parameter.
162: * To set a status code use setStatus(int), to send an error with a description
163: * use sendError(int, String). Sets the status code and message for this response.
164: **/
165: public void setStatus(int sc, String msg) {
166: setStatus(sc);
167: }
168:
169: /**
170: * Adds a field to the response header with the given name and value.
171: * If the field had already been set, the new value overwrites the
172: * previous one. The <code>containsHeader</code> method can be
173: * used to test for the presence of a header before setting its
174: * value.
175: **/
176: public void setHeader(String name, String value) {
177: ArrayList values = new ArrayList();
178: values.add(value);
179: synchronized (_headers) {
180: _headers.put(name.toUpperCase(), values);
181: }
182: }
183:
184: /**
185: * Adds a field to the response header with the given name and
186: * integer value. If the field had already been set, the new value
187: * overwrites the previous one. The <code>containsHeader</code>
188: * method can be used to test for the presence of a header before
189: * setting its value.
190: **/
191: public void setIntHeader(String name, int value) {
192: setHeader(name, asHeaderValue(value));
193: }
194:
195: private String asHeaderValue(int value) {
196: return Integer.toString(value);
197: }
198:
199: /**
200: * Adds a field to the response header with the given name and
201: * date-valued field. The date is specified in terms of
202: * milliseconds since the epoch. If the date field had already
203: * been set, the new value overwrites the previous one. The
204: * <code>containsHeader</code> method can be used to test for the
205: * presence of a header before setting its value.
206: **/
207: public void setDateHeader(String name, long date) {
208: setHeader(name, asDateHeaderValue(date));
209: }
210:
211: private String asDateHeaderValue(long date) {
212: Date value = new Date(date);
213: SimpleDateFormat formatter = new SimpleDateFormat(
214: RFC1123_DATE_SPEC, Locale.US);
215: formatter.setTimeZone(TimeZone
216: .getTimeZone("Greenwich Mean Time"));
217: return formatter.format(value);
218: }
219:
220: /**
221: * Returns the name of the character set encoding used for
222: * the MIME body sent by this response.
223: **/
224: public String getCharacterEncoding() {
225: return _encoding == null ? HttpUnitUtils.DEFAULT_CHARACTER_SET
226: : _encoding;
227: }
228:
229: /**
230: * Sets the content type of the response the server sends to
231: * the client. The content type may include the type of character
232: * encoding used, for example, <code>text/html; charset=ISO-8859-4</code>.
233: *
234: * <p>You can only use this method once, and you should call it
235: * before you obtain a <code>PrintWriter</code> or
236: * {@link ServletOutputStream} object to return a response.
237: **/
238: public void setContentType(String type) {
239: String[] typeAndEncoding = HttpUnitUtils
240: .parseContentTypeHeader(type);
241:
242: _contentType = typeAndEncoding[0];
243: if (typeAndEncoding[1] != null)
244: _encoding = typeAndEncoding[1];
245: }
246:
247: /**
248: * Returns a {@link ServletOutputStream} suitable for writing binary
249: * data in the response. The servlet engine does not encode the
250: * binary data.
251: *
252: * @exception IllegalStateException if you have already called the <code>getWriter</code> method
253: **/
254: public ServletOutputStream getOutputStream() throws IOException {
255: if (_writer != null)
256: throw new IllegalStateException(
257: "Tried to create output stream; writer already exists");
258: if (_servletStream == null) {
259: _outputStream = new ByteArrayOutputStream();
260: _servletStream = new ServletUnitOutputStream(_outputStream);
261: }
262: return _servletStream;
263: }
264:
265: /**
266: * Returns a <code>PrintWriter</code> object that you
267: * can use to send character text to the client.
268: * The character encoding used is the one specified
269: * in the <code>charset=</code> property of the
270: * {@link #setContentType} method, which you must call
271: * <i>before</i> you call this method.
272: *
273: * <p>If necessary, the MIME type of the response is
274: * modified to reflect the character encoding used.
275: *
276: * <p> You cannot use this method if you have already
277: * called {@link #getOutputStream} for this
278: * <code>ServletResponse</code> object.
279: *
280: * @exception UnsupportedEncodingException if the character encoding specified in
281: * <code>setContentType</code> cannot be
282: * used
283: *
284: * @exception IllegalStateException if the <code>getOutputStream</code>
285: * method has already been called for this
286: * response object; in that case, you can't
287: * use this method
288: *
289: **/
290: public PrintWriter getWriter() throws UnsupportedEncodingException {
291: if (_servletStream != null)
292: throw new IllegalStateException(
293: "Tried to create writer; output stream already exists");
294: if (_writer == null) {
295: _outputStream = new ByteArrayOutputStream();
296: _writer = new PrintWriter(new OutputStreamWriter(
297: _outputStream, getCharacterEncoding()));
298: }
299: return _writer;
300: }
301:
302: /**
303: * Sets the length of the content the server returns
304: * to the client. In HTTP servlets, this method sets the
305: * HTTP Content-Length header.
306: **/
307: public void setContentLength(int len) {
308: setIntHeader("Content-Length", len);
309: }
310:
311: //------------------------------- the following methods are new in JSDK 2.2 ----------------------
312:
313: /**
314: * Adds a response header with the given name and value. This method allows response headers to have multiple values.
315: **/
316: public void addHeader(String name, String value) {
317: synchronized (_headers) {
318: String key = name.toUpperCase();
319: ArrayList values = (ArrayList) _headers.get(key);
320: if (values == null) {
321: values = new ArrayList();
322: _headers.put(key, values);
323: }
324: values.add(value);
325: }
326: }
327:
328: /**
329: * Adds a response header with the given name and value. This method allows response headers to have multiple values.
330: **/
331: public void addIntHeader(String name, int value) {
332: addHeader(name, asHeaderValue(value));
333: }
334:
335: /**
336: * Adds a response header with the given name and value. This method allows response headers to have multiple values.
337: **/
338: public void addDateHeader(String name, long value) {
339: addHeader(name, asDateHeaderValue(value));
340: }
341:
342: /**
343: * Sets the preferred buffer size for the body of the response. The servlet container
344: * will use a buffer at least as large as the size requested. The actual buffer size
345: * used can be found using getBufferSize.
346: **/
347: public void setBufferSize(int size) {
348: if (getContents().length != 0)
349: throw new IllegalStateException(
350: "May not set buffer size after data is written");
351: }
352:
353: /**
354: * Returns the actual buffer size used for the response. If no buffering is used, this method returns 0.
355: **/
356: public int getBufferSize() {
357: return 0;
358: }
359:
360: /**
361: * Returns a boolean indicating if the response has been committed. A committed response has
362: * already had its status code and headers written.
363: **/
364: public boolean isCommitted() {
365: return _committed;
366: }
367:
368: /**
369: * Forces any content in the buffer to be written to the client. A call to this method automatically
370: * commits the response, meaning the status code and headers will be written.
371: **/
372: public void flushBuffer() throws IOException {
373: _committed = true;
374: }
375:
376: /**
377: * Clears any data that exists in the buffer as well as the status code and headers.
378: * If the response has been committed, this method throws an IllegalStateException.
379: **/
380: public void reset() {
381: resetBuffer();
382: _headers.clear();
383: _headersComplete = false;
384: _status = SC_OK;
385: }
386:
387: /**
388: * Sets the locale of the response, setting the headers (including the Content-Type's charset)
389: * as appropriate. This method should be called before a call to getWriter().
390: * By default, the response locale is the default locale for the server.
391: **/
392: public void setLocale(Locale locale) {
393: _locale = locale;
394: if (_encoding == null) {
395: for (Iterator it = ENCODING_MAP.entrySet().iterator(); it
396: .hasNext();) {
397: Map.Entry entry = (Map.Entry) it.next();
398: String locales = (String) entry.getValue();
399: if (locales.indexOf(locale.getLanguage()) >= 0
400: || locales.indexOf(locale.toString()) >= 0) {
401: _encoding = (String) entry.getKey();
402: return;
403: }
404: }
405: }
406: }
407:
408: /**
409: * Returns the locale assigned to the response.
410: **/
411: public Locale getLocale() {
412: return _locale;
413: }
414:
415: //----------------------------- methods added to ServletResponse in JSDK 2.3 --------------------------------------
416:
417: /**
418: * Clears the content of the underlying buffer in the response without clearing headers or status code.
419: * If the response has been committed, this method throws an IllegalStateException.
420: *
421: * @since 1.3
422: */
423: public void resetBuffer() {
424: if (_committed)
425: throw new IllegalStateException(
426: "May not resetBuffer after response is committed");
427: _outputStream = null;
428: _servletStream = null;
429: _writer = null;
430: }
431:
432: //---------------------------------------------- package methods --------------------------------------------------
433:
434: /**
435: * Returns the contents of this response.
436: **/
437: byte[] getContents() {
438: if (_outputStream == null) {
439: return new byte[0];
440: } else {
441: if (_writer != null)
442: _writer.flush();
443: return _outputStream.toByteArray();
444: }
445: }
446:
447: /**
448: * Returns the status of this response.
449: **/
450: int getStatus() {
451: return _status;
452: }
453:
454: /**
455: * Returns the message associated with this response's status.
456: **/
457: String getMessage() {
458: return _statusMessage;
459: }
460:
461: public String[] getHeaderFieldNames() {
462: if (!_headersComplete)
463: completeHeaders();
464: Vector names = new Vector();
465: for (Enumeration e = _headers.keys(); e.hasMoreElements();) {
466: names.addElement(e.nextElement());
467: }
468: String[] result = new String[names.size()];
469: names.copyInto(result);
470: return result;
471: }
472:
473: /**
474: * Returns the headers defined for this response.
475: **/
476: String getHeaderField(String name) {
477: if (!_headersComplete)
478: completeHeaders();
479:
480: ArrayList values;
481: synchronized (_headers) {
482: values = (ArrayList) _headers.get(name.toUpperCase());
483: }
484:
485: return values == null ? null : (String) values.get(0);
486: }
487:
488: /**
489: * Return an array of all the header values associated with the
490: * specified header name, or an zero-length array if there are no such
491: * header values.
492: *
493: * @param name Header name to look up
494: */
495: public String[] getHeaderFields(String name) {
496: if (!_headersComplete)
497: completeHeaders();
498: ArrayList values;
499: synchronized (_headers) {
500: values = (ArrayList) _headers.get(name.toUpperCase());
501: }
502: if (values == null)
503: return (new String[0]);
504: String results[] = new String[values.size()];
505: return ((String[]) values.toArray(results));
506:
507: }
508:
509: //--------------------------------------- methods added to ServletRequest in Servlet API 2.4 ----------------------------
510:
511: public void setCharacterEncoding(String string) {
512: _encoding = string;
513: }
514:
515: /**
516: * Returns the content type defined for this response.
517: **/
518: public String getContentType() {
519: return _contentType;
520: }
521:
522: //------------------------------------------- private members ------------------------------------
523:
524: private String _contentType = "text/plain";
525:
526: private String _encoding;
527:
528: private PrintWriter _writer;
529:
530: private ServletOutputStream _servletStream;
531:
532: private ByteArrayOutputStream _outputStream;
533:
534: private int _status = SC_OK;
535:
536: private String _statusMessage = "OK";
537:
538: private final Hashtable _headers = new Hashtable();
539:
540: private boolean _headersComplete;
541:
542: private Vector _cookies = new Vector();
543:
544: private void completeHeaders() {
545: if (_headersComplete)
546: return;
547: addCookieHeader();
548: setHeader("Content-Type", _contentType + "; charset="
549: + getCharacterEncoding());
550: _headersComplete = true;
551: }
552:
553: private void addCookieHeader() {
554: if (_cookies.isEmpty())
555: return;
556:
557: StringBuffer sb = new StringBuffer();
558: for (Enumeration e = _cookies.elements(); e.hasMoreElements();) {
559: Cookie cookie = (Cookie) e.nextElement();
560: sb.append(cookie.getName()).append('=').append(
561: cookie.getValue());
562: if (cookie.getPath() != null)
563: sb.append(";path=").append(cookie.getPath());
564: if (cookie.getDomain() != null)
565: sb.append(";domain=").append(cookie.getDomain());
566: if (e.hasMoreElements())
567: sb.append(',');
568: }
569: setHeader("Set-Cookie", sb.toString());
570: }
571:
572: static {
573: ENCODING_MAP.put("iso-8859-1",
574: "ca da de en es fi fr is it nl no pt sv ");
575: ENCODING_MAP.put("iso-8859-2", "cs hr hu pl ro sh sk sl sq ");
576: ENCODING_MAP.put("iso-8859-4", "et lt lv ");
577: ENCODING_MAP.put("iso-8859-5", "be bg mk ru sr uk ");
578: ENCODING_MAP.put("iso-8859-6", "ar ");
579: ENCODING_MAP.put("iso-8859-7", "el ");
580: ENCODING_MAP.put("iso-8859-8", "iw he ");
581: ENCODING_MAP.put("iso-8859-9", "tr ");
582:
583: ENCODING_MAP.put("Shift_JIS", "ja ");
584: ENCODING_MAP.put("EUC-KR", "ko ");
585: ENCODING_MAP.put("TIS-620", "th ");
586: ENCODING_MAP.put("GB2312", "zh ");
587: ENCODING_MAP.put("Big5", "zh_TW zh_HK ");
588: }
589:
590: }
591:
592: class ServletUnitOutputStream extends ServletOutputStream {
593:
594: ServletUnitOutputStream(ByteArrayOutputStream stream) {
595: _stream = stream;
596: }
597:
598: public void write(int aByte) throws IOException {
599: _stream.write(aByte);
600: }
601:
602: private ByteArrayOutputStream _stream;
603: }
|