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