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.page; 018 019import java.time.Instant; 020import java.util.ArrayList; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.LinkedHashMap; 024import java.util.List; 025import java.util.Map; 026import javax.servlet.http.Cookie; 027 028import org.apache.wicket.Application; 029import org.apache.wicket.Component; 030import org.apache.wicket.Page; 031import org.apache.wicket.behavior.Behavior; 032import org.apache.wicket.feedback.FeedbackDelay; 033import org.apache.wicket.markup.head.HeaderItem; 034import org.apache.wicket.markup.head.IHeaderResponse; 035import org.apache.wicket.markup.head.IWrappedHeaderItem; 036import org.apache.wicket.markup.head.JavaScriptHeaderItem; 037import org.apache.wicket.markup.head.OnDomReadyHeaderItem; 038import org.apache.wicket.markup.head.OnEventHeaderItem; 039import org.apache.wicket.markup.head.OnLoadHeaderItem; 040import org.apache.wicket.markup.head.internal.HeaderResponse; 041import org.apache.wicket.markup.html.internal.HtmlHeaderContainer; 042import org.apache.wicket.markup.parser.filter.HtmlHeaderSectionHandler; 043import org.apache.wicket.markup.renderStrategy.AbstractHeaderRenderStrategy; 044import org.apache.wicket.markup.renderStrategy.IHeaderRenderStrategy; 045import org.apache.wicket.markup.repeater.AbstractRepeater; 046import org.apache.wicket.request.IRequestCycle; 047import org.apache.wicket.request.Response; 048import org.apache.wicket.request.cycle.RequestCycle; 049import org.apache.wicket.request.http.WebResponse; 050import org.apache.wicket.response.StringResponse; 051import org.apache.wicket.util.lang.Args; 052import org.apache.wicket.util.lang.Classes; 053import org.apache.wicket.util.lang.Generics; 054import org.apache.wicket.util.string.AppendingStringBuffer; 055import org.slf4j.Logger; 056import org.slf4j.LoggerFactory; 057 058/** 059 * A partial update of a page that collects components and header contributions to be written to the 060 * client in a specific String-based format (XML, JSON, * ...). 061 * <p> 062 * The elements of such response are: 063 * <ul> 064 * <li>component - the markup of the updated component</li> 065 * <li>header-contribution - all HeaderItems which have been contributed in any{@link Component#renderHead(IHeaderResponse)}, 066 * {@link Behavior#renderHead(Component, IHeaderResponse)} or JavaScript explicitly added via {@link #appendJavaScript(CharSequence)} 067 * or {@link #prependJavaScript(CharSequence)}</li> 068 * </ul> 069 */ 070public abstract class PartialPageUpdate 071{ 072 private static final Logger LOG = LoggerFactory.getLogger(PartialPageUpdate.class); 073 074 /** 075 * Length of the script block that combined scripts are wrapped in. This includes the script tag, 076 * CDATA and if CSP is enabled also the nonce. 077 */ 078 private static final int SCRIPT_BLOCK_LENGTH = 100; 079 080 /** 081 * A list of scripts (JavaScript) which should be executed on the client side before the 082 * components' replacement 083 */ 084 protected final List<CharSequence> prependJavaScripts = Generics.newArrayList(); 085 086 /** 087 * A list of scripts (JavaScript) which should be executed on the client side after the 088 * components' replacement 089 */ 090 protected final List<CharSequence> appendJavaScripts = Generics.newArrayList(); 091 092 /** 093 * A list of scripts (JavaScript) which should be executed on the client side after the 094 * components' replacement. 095 * Executed immediately after the replacement of the components, and before appendJavaScripts 096 */ 097 protected final List<CharSequence> domReadyJavaScripts = Generics.newArrayList(); 098 099 /** 100 * The component instances that will be rendered/replaced. 101 */ 102 protected final Map<String, Component> markupIdToComponent = new LinkedHashMap<>(); 103 104 /** 105 * A flag that indicates that components cannot be added anymore. 106 * See https://issues.apache.org/jira/browse/WICKET-3564 107 * 108 * @see #add(Component, String) 109 */ 110 protected transient boolean componentsFrozen; 111 112 /** 113 * A flag that indicates that javascripts cannot be added anymore. 114 * See https://issues.apache.org/jira/browse/WICKET-6902 115 */ 116 protected transient boolean javascriptsFrozen; 117 118 /** 119 * Buffer of response body. 120 */ 121 protected final ResponseBuffer bodyBuffer; 122 123 /** 124 * Buffer of response header. 125 */ 126 protected final ResponseBuffer headerBuffer; 127 128 protected HtmlHeaderContainer header = null; 129 130 private Component originalHeaderContainer; 131 132 // whether a header contribution is being rendered 133 private boolean headerRendering = false; 134 135 private IHeaderResponse headerResponse; 136 137 /** 138 * The page which components are being updated. 139 */ 140 private final Page page; 141 142 /** 143 * Constructor. 144 * 145 * @param page 146 * the page which components are being updated. 147 */ 148 public PartialPageUpdate(final Page page) 149 { 150 this.page = page; 151 this.originalHeaderContainer = page.get(HtmlHeaderSectionHandler.HEADER_ID); 152 153 WebResponse response = (WebResponse) page.getResponse(); 154 bodyBuffer = new ResponseBuffer(response); 155 headerBuffer = new ResponseBuffer(response); 156 } 157 158 /** 159 * @return returns true if and only if nothing has being added to partial update. 160 */ 161 public boolean isEmpty() 162 { 163 return prependJavaScripts.isEmpty() && appendJavaScripts.isEmpty() && domReadyJavaScripts.isEmpty() && markupIdToComponent.isEmpty(); 164 } 165 166 /** 167 * Serializes this object to the response. 168 * 169 * @param response 170 * the response to write to 171 * @param encoding 172 * the encoding for the response 173 */ 174 public void writeTo(final Response response, final String encoding) 175 { 176 try { 177 writeHeader(response, encoding); 178 179 onBeforeRespond(response); 180 181 // process added components 182 writeComponents(response, encoding); 183 184 onAfterRespond(response); 185 186 javascriptsFrozen = true; 187 188 // queue up prepend javascripts. unlike other steps these are executed out of order so that 189 // components can contribute them from during rendering. 190 writePriorityEvaluations(response, prependJavaScripts); 191 192 // execute the dom ready javascripts as first javascripts 193 // after component replacement 194 List<CharSequence> evaluationScripts = new ArrayList<>(); 195 evaluationScripts.addAll(domReadyJavaScripts); 196 evaluationScripts.addAll(appendJavaScripts); 197 writeEvaluations(response, evaluationScripts); 198 199 writeFooter(response, encoding); 200 } finally { 201 if (header != null && originalHeaderContainer != null) { 202 // restore a normal header 203 page.replace(originalHeaderContainer); 204 header = null; 205 } 206 } 207 } 208 209 /** 210 * Hook-method called before components are written. 211 * 212 * @param response 213 */ 214 protected void onBeforeRespond(Response response) { 215 } 216 217 /** 218 * Hook-method called after components are written. 219 * 220 * @param response 221 */ 222 protected void onAfterRespond(Response response) { 223 } 224 225 /** 226 * @param response 227 * the response to write to 228 * @param encoding 229 * the encoding for the response 230 */ 231 protected abstract void writeFooter(Response response, String encoding); 232 233 /** 234 * 235 * @param response 236 * the response to write to 237 * @param scripts 238 * the JavaScripts to evaluate 239 */ 240 protected void writePriorityEvaluations(final Response response, Collection<CharSequence> scripts) 241 { 242 if (!scripts.isEmpty()) 243 { 244 CharSequence contents = renderScripts(scripts); 245 246 writePriorityEvaluation(response, contents); 247 } 248 } 249 250 /** 251 * 252 * @param response 253 * the response to write to 254 * @param scripts 255 * the JavaScripts to evaluate 256 */ 257 protected void writeEvaluations(final Response response, Collection<CharSequence> scripts) 258 { 259 if (!scripts.isEmpty()) 260 { 261 CharSequence contents = renderScripts(scripts); 262 263 writeEvaluation(response, contents); 264 } 265 } 266 267 private CharSequence renderScripts(Collection<CharSequence> scripts) { 268 StringBuilder combinedScript = new StringBuilder(1024); 269 for (CharSequence script : scripts) 270 { 271 combinedScript.append("(function(){").append(script).append("})();"); 272 } 273 274 StringResponse stringResponse = new StringResponse(combinedScript.length() + SCRIPT_BLOCK_LENGTH); 275 IHeaderResponse decoratedHeaderResponse = Application.get().decorateHeaderResponse(new HeaderResponse() 276 { 277 @Override 278 protected Response getRealResponse() 279 { 280 return stringResponse; 281 } 282 }); 283 284 decoratedHeaderResponse.render(JavaScriptHeaderItem.forScript(combinedScript, null)); 285 decoratedHeaderResponse.close(); 286 287 return stringResponse.getBuffer(); 288 } 289 290 /** 291 * Processes components added to the target. This involves attaching components, rendering 292 * markup into a client side xml envelope, and detaching them 293 * 294 * @param response 295 * the response to write to 296 * @param encoding 297 * the encoding for the response 298 */ 299 private void writeComponents(Response response, String encoding) 300 { 301 componentsFrozen = true; 302 303 List<Component> toBeWritten = new ArrayList<>(markupIdToComponent.size()); 304 305 // delay preparation of feedbacks after all other components 306 try (FeedbackDelay delay = new FeedbackDelay(RequestCycle.get())) { 307 for (Component component : markupIdToComponent.values()) 308 { 309 if (!containsAncestorFor(component) && prepareComponent(component)) { 310 toBeWritten.add(component); 311 } 312 } 313 314 // .. now prepare all postponed feedbacks 315 delay.beforeRender(); 316 } 317 318 // write components 319 for (Component component : toBeWritten) 320 { 321 writeComponent(response, component.getAjaxRegionMarkupId(), component, encoding); 322 } 323 324 if (header != null) 325 { 326 RequestCycle cycle = RequestCycle.get(); 327 328 // some header responses buffer all calls to render*** until close is called. 329 // when they are closed, they do something (i.e. aggregate all JS resource urls to a 330 // single url), and then "flush" (by writing to the real response) before closing. 331 // to support this, we need to allow header contributions to be written in the close 332 // tag, which we do here: 333 headerRendering = true; 334 // save old response, set new 335 Response oldResponse = cycle.setResponse(headerBuffer); 336 headerBuffer.reset(); 337 338 // now, close the response (which may render things) 339 header.getHeaderResponse().close(); 340 341 // revert to old response 342 cycle.setResponse(oldResponse); 343 344 // write the XML tags and we're done 345 writeHeaderContribution(response, headerBuffer.getContents()); 346 headerRendering = false; 347 } 348 } 349 350 /** 351 * Prepare a single component 352 * 353 * @param component 354 * the component to prepare 355 * @return whether the component was prepared 356 */ 357 protected boolean prepareComponent(Component component) 358 { 359 if (component.getRenderBodyOnly()) 360 { 361 throw new IllegalStateException( 362 "A partial update is not possible for a component that has renderBodyOnly enabled. Component: " + 363 component.toString()); 364 } 365 366 component.setOutputMarkupId(true); 367 368 // Initialize temporary variables 369 final Page parentPage = component.findParent(Page.class); 370 if (parentPage == null) 371 { 372 // dont throw an exception but just ignore this component, somehow 373 // it got removed from the page. 374 LOG.warn("Component '{}' not rendered because it was already removed from page", component); 375 return false; 376 } 377 378 try 379 { 380 component.beforeRender(); 381 } 382 catch (RuntimeException e) 383 { 384 bodyBuffer.reset(); 385 throw e; 386 } 387 388 return true; 389 } 390 391 /** 392 * Writes a single component 393 * 394 * @param response 395 * the response to write to 396 * @param markupId 397 * the markup id to use for the component replacement 398 * @param component 399 * the component which markup will be used as replacement 400 * @param encoding 401 * the encoding for the response 402 */ 403 protected void writeComponent(Response response, String markupId, Component component, String encoding) 404 { 405 // substitute our encoding response for the old one so we can capture 406 // component's markup in a manner safe for transport inside CDATA block 407 Response oldResponse = RequestCycle.get().setResponse(bodyBuffer); 408 409 try 410 { 411 // render any associated headers of the component 412 writeHeaderContribution(response, component); 413 414 bodyBuffer.reset(); 415 416 try 417 { 418 component.renderPart(); 419 } 420 catch (RuntimeException e) 421 { 422 bodyBuffer.reset(); 423 throw e; 424 } 425 } 426 finally 427 { 428 // Restore original response 429 RequestCycle.get().setResponse(oldResponse); 430 } 431 432 writeComponent(response, markupId, bodyBuffer.getContents()); 433 434 bodyBuffer.reset(); 435 } 436 437 /** 438 * Writes the head part of the response. 439 * For example XML preamble 440 * 441 * @param response 442 * the response to write to 443 * @param encoding 444 * the encoding for the response 445 */ 446 protected abstract void writeHeader(Response response, String encoding); 447 448 /** 449 * Writes a component to the response. 450 * <p> 451 * TODO make abstract in Wicket 10 452 * 453 * @param response 454 * the response to write to 455 * @param contents 456 * the contents 457 */ 458 protected void writeComponent(Response response, String markupId, CharSequence contents) { 459 throw new UnsupportedOperationException(); 460 } 461 462 /** 463 * TODO make abstract in Wicket 10 464 */ 465 protected void writePriorityEvaluation(Response response, CharSequence contents) { 466 throw new UnsupportedOperationException(); 467 } 468 469 /** 470 * Writes a header contribution to the response. 471 * 472 * @param response 473 * the response to write to 474 * @param contents 475 * the contents 476 */ 477 protected abstract void writeHeaderContribution(Response response, CharSequence contents); 478 479 /** 480 * TODO make abstract in Wicket 10 481 */ 482 protected void writeEvaluation(Response response, CharSequence contents) { 483 throw new UnsupportedOperationException(); 484 } 485 486 @Override 487 public boolean equals(Object o) 488 { 489 if (this == o) return true; 490 if (o == null || getClass() != o.getClass()) return false; 491 492 PartialPageUpdate that = (PartialPageUpdate) o; 493 494 if (!appendJavaScripts.equals(that.appendJavaScripts)) return false; 495 if (!domReadyJavaScripts.equals(that.domReadyJavaScripts)) return false; 496 return prependJavaScripts.equals(that.prependJavaScripts); 497 } 498 499 @Override 500 public int hashCode() 501 { 502 int result = prependJavaScripts.hashCode(); 503 result = 31 * result + appendJavaScripts.hashCode(); 504 result = 31 * result + domReadyJavaScripts.hashCode(); 505 return result; 506 } 507 508 /** 509 * Adds script to the ones which are executed after the component replacement. 510 * 511 * @param javascript 512 * the javascript to execute 513 */ 514 public final void appendJavaScript(final CharSequence javascript) 515 { 516 Args.notNull(javascript, "javascript"); 517 518 if (javascriptsFrozen) 519 { 520 throw new IllegalStateException("A partial update of the page is being rendered, JavaScript can no longer be added"); 521 } 522 523 appendJavaScripts.add(javascript); 524 } 525 526 /** 527 * Adds script to the ones which are executed before the component replacement. 528 * 529 * @param javascript 530 * the javascript to execute 531 */ 532 public final void prependJavaScript(CharSequence javascript) 533 { 534 Args.notNull(javascript, "javascript"); 535 536 if (javascriptsFrozen) 537 { 538 throw new IllegalStateException("A partial update of the page is being rendered, JavaScript can no longer be added"); 539 } 540 541 prependJavaScripts.add(javascript); 542 } 543 544 /** 545 * Adds a component to be updated at the client side with its current markup 546 * 547 * @param component 548 * the component to update 549 * @param markupId 550 * the markup id to use to find the component in the page's markup 551 * @throws IllegalArgumentException 552 * thrown when a Page or an AbstractRepeater is added 553 * @throws IllegalStateException 554 * thrown when components no more can be added for replacement. 555 */ 556 public final void add(final Component component, final String markupId) 557 { 558 Args.notEmpty(markupId, "markupId"); 559 Args.notNull(component, "component"); 560 561 if (component instanceof Page) 562 { 563 if (component != page) 564 { 565 throw new IllegalArgumentException("Cannot add another page"); 566 } 567 } 568 else 569 { 570 Page pageOfComponent = component.findParent(Page.class); 571 if (pageOfComponent == null) 572 { 573 // no longer on page - log the error but don't block the user of the application 574 // (which was the behavior in Wicket <= 7). 575 LOG.warn("Component '{}' not cannot be updated because it was already removed from page", component); 576 return; 577 } 578 else if (pageOfComponent != page) 579 { 580 // on another page 581 throw new IllegalArgumentException("Component " + component.toString() + " cannot be updated because it is on another page."); 582 } 583 584 if (component instanceof AbstractRepeater) 585 { 586 throw new IllegalArgumentException( 587 "Component " + 588 Classes.name(component.getClass()) + 589 " is a repeater and cannot be added to a partial page update directly. " + 590 "Instead add its parent or another markup container higher in the hierarchy."); 591 } 592 } 593 594 if (componentsFrozen) 595 { 596 throw new IllegalStateException("A partial update of the page is being rendered, component " + component.toString() + " can no longer be added"); 597 } 598 599 component.setMarkupId(markupId); 600 markupIdToComponent.put(markupId, component); 601 } 602 603 /** 604 * @return a read-only collection of all components which have been added for replacement so far. 605 */ 606 public final Collection<? extends Component> getComponents() 607 { 608 return Collections.unmodifiableCollection(markupIdToComponent.values()); 609 } 610 611 /** 612 * Detaches the page if at least one of its components was updated. 613 * 614 * @param requestCycle 615 * the current request cycle 616 */ 617 public void detach(IRequestCycle requestCycle) 618 { 619 for (final Component component : markupIdToComponent.values()) { 620 final Page parentPage = component.findParent(Page.class); 621 if (parentPage != null) { 622 parentPage.detach(); 623 break; 624 } 625 } 626 } 627 628 /** 629 * Checks if the target contains an ancestor for the given component 630 * 631 * @param component 632 * the component which ancestors should be checked. 633 * @return <code>true</code> if target contains an ancestor for the given component 634 */ 635 protected boolean containsAncestorFor(Component component) 636 { 637 Component cursor = component.getParent(); 638 while (cursor != null) 639 { 640 if (markupIdToComponent.containsValue(cursor)) 641 { 642 return true; 643 } 644 cursor = cursor.getParent(); 645 } 646 return false; 647 } 648 649 /** 650 * @return {@code true} if the page has been added for replacement 651 */ 652 public boolean containsPage() 653 { 654 return markupIdToComponent.containsValue(page); 655 } 656 657 /** 658 * Gets or creates an IHeaderResponse instance to use for the header contributions. 659 * 660 * @return IHeaderResponse instance to use for the header contributions. 661 */ 662 public IHeaderResponse getHeaderResponse() 663 { 664 if (headerResponse == null) 665 { 666 // we don't need to decorate the header response here because this is called from 667 // within PartialHtmlHeaderContainer, which decorates the response 668 headerResponse = new PartialHeaderResponse(); 669 } 670 return headerResponse; 671 } 672 673 /** 674 * @param response 675 * the response to write to 676 * @param component 677 * to component which will contribute to the header 678 */ 679 protected void writeHeaderContribution(final Response response, final Component component) 680 { 681 headerRendering = true; 682 683 // create the htmlheadercontainer if needed 684 if (header == null) 685 { 686 header = new PartialHtmlHeaderContainer(this); 687 page.addOrReplace(header); 688 } 689 690 RequestCycle requestCycle = component.getRequestCycle(); 691 692 // save old response, set new 693 Response oldResponse = requestCycle.setResponse(headerBuffer); 694 695 try { 696 headerBuffer.reset(); 697 698 IHeaderRenderStrategy strategy = AbstractHeaderRenderStrategy.get(); 699 700 strategy.renderHeader(header, null, component); 701 } finally { 702 // revert to old response 703 requestCycle.setResponse(oldResponse); 704 } 705 706 // note: in almost all cases the header will be empty here, 707 // since all header items will be rendered later on close only 708 writeHeaderContribution(response, headerBuffer.getContents()); 709 headerRendering = false; 710 } 711 712 /** 713 * Sets the Content-Type header to indicate the type of the response. 714 * 715 * @param response 716 * the current we response 717 * @param encoding 718 * the encoding to use 719 */ 720 public abstract void setContentType(WebResponse response, String encoding); 721 722 /** 723 * Header container component for partial page updates. 724 * <p> 725 * This container is temporarily injected into the page to provide the 726 * {@link IHeaderResponse} while components are rendered. It is never 727 * rendered itself. 728 * 729 * @author Matej Knopp 730 */ 731 private static class PartialHtmlHeaderContainer extends HtmlHeaderContainer 732 { 733 private static final long serialVersionUID = 1L; 734 735 /** 736 * Keep transiently, in case the containing page gets serialized before 737 * this container is removed again. This happens when DebugBar determines 738 * the page size by serializing/deserializing it. 739 */ 740 private transient PartialPageUpdate pageUpdate; 741 742 /** 743 * Constructor. 744 * 745 * @param pageUpdate 746 * the partial page update 747 */ 748 public PartialHtmlHeaderContainer(PartialPageUpdate pageUpdate) 749 { 750 super(HtmlHeaderSectionHandler.HEADER_ID); 751 752 this.pageUpdate = pageUpdate; 753 } 754 755 /** 756 * 757 * @see org.apache.wicket.markup.html.internal.HtmlHeaderContainer#newHeaderResponse() 758 */ 759 @Override 760 protected IHeaderResponse newHeaderResponse() 761 { 762 if (pageUpdate == null) { 763 throw new IllegalStateException("disconnected from pageUpdate after serialization"); 764 } 765 766 return pageUpdate.getHeaderResponse(); 767 } 768 } 769 770 /** 771 * Header response for partial updates. 772 * 773 * @author Matej Knopp 774 */ 775 private class PartialHeaderResponse extends HeaderResponse 776 { 777 @Override 778 public void render(HeaderItem item) 779 { 780 while (item instanceof IWrappedHeaderItem) 781 { 782 item = ((IWrappedHeaderItem) item).getWrapped(); 783 } 784 785 if (item instanceof OnLoadHeaderItem) 786 { 787 if (!wasItemRendered(item)) 788 { 789 PartialPageUpdate.this.appendJavaScript(((OnLoadHeaderItem) item).getJavaScript()); 790 markItemRendered(item); 791 } 792 } 793 else if (item instanceof OnEventHeaderItem) 794 { 795 if (!wasItemRendered(item)) 796 { 797 PartialPageUpdate.this.appendJavaScript(((OnEventHeaderItem) item).getCompleteJavaScript()); 798 markItemRendered(item); 799 } 800 } 801 else if (item instanceof OnDomReadyHeaderItem) 802 { 803 if (!wasItemRendered(item)) 804 { 805 PartialPageUpdate.this.domReadyJavaScripts.add(((OnDomReadyHeaderItem)item).getJavaScript()); 806 markItemRendered(item); 807 } 808 } 809 else if (headerRendering) 810 { 811 super.render(item); 812 } 813 else 814 { 815 LOG.debug("Only methods that can be called on IHeaderResponse outside renderHead() are #render(OnLoadHeaderItem) and #render(OnDomReadyHeaderItem)"); 816 } 817 } 818 819 @Override 820 protected Response getRealResponse() 821 { 822 return RequestCycle.get().getResponse(); 823 } 824 } 825 826 /** 827 * Wrapper of a response that buffers its contents. 828 * 829 * @author Igor Vaynberg (ivaynberg) 830 * @author Sven Meier (svenmeier) 831 * 832 * @see ResponseBuffer#getContents() 833 * @see ResponseBuffer#reset() 834 */ 835 protected static final class ResponseBuffer extends WebResponse 836 { 837 private final AppendingStringBuffer buffer = new AppendingStringBuffer(256); 838 839 private final WebResponse originalResponse; 840 841 /** 842 * Constructor. 843 * 844 * @param originalResponse 845 * the original request cycle response 846 */ 847 private ResponseBuffer(WebResponse originalResponse) 848 { 849 this.originalResponse = originalResponse; 850 } 851 852 /** 853 * @see org.apache.wicket.request.Response#encodeURL(CharSequence) 854 */ 855 @Override 856 public String encodeURL(CharSequence url) 857 { 858 return originalResponse.encodeURL(url); 859 } 860 861 /** 862 * @return contents of the response 863 */ 864 public CharSequence getContents() 865 { 866 return buffer; 867 } 868 869 /** 870 * @see org.apache.wicket.request.Response#write(CharSequence) 871 */ 872 @Override 873 public void write(CharSequence cs) 874 { 875 buffer.append(cs); 876 } 877 878 /** 879 * Resets the response to a clean state so it can be reused to save on garbage. 880 */ 881 @Override 882 public void reset() 883 { 884 buffer.clear(); 885 } 886 887 @Override 888 public void write(byte[] array) 889 { 890 throw new UnsupportedOperationException("Cannot write binary data."); 891 } 892 893 @Override 894 public void write(byte[] array, int offset, int length) 895 { 896 throw new UnsupportedOperationException("Cannot write binary data."); 897 } 898 899 @Override 900 public Object getContainerResponse() 901 { 902 return originalResponse.getContainerResponse(); 903 } 904 905 @Override 906 public void addCookie(Cookie cookie) 907 { 908 originalResponse.addCookie(cookie); 909 } 910 911 @Override 912 public void clearCookie(Cookie cookie) 913 { 914 originalResponse.clearCookie(cookie); 915 } 916 917 @Override 918 public boolean isHeaderSupported() 919 { 920 return originalResponse.isHeaderSupported(); 921 } 922 923 @Override 924 public void setHeader(String name, String value) 925 { 926 originalResponse.setHeader(name, value); 927 } 928 929 @Override 930 public void addHeader(String name, String value) 931 { 932 originalResponse.addHeader(name, value); 933 } 934 935 @Override 936 public void setDateHeader(String name, Instant date) 937 { 938 originalResponse.setDateHeader(name, date); 939 } 940 941 @Override 942 public void setContentLength(long length) 943 { 944 originalResponse.setContentLength(length); 945 } 946 947 @Override 948 public void setContentType(String mimeType) 949 { 950 originalResponse.setContentType(mimeType); 951 } 952 953 @Override 954 public void setStatus(int sc) 955 { 956 originalResponse.setStatus(sc); 957 } 958 959 @Override 960 public void sendError(int sc, String msg) 961 { 962 originalResponse.sendError(sc, msg); 963 } 964 965 @Override 966 public String encodeRedirectURL(CharSequence url) 967 { 968 return originalResponse.encodeRedirectURL(url); 969 } 970 971 @Override 972 public void sendRedirect(String url) 973 { 974 originalResponse.sendRedirect(url); 975 } 976 977 @Override 978 public boolean isRedirect() 979 { 980 return originalResponse.isRedirect(); 981 } 982 983 @Override 984 public void flush() 985 { 986 originalResponse.flush(); 987 } 988 } 989}