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 }