001 /*
002 * Copyright 1999,2004 The Apache Software Foundation.
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
018 package org.apache.commons.messagelet.impl;
019
020
021 import java.io.IOException;
022 import java.text.SimpleDateFormat;
023 import java.util.ArrayList;
024 import java.util.Date;
025 import java.util.HashMap;
026 import java.util.Locale;
027 import java.util.TimeZone;
028
029 import javax.servlet.http.Cookie;
030 import javax.servlet.http.HttpServletResponse;
031
032
033 /**
034 * Based on the HttpRequestBase code from Catalina.
035 *
036 * @author Craig R. McClanahan
037 * @author James Strachan
038 * @version $Revision: 155459 $ $Date: 2005-02-26 13:24:44 +0000 (Sat, 26 Feb 2005) $
039 */
040
041 public class HttpServletResponseImpl extends ServletResponseImpl implements HttpServletResponse {
042
043 /**
044 * The set of Cookies associated with this Response.
045 */
046 protected ArrayList cookies = new ArrayList();
047
048
049 /**
050 * The date format we will use for creating date headers.
051 */
052 protected static final SimpleDateFormat format =
053 new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz",Locale.US);
054 static {
055 format.setTimeZone(TimeZone.getTimeZone("GMT"));
056 };
057
058
059 /**
060 * The HTTP headers explicitly added via addHeader(), but not including
061 * those to be added with setContentLength(), setContentType(), and so on.
062 * This collection is keyed by the header name, and the elements are
063 * ArrayLists containing the associated values that have been set.
064 */
065 protected HashMap headers = new HashMap();
066
067
068
069 /**
070 * The error message set by <code>sendError()</code>.
071 */
072 protected String message = getStatusMessage(HttpServletResponse.SC_OK);
073
074
075 /**
076 * The HTTP status code associated with this Response.
077 */
078 protected int status = HttpServletResponse.SC_OK;
079
080
081 /**
082 * The time zone with which to construct date headers.
083 */
084 protected static final TimeZone zone = TimeZone.getTimeZone("GMT");
085
086
087
088 // --------------------------------------------------------- Public Methods
089
090
091 /**
092 * Return an array of all cookies set for this response, or
093 * a zero-length array if no cookies have been set.
094 */
095 public Cookie[] getCookies() {
096
097 synchronized (cookies) {
098 return ((Cookie[]) cookies.toArray(new Cookie[cookies.size()]));
099 }
100
101 }
102
103
104 /**
105 * Return the value for the specified header, or <code>null</code> if this
106 * header has not been set. If more than one value was added for this
107 * name, only the first is returned; use getHeaderValues() to retrieve all
108 * of them.
109 *
110 * @param name Header name to look up
111 */
112 public String getHeader(String name) {
113
114 ArrayList values = null;
115 synchronized (headers) {
116 values = (ArrayList) headers.get(name);
117 }
118 if (values != null)
119 return ((String) values.get(0));
120 else
121 return (null);
122
123 }
124
125
126 /**
127 * Return an array of all the header names set for this response, or
128 * a zero-length array if no headers have been set.
129 */
130 public String[] getHeaderNames() {
131
132 synchronized (headers) {
133 String results[] = new String[headers.size()];
134 return ((String[]) headers.keySet().toArray(results));
135 }
136
137 }
138
139
140 /**
141 * Return an array of all the header values associated with the
142 * specified header name, or an zero-length array if there are no such
143 * header values.
144 *
145 * @param name Header name to look up
146 */
147 public String[] getHeaderValues(String name) {
148
149 ArrayList values = null;
150 synchronized (headers) {
151 values = (ArrayList) headers.get(name);
152 }
153 if (values == null)
154 return (new String[0]);
155 String results[] = new String[values.size()];
156 return ((String[]) values.toArray(results));
157
158 }
159
160
161 /**
162 * Return the error message that was set with <code>sendError()</code>
163 * for this Response.
164 */
165 public String getMessage() {
166
167 return (this.message);
168
169 }
170
171
172 /**
173 * Return the HTTP status code associated with this Response.
174 */
175 public int getStatus() {
176
177 return (this.status);
178
179 }
180
181
182 /**
183 * Release all object references, and initialize instance variables, in
184 * preparation for reuse of this object.
185 */
186 public void recycle() {
187 //super.recycle();
188 cookies.clear();
189 headers.clear();
190 message = getStatusMessage(HttpServletResponse.SC_OK);
191 status = HttpServletResponse.SC_OK;
192
193 }
194
195
196 /**
197 * Reset this response, and specify the values for the HTTP status code
198 * and corresponding message.
199 *
200 * @exception IllegalStateException if this response has already been
201 * committed
202 */
203 public void reset(int status, String message) {
204
205 reset();
206 setStatus(status, message);
207
208 }
209
210
211 // ------------------------------------------------------ Protected Methods
212
213
214 /**
215 * Returns a default status message for the specified HTTP status code.
216 *
217 * @param status The status code for which a message is desired
218 */
219 protected String getStatusMessage(int status) {
220
221 switch (status) {
222 case SC_OK:
223 return ("OK");
224 case SC_ACCEPTED:
225 return ("Accepted");
226 case SC_BAD_GATEWAY:
227 return ("Bad Gateway");
228 case SC_BAD_REQUEST:
229 return ("Bad Request");
230 case SC_CONFLICT:
231 return ("Conflict");
232 case SC_CONTINUE:
233 return ("Continue");
234 case SC_CREATED:
235 return ("Created");
236 case SC_EXPECTATION_FAILED:
237 return ("Expectation Failed");
238 case SC_FORBIDDEN:
239 return ("Forbidden");
240 case SC_GATEWAY_TIMEOUT:
241 return ("Gateway Timeout");
242 case SC_GONE:
243 return ("Gone");
244 case SC_HTTP_VERSION_NOT_SUPPORTED:
245 return ("HTTP Version Not Supported");
246 case SC_INTERNAL_SERVER_ERROR:
247 return ("Internal Server Error");
248 case SC_LENGTH_REQUIRED:
249 return ("Length Required");
250 case SC_METHOD_NOT_ALLOWED:
251 return ("Method Not Allowed");
252 case SC_MOVED_PERMANENTLY:
253 return ("Moved Permanently");
254 case SC_MOVED_TEMPORARILY:
255 return ("Moved Temporarily");
256 case SC_MULTIPLE_CHOICES:
257 return ("Multiple Choices");
258 case SC_NO_CONTENT:
259 return ("No Content");
260 case SC_NON_AUTHORITATIVE_INFORMATION:
261 return ("Non-Authoritative Information");
262 case SC_NOT_ACCEPTABLE:
263 return ("Not Acceptable");
264 case SC_NOT_FOUND:
265 return ("Not Found");
266 case SC_NOT_IMPLEMENTED:
267 return ("Not Implemented");
268 case SC_NOT_MODIFIED:
269 return ("Not Modified");
270 case SC_PARTIAL_CONTENT:
271 return ("Partial Content");
272 case SC_PAYMENT_REQUIRED:
273 return ("Payment Required");
274 case SC_PRECONDITION_FAILED:
275 return ("Precondition Failed");
276 case SC_PROXY_AUTHENTICATION_REQUIRED:
277 return ("Proxy Authentication Required");
278 case SC_REQUEST_ENTITY_TOO_LARGE:
279 return ("Request Entity Too Large");
280 case SC_REQUEST_TIMEOUT:
281 return ("Request Timeout");
282 case SC_REQUEST_URI_TOO_LONG:
283 return ("Request URI Too Long");
284 case SC_REQUESTED_RANGE_NOT_SATISFIABLE:
285 return ("Requested Range Not Satisfiable");
286 case SC_RESET_CONTENT:
287 return ("Reset Content");
288 case SC_SEE_OTHER:
289 return ("See Other");
290 case SC_SERVICE_UNAVAILABLE:
291 return ("Service Unavailable");
292 case SC_SWITCHING_PROTOCOLS:
293 return ("Switching Protocols");
294 case SC_UNAUTHORIZED:
295 return ("Unauthorized");
296 case SC_UNSUPPORTED_MEDIA_TYPE:
297 return ("Unsupported Media Type");
298 case SC_USE_PROXY:
299 return ("Use Proxy");
300 case 207: // WebDAV
301 return ("Multi-Status");
302 case 422: // WebDAV
303 return ("Unprocessable Entity");
304 case 423: // WebDAV
305 return ("Locked");
306 case 507: // WebDAV
307 return ("Insufficient Storage");
308 default:
309 return ("HTTP Response Status " + status);
310 }
311
312 }
313
314
315
316 // ------------------------------------------------ ServletResponse Methods
317
318
319 /**
320 * Flush the buffer and commit this response. If this is the first output,
321 * send the HTTP headers prior to the user data.
322 *
323 * @exception IOException if an input/output error occurs
324 */
325 public void flushBuffer() throws IOException {
326 /*
327 if (!isCommitted()) {
328 sendHeaders();
329 }
330 */
331 super.flushBuffer();
332
333 }
334
335
336 /**
337 * Clear any content written to the buffer. In addition, all cookies
338 * and headers are cleared, and the status is reset.
339 *
340 * @exception IllegalStateException if this response has already
341 * been committed
342 */
343 public void reset() {
344
345 if (included)
346 return; // Ignore any call from an included servlet
347
348 super.reset();
349 cookies.clear();
350 headers.clear();
351 message = null;
352 status = HttpServletResponse.SC_OK;
353
354 }
355
356
357 /**
358 * Set the content length (in bytes) for this Response.
359 *
360 * @param length The new content length
361 */
362 public void setContentLength(int length) {
363
364 if (isCommitted())
365 return;
366
367 if (included)
368 return; // Ignore any call from an included servlet
369
370 super.setContentLength(length);
371
372 }
373
374
375
376 /**
377 * Set the content type for this Response.
378 *
379 * @param type The new content type
380 */
381 public void setContentType(String type) {
382
383 if (isCommitted())
384 return;
385
386 if (included)
387 return; // Ignore any call from an included servlet
388
389 super.setContentType(type);
390
391 }
392
393
394 /**
395 * Set the Locale that is appropriate for this response, including
396 * setting the appropriate character encoding.
397 *
398 * @param locale The new locale
399 */
400 public void setLocale(Locale locale) {
401
402 if (isCommitted())
403 return;
404
405 if (included)
406 return; // Ignore any call from an included servlet
407
408 super.setLocale(locale);
409 String language = locale.getLanguage();
410 if ((language != null) && (language.length() > 0)) {
411 String country = locale.getCountry();
412 StringBuffer value = new StringBuffer(language);
413 if ((country != null) && (country.length() > 0)) {
414 value.append('-');
415 value.append(country);
416 }
417 setHeader("Content-Language", value.toString());
418 }
419
420 }
421
422
423 // -------------------------------------------- HttpServletResponse Methods
424
425
426 /**
427 * Add the specified Cookie to those that will be included with
428 * this Response.
429 *
430 * @param cookie Cookie to be added
431 */
432 public void addCookie(Cookie cookie) {
433
434 if (isCommitted())
435 return;
436
437 if (included)
438 return; // Ignore any call from an included servlet
439
440 synchronized (cookies) {
441 cookies.add(cookie);
442 }
443
444 }
445
446
447 /**
448 * Add the specified date header to the specified value.
449 *
450 * @param name Name of the header to set
451 * @param value Date value to be set
452 */
453 public void addDateHeader(String name, long value) {
454
455 if (isCommitted())
456 return;
457
458 if (included)
459 return; // Ignore any call from an included servlet
460
461 addHeader(name, format.format(new Date(value)));
462
463 }
464
465
466 /**
467 * Add the specified header to the specified value.
468 *
469 * @param name Name of the header to set
470 * @param value Value to be set
471 */
472 public void addHeader(String name, String value) {
473
474 if (isCommitted())
475 return;
476
477 if (included)
478 return; // Ignore any call from an included servlet
479
480 synchronized (headers) {
481 ArrayList values = (ArrayList) headers.get(name);
482 if (values == null) {
483 values = new ArrayList();
484 headers.put(name, values);
485 }
486 values.add(value);
487 }
488
489 }
490
491
492 /**
493 * Add the specified integer header to the specified value.
494 *
495 * @param name Name of the header to set
496 * @param value Integer value to be set
497 */
498 public void addIntHeader(String name, int value) {
499
500 if (isCommitted())
501 return;
502
503 if (included)
504 return; // Ignore any call from an included servlet
505
506 addHeader(name, "" + value);
507
508 }
509
510
511 /**
512 * Has the specified header been set already in this response?
513 *
514 * @param name Name of the header to check
515 */
516 public boolean containsHeader(String name) {
517
518 synchronized (headers) {
519 return (headers.get(name) != null);
520 }
521
522 }
523
524
525 /**
526 * Encode the session identifier associated with this response
527 * into the specified redirect URL, if necessary.
528 *
529 * @param url URL to be encoded
530 */
531 public String encodeRedirectURL(String url) {
532
533 return (url);
534
535 }
536
537
538 /**
539 * Encode the session identifier associated with this response
540 * into the specified redirect URL, if necessary.
541 *
542 * @param url URL to be encoded
543 *
544 * @deprecated As of Version 2.1 of the Java Servlet API, use
545 * <code>encodeRedirectURL()</code> instead.
546 */
547 public String encodeRedirectUrl(String url) {
548
549 return (encodeRedirectURL(url));
550
551 }
552
553
554 /**
555 * Encode the session identifier associated with this response
556 * into the specified URL, if necessary.
557 *
558 * @param url URL to be encoded
559 */
560 public String encodeURL(String url) {
561
562 return (url);
563 }
564
565
566 /**
567 * Encode the session identifier associated with this response
568 * into the specified URL, if necessary.
569 *
570 * @param url URL to be encoded
571 *
572 * @deprecated As of Version 2.1 of the Java Servlet API, use
573 * <code>encodeURL()</code> instead.
574 */
575 public String encodeUrl(String url) {
576
577 return (encodeURL(url));
578
579 }
580
581
582 /**
583 * Send an error response with the specified status and a
584 * default message.
585 *
586 * @param status HTTP status code to send
587 *
588 * @exception IllegalStateException if this response has
589 * already been committed
590 * @exception IOException if an input/output error occurs
591 */
592 public void sendError(int status) throws IOException {
593
594 sendError(status, getStatusMessage(status));
595
596 }
597
598
599 /**
600 * Send an error response with the specified status and message.
601 *
602 * @param status HTTP status code to send
603 * @param message Corresponding message to send
604 *
605 * @exception IllegalStateException if this response has
606 * already been committed
607 * @exception IOException if an input/output error occurs
608 */
609 public void sendError(int status, String message) throws IOException {
610
611 if (isCommitted()) {
612 throw new IllegalStateException( "Cannot send error, already committed" );
613 }
614
615 if (included) {
616 return; // Ignore any call from an included servlet
617 }
618
619 //setError();
620
621 // Record the status code and message.
622 this.status = status;
623 this.message = message;
624
625 // Clear any data content that has been buffered
626 resetBuffer();
627
628 // Cause the response to be committed
629 /* Per spec clarification, no default content type is set
630 String contentType = getContentType();
631 if ((contentType == null) || "text/plain".equals(contentType))
632 setContentType("text/html");
633 */
634
635 // Temporarily comment out the following flush so that
636 // default error reports can still set the content type
637 // FIXME - this stuff needs to be refactored
638 /*
639 try {
640 flushBuffer();
641 } catch (IOException e) {
642 ;
643 }
644 */
645
646 }
647
648
649 /**
650 * Send a temporary redirect to the specified redirect location URL.
651 *
652 * @param location Location URL to redirect to
653 *
654 * @exception IllegalStateException if this response has
655 * already been committed
656 * @exception IOException if an input/output error occurs
657 */
658 public void sendRedirect(String location) throws IOException {
659
660 if (isCommitted()) {
661 throw new IllegalStateException( "Cannot send error, already committed" );
662 }
663 if (included)
664 return; // Ignore any call from an included servlet
665
666 // Clear any data content that has been buffered
667 resetBuffer();
668
669 // Generate a temporary redirect to the specified location
670 //String absolute = toAbsolute(location);
671 String absolute = location;
672 setStatus(SC_MOVED_TEMPORARILY);
673 setHeader("Location", absolute);
674
675 }
676
677
678 /**
679 * Set the specified date header to the specified value.
680 *
681 * @param name Name of the header to set
682 * @param value Date value to be set
683 */
684 public void setDateHeader(String name, long value) {
685
686 if (isCommitted())
687 return;
688
689 if (included)
690 return; // Ignore any call from an included servlet
691
692 setHeader(name, format.format(new Date(value)));
693
694 }
695
696
697 /**
698 * Set the specified header to the specified value.
699 *
700 * @param name Name of the header to set
701 * @param value Value to be set
702 */
703 public void setHeader(String name, String value) {
704
705 if (isCommitted())
706 return;
707
708 if (included)
709 return; // Ignore any call from an included servlet
710
711 ArrayList values = new ArrayList();
712 values.add(value);
713 synchronized (headers) {
714 headers.put(name, values);
715 }
716
717 String match = name.toLowerCase();
718 if (match.equals("content-length")) {
719 int contentLength = -1;
720 try {
721 contentLength = Integer.parseInt(value);
722 } catch (NumberFormatException e) {
723 ;
724 }
725 if (contentLength >= 0)
726 setContentLength(contentLength);
727 } else if (match.equals("content-type")) {
728 setContentType(value);
729 }
730
731 }
732
733
734 /**
735 * Set the specified integer header to the specified value.
736 *
737 * @param name Name of the header to set
738 * @param value Integer value to be set
739 */
740 public void setIntHeader(String name, int value) {
741
742 if (isCommitted())
743 return;
744
745 if (included)
746 return; // Ignore any call from an included servlet
747
748 setHeader(name, "" + value);
749
750 }
751
752
753 /**
754 * Set the HTTP status to be returned with this response.
755 *
756 * @param status The new HTTP status
757 */
758 public void setStatus(int status) {
759
760 setStatus(status, getStatusMessage(status));
761
762 }
763
764
765 /**
766 * Set the HTTP status and message to be returned with this response.
767 *
768 * @param status The new HTTP status
769 * @param message The associated text message
770 *
771 * @deprecated As of Version 2.1 of the Java Servlet API, this method
772 * has been deprecated due to the ambiguous meaning of the message
773 * parameter.
774 */
775 public void setStatus(int status, String message) {
776
777 if (included)
778 return; // Ignore any call from an included servlet
779
780 this.status = status;
781 this.message = message;
782
783 }
784
785
786 }