001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.protocol.http.mock; 018 019import java.io.ByteArrayOutputStream; 020import java.io.IOException; 021import java.io.PrintWriter; 022import java.io.StringWriter; 023import java.text.DateFormat; 024import java.util.ArrayList; 025import java.util.Calendar; 026import java.util.Collection; 027import java.util.Collections; 028import java.util.Date; 029import java.util.GregorianCalendar; 030import java.util.Iterator; 031import java.util.List; 032import java.util.Locale; 033import java.util.Set; 034import java.util.TimeZone; 035 036import javax.servlet.ServletOutputStream; 037import javax.servlet.WriteListener; 038import javax.servlet.http.Cookie; 039import javax.servlet.http.HttpServletResponse; 040 041import org.apache.wicket.protocol.http.IMetaDataBufferingWebResponse; 042import org.apache.wicket.request.http.WebResponse; 043import org.apache.wicket.util.value.ValueMap; 044 045 046/** 047 * Mock servlet response. Implements all of the methods from the standard HttpServletResponse class 048 * plus helper methods to aid viewing the generated response. 049 * 050 * @author Chris Turner 051 */ 052public class MockHttpServletResponse implements HttpServletResponse, IMetaDataBufferingWebResponse 053{ 054 private static final int MODE_BINARY = 1; 055 056 private static final int MODE_NONE = 0; 057 058 private static final int MODE_TEXT = 2; 059 060 private ByteArrayOutputStream byteStream; 061 062 private String characterEncoding = "UTF-8"; 063 064 private final List<Cookie> cookies = new ArrayList<Cookie>(); 065 066 private String errorMessage = null; 067 068 private final ValueMap headers = new ValueMap(); 069 070 private Locale locale = null; 071 072 private int mode = MODE_NONE; 073 074 private PrintWriter printWriter; 075 076 private String redirectLocation = null; 077 078 private ServletOutputStream servletStream; 079 080 private int status = HttpServletResponse.SC_OK; 081 082 private StringWriter stringWriter; 083 084 /** 085 * Create the response object. 086 * 087 * @param servletRequest 088 */ 089 public MockHttpServletResponse(MockHttpServletRequest servletRequest) 090 { 091 initialize(); 092 } 093 094 /** 095 * Add a cookie to the response. 096 * 097 * @param cookie 098 * The cookie to add 099 */ 100 @Override 101 public void addCookie(final Cookie cookie) 102 { 103 // remove any potential duplicates 104 // see http://www.ietf.org/rfc/rfc2109.txt, p.4.3.3 105 Iterator<Cookie> iterator = cookies.iterator(); 106 while (iterator.hasNext()) 107 { 108 Cookie old = iterator.next(); 109 if (Cookies.isEqual(cookie, old)) 110 { 111 iterator.remove(); 112 } 113 } 114 cookies.add(cookie); 115 } 116 117 /** 118 * Add a date header. 119 * 120 * @param name 121 * The header value 122 * @param l 123 * The long value 124 */ 125 @Override 126 public void addDateHeader(String name, long l) 127 { 128 DateFormat df = DateFormat.getDateInstance(DateFormat.FULL); 129 addHeader(name, df.format(new Date(l))); 130 } 131 132 /** 133 * Add the given header value, including an additional entry if one already exists. 134 * 135 * @param name 136 * The name for the header 137 * @param value 138 * The value for the header 139 */ 140 @Override 141 @SuppressWarnings("unchecked") 142 public void addHeader(final String name, final String value) 143 { 144 List<String> list = (List<String>)headers.get(name); 145 if (list == null) 146 { 147 list = new ArrayList<String>(1); 148 headers.put(name, list); 149 } 150 list.add(value); 151 } 152 153 /** 154 * Add an int header value. 155 * 156 * @param name 157 * The header name 158 * @param i 159 * The value 160 */ 161 @Override 162 public void addIntHeader(final String name, final int i) 163 { 164 addHeader(name, "" + i); 165 } 166 167 /** 168 * Check if the response contains the given header name. 169 * 170 * @param name 171 * The name to check 172 * @return Whether header in response or not 173 */ 174 @Override 175 public boolean containsHeader(final String name) 176 { 177 return headers.containsKey(name); 178 } 179 180 /** 181 * Encode the redirectLocation URL. Does no changes as this test implementation uses cookie 182 * based url tracking. 183 * 184 * @param url 185 * The url to encode 186 * @return The encoded url 187 */ 188 @Override 189 public String encodeRedirectUrl(final String url) 190 { 191 return url; 192 } 193 194 /** 195 * Encode the redirectLocation URL. Does no changes as this test implementation uses cookie 196 * based url tracking. 197 * 198 * @param url 199 * The url to encode 200 * @return The encoded url 201 */ 202 @Override 203 public String encodeRedirectURL(final String url) 204 { 205 return url; 206 } 207 208 /** 209 * Encode the URL. Does no changes as this test implementation uses cookie based url tracking. 210 * 211 * @param url 212 * The url to encode 213 * @return The encoded url 214 */ 215 @Override 216 public String encodeUrl(final String url) 217 { 218 return url; 219 } 220 221 /** 222 * Encode the URL. Does no changes as this test implementation uses cookie based url tracking. 223 * 224 * @param url 225 * The url to encode 226 * @return The encoded url 227 */ 228 @Override 229 public String encodeURL(final String url) 230 { 231 return url; 232 } 233 234 /** 235 * Flush the buffer. 236 * 237 * @throws IOException 238 */ 239 @Override 240 public void flushBuffer() throws IOException 241 { 242 } 243 244 /** 245 * Get the binary content that was written to the servlet stream. 246 * 247 * @return The binary content 248 */ 249 public byte[] getBinaryContent() 250 { 251 return byteStream.toByteArray(); 252 } 253 254 /** 255 * Return the current buffer size 256 * 257 * @return The buffer size 258 */ 259 @Override 260 public int getBufferSize() 261 { 262 if (mode == MODE_NONE) 263 { 264 return 0; 265 } 266 else if (mode == MODE_BINARY) 267 { 268 return byteStream.size(); 269 } 270 else 271 { 272 return stringWriter.getBuffer().length(); 273 } 274 } 275 276 /** 277 * Get the character encoding of the response. 278 * 279 * @return The character encoding 280 */ 281 @Override 282 public String getCharacterEncoding() 283 { 284 return characterEncoding; 285 } 286 287 288 /** 289 * Get all of the cookies that have been added to the response. 290 * 291 * @return The collection of cookies 292 */ 293 public List<Cookie> getCookies() 294 { 295 List<Cookie> copies = new ArrayList<Cookie>(); 296 for (Cookie cookie : cookies) 297 { 298 copies.add(Cookies.copyOf(cookie)); 299 } 300 return copies; 301 } 302 303 /** 304 * Get the text document that was written as part of this response. 305 * 306 * @return The document 307 */ 308 public String getDocument() 309 { 310 if (mode == MODE_BINARY) 311 { 312 return new String(byteStream.toByteArray()); 313 } 314 else 315 { 316 return stringWriter.getBuffer().toString(); 317 } 318 } 319 320 /** 321 * Get the error message. 322 * 323 * @return The error message, or null if no message 324 */ 325 public String getErrorMessage() 326 { 327 return errorMessage; 328 } 329 330 /** 331 * Return the value of the given named header. 332 * 333 * @param name 334 * The header name 335 * @return The value, or null 336 */ 337 @Override 338 @SuppressWarnings("unchecked") 339 public String getHeader(final String name) 340 { 341 List<String> l = (List<String>)headers.get(name); 342 if (l == null || l.size() < 1) 343 { 344 return null; 345 } 346 else 347 { 348 return l.get(0); 349 } 350 } 351 352 /** 353 * Get the names of all of the headers. 354 * 355 * @return The header names 356 */ 357 @Override 358 public Set<String> getHeaderNames() 359 { 360 return headers.keySet(); 361 } 362 363 /** 364 * Get the encoded locale 365 * 366 * @return The locale 367 */ 368 @Override 369 public Locale getLocale() 370 { 371 return locale; 372 } 373 374 /** 375 * Get the output stream for writing binary data from the servlet. 376 * 377 * @return The binary output stream. 378 */ 379 @Override 380 public ServletOutputStream getOutputStream() 381 { 382 if (mode == MODE_TEXT) 383 { 384 throw new IllegalArgumentException("Can't write binary after already selecting text"); 385 } 386 mode = MODE_BINARY; 387 return servletStream; 388 } 389 390 /** 391 * Get the location that was redirected to. 392 * 393 * @return The redirect location, or null if not a redirect 394 */ 395 public String getRedirectLocation() 396 { 397 return redirectLocation; 398 } 399 400 /** 401 * Get the status code. 402 * 403 * @return The status code 404 */ 405 @Override 406 public int getStatus() 407 { 408 return status; 409 } 410 411 /** 412 * Get the print writer for writing text output for this response. 413 * 414 * @return The writer 415 * @throws IOException 416 * Not used 417 */ 418 @Override 419 public PrintWriter getWriter() throws IOException 420 { 421 if (mode == MODE_BINARY) 422 { 423 throw new IllegalArgumentException("Can't write text after already selecting binary"); 424 } 425 mode = MODE_TEXT; 426 return printWriter; 427 } 428 429 /** 430 * Reset the response ready for reuse. 431 */ 432 public void initialize() 433 { 434 cookies.clear(); 435 headers.clear(); 436 errorMessage = null; 437 redirectLocation = null; 438 status = HttpServletResponse.SC_OK; 439 characterEncoding = "UTF-8"; 440 locale = null; 441 442 byteStream = new ByteArrayOutputStream(); 443 servletStream = new ServletOutputStream() 444 { 445 @Override 446 public boolean isReady() 447 { 448 return true; 449 } 450 451 @Override 452 public void setWriteListener(WriteListener writeListener) 453 { 454 } 455 456 @Override 457 public void write(int b) 458 { 459 byteStream.write(b); 460 } 461 }; 462 stringWriter = new StringWriter(); 463 printWriter = new PrintWriter(stringWriter) 464 { 465 @Override 466 public void close() 467 { 468 // Do nothing 469 } 470 471 @Override 472 public void flush() 473 { 474 // Do nothing 475 } 476 }; 477 mode = MODE_NONE; 478 } 479 480 /** 481 * Always returns false. 482 * 483 * @return Always false 484 */ 485 @Override 486 public boolean isCommitted() 487 { 488 return false; 489 } 490 491 /** 492 * Return whether the servlet returned an error code or not. 493 * 494 * @return Whether an error occurred or not 495 */ 496 public boolean isError() 497 { 498 return (status != HttpServletResponse.SC_OK); 499 } 500 501 /** 502 * Check whether the response was redirected or not. 503 * 504 * @return Whether the state was redirected or not 505 */ 506 public boolean isRedirect() 507 { 508 return (redirectLocation != null); 509 } 510 511 /** 512 * Delegate to initialize method. 513 */ 514 @Override 515 public void reset() 516 { 517 initialize(); 518 } 519 520 /** 521 * Clears the buffer. 522 */ 523 @Override 524 public void resetBuffer() 525 { 526 if (mode == MODE_BINARY) 527 { 528 byteStream.reset(); 529 } 530 else if (mode == MODE_TEXT) 531 { 532 stringWriter.getBuffer().delete(0, stringWriter.getBuffer().length()); 533 } 534 } 535 536 /** 537 * Send an error code. This implementation just sets the internal error state information. 538 * 539 * @param code 540 * The code 541 * @throws IOException 542 * Not used 543 */ 544 @Override 545 public void sendError(final int code) throws IOException 546 { 547 status = code; 548 errorMessage = null; 549 } 550 551 /** 552 * Send an error code. This implementation just sets the internal error state information. 553 * 554 * @param code 555 * The error code 556 * @param msg 557 * The error message 558 * @throws IOException 559 * Not used 560 */ 561 @Override 562 public void sendError(final int code, final String msg) throws IOException 563 { 564 status = code; 565 errorMessage = msg; 566 } 567 568 /** 569 * Indicate sending of a redirectLocation to a particular named resource. This implementation 570 * just keeps hold of the redirectLocation info and makes it available for query. 571 * 572 * @param location 573 * The location to redirectLocation to 574 * @throws IOException 575 * Not used 576 */ 577 @Override 578 public void sendRedirect(String location) throws IOException 579 { 580 redirectLocation = location; 581 status = HttpServletResponse.SC_FOUND; 582 } 583 584 /** 585 * Method ignored. 586 * 587 * @param size 588 * The size 589 */ 590 @Override 591 public void setBufferSize(final int size) 592 { 593 } 594 595 /** 596 * Set the character encoding. 597 * 598 * @param characterEncoding 599 * The character encoding 600 */ 601 @Override 602 public void setCharacterEncoding(final String characterEncoding) 603 { 604 this.characterEncoding = characterEncoding; 605 } 606 607 /** 608 * Set the content length. 609 * 610 * @param length 611 * The length 612 */ 613 @Override 614 public void setContentLength(final int length) 615 { 616 setIntHeader("Content-Length", length); 617 } 618 619 @Override 620 public void setContentLengthLong(long len) 621 { 622 setContentLength((int) len); 623 } 624 625 /** 626 * Set the content type. 627 * 628 * @param type 629 * The content type 630 */ 631 @Override 632 public void setContentType(final String type) 633 { 634 setHeader("Content-Type", type); 635 } 636 637 /** 638 * @return value of content-type header 639 */ 640 @Override 641 public String getContentType() 642 { 643 return getHeader("Content-Type"); 644 } 645 646 /** 647 * Set a date header. 648 * 649 * @param name 650 * The header name 651 * @param l 652 * The long value 653 */ 654 @Override 655 public void setDateHeader(final String name, final long l) 656 { 657 setHeader(name, formatDate(l)); 658 } 659 660 /** 661 * @param l 662 * @return formatted date 663 */ 664 public static String formatDate(long l) 665 { 666 StringBuilder _dateBuffer = new StringBuilder(32); 667 Calendar _calendar = new GregorianCalendar(TimeZone.getTimeZone("GMT")); 668 _calendar.setTimeInMillis(l); 669 formatDate(_dateBuffer, _calendar, false); 670 return _dateBuffer.toString(); 671 } 672 673 /* BEGIN: This code comes from Jetty 6.1.1 */ 674 private static String[] DAYS = { "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; 675 private static String[] MONTHS = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", 676 "Sep", "Oct", "Nov", "Dec", "Jan" }; 677 678 /** 679 * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'" or "EEE, dd-MMM-yy HH:mm:ss 'GMT'"for 680 * cookies 681 * 682 * @param buf 683 * @param calendar 684 * @param cookie 685 */ 686 public static void formatDate(StringBuilder buf, Calendar calendar, boolean cookie) 687 { 688 // "EEE, dd MMM yyyy HH:mm:ss 'GMT'" 689 // "EEE, dd-MMM-yy HH:mm:ss 'GMT'", cookie 690 691 int day_of_week = calendar.get(Calendar.DAY_OF_WEEK); 692 int day_of_month = calendar.get(Calendar.DAY_OF_MONTH); 693 int month = calendar.get(Calendar.MONTH); 694 int year = calendar.get(Calendar.YEAR); 695 int century = year / 100; 696 year = year % 100; 697 698 int epoch = (int)((calendar.getTimeInMillis() / 1000) % (60 * 60 * 24)); 699 int seconds = epoch % 60; 700 epoch = epoch / 60; 701 int minutes = epoch % 60; 702 int hours = epoch / 60; 703 704 buf.append(DAYS[day_of_week]); 705 buf.append(','); 706 buf.append(' '); 707 append2digits(buf, day_of_month); 708 709 if (cookie) 710 { 711 buf.append('-'); 712 buf.append(MONTHS[month]); 713 buf.append('-'); 714 append2digits(buf, year); 715 } 716 else 717 { 718 buf.append(' '); 719 buf.append(MONTHS[month]); 720 buf.append(' '); 721 append2digits(buf, century); 722 append2digits(buf, year); 723 } 724 buf.append(' '); 725 append2digits(buf, hours); 726 buf.append(':'); 727 append2digits(buf, minutes); 728 buf.append(':'); 729 append2digits(buf, seconds); 730 buf.append(" GMT"); 731 } 732 733 /** 734 * @param buf 735 * @param i 736 */ 737 public static void append2digits(StringBuilder buf, int i) 738 { 739 if (i < 100) 740 { 741 buf.append((char)(i / 10 + '0')); 742 buf.append((char)(i % 10 + '0')); 743 } 744 } 745 746 /* END: This code comes from Jetty 6.1.1 */ 747 748 /** 749 * Set the given header value. 750 * 751 * @param name 752 * The name for the header 753 * @param value 754 * The value for the header 755 */ 756 @Override 757 public void setHeader(final String name, final String value) 758 { 759 List<String> l = new ArrayList<String>(1); 760 l.add(value); 761 headers.put(name, l); 762 } 763 764 /** 765 * Set an int header value. 766 * 767 * @param name 768 * The header name 769 * @param i 770 * The value 771 */ 772 @Override 773 public void setIntHeader(final String name, final int i) 774 { 775 setHeader(name, "" + i); 776 } 777 778 /** 779 * Set the locale in the response header. 780 * 781 * @param locale 782 * The locale 783 */ 784 @Override 785 public void setLocale(final Locale locale) 786 { 787 this.locale = locale; 788 } 789 790 /** 791 * Set the status for this response. 792 * 793 * @param status 794 * The status 795 */ 796 @Override 797 public void setStatus(final int status) 798 { 799 this.status = status; 800 } 801 802 /** 803 * Set the status for this response. 804 * 805 * @param status 806 * The status 807 * @param msg 808 * The message 809 * @deprecated 810 */ 811 @Override 812 @Deprecated 813 public void setStatus(final int status, final String msg) 814 { 815 setStatus(status); 816 } 817 818 /** 819 * @return binary response 820 */ 821 public String getBinaryResponse() 822 { 823 String ctheader = getHeader("Content-Length"); 824 if (ctheader == null) 825 { 826 return getDocument(); 827 } 828 else 829 { 830 return getDocument().substring(0, Integer.valueOf(ctheader)); 831 } 832 } 833 834 /** 835 * @param name 836 * @return headers with given name 837 */ 838 @Override 839 public Collection<String> getHeaders(String name) 840 { 841 return Collections.singletonList(headers.get(name).toString()); 842 } 843 844 @Override 845 public void writeMetaData(WebResponse webResponse) 846 { 847 for (Cookie cookie : cookies) 848 { 849 webResponse.addCookie(cookie); 850 } 851 for (String name : headers.keySet()) 852 { 853 webResponse.setHeader(name, headers.get(name).toString()); 854 } 855 webResponse.setStatus(status); 856 } 857}