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 jakarta.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 * 451 * @param response 452 * the response to write to 453 * @param contents 454 * the contents 455 */ 456 protected abstract void writeComponent(Response response, String markupId, CharSequence contents); 457 458 /** 459 * Write priority-evaluation. 460 */ 461 protected abstract void writePriorityEvaluation(Response response, CharSequence contents); 462 463 /** 464 * Writes a header contribution to the response. 465 * 466 * @param response 467 * the response to write to 468 * @param contents 469 * the contents 470 */ 471 protected abstract void writeHeaderContribution(Response response, CharSequence contents); 472 473 /** 474 * Write evaluation. 475 */ 476 protected abstract void writeEvaluation(Response response, CharSequence contents); 477 478 @Override 479 public boolean equals(Object o) 480 { 481 if (this == o) return true; 482 if (o == null || getClass() != o.getClass()) return false; 483 484 PartialPageUpdate that = (PartialPageUpdate) o; 485 486 if (!appendJavaScripts.equals(that.appendJavaScripts)) return false; 487 if (!domReadyJavaScripts.equals(that.domReadyJavaScripts)) return false; 488 return prependJavaScripts.equals(that.prependJavaScripts); 489 } 490 491 @Override 492 public int hashCode() 493 { 494 int result = prependJavaScripts.hashCode(); 495 result = 31 * result + appendJavaScripts.hashCode(); 496 result = 31 * result + domReadyJavaScripts.hashCode(); 497 return result; 498 } 499 500 /** 501 * Adds script to the ones which are executed after the component replacement. 502 * 503 * @param javascript 504 * the javascript to execute 505 */ 506 public final void appendJavaScript(final CharSequence javascript) 507 { 508 Args.notNull(javascript, "javascript"); 509 510 if (javascriptsFrozen) 511 { 512 throw new IllegalStateException("A partial update of the page is being rendered, JavaScript can no longer be added"); 513 } 514 515 appendJavaScripts.add(javascript); 516 } 517 518 /** 519 * Adds script to the ones which are executed before the component replacement. 520 * 521 * @param javascript 522 * the javascript to execute 523 */ 524 public final void prependJavaScript(CharSequence javascript) 525 { 526 Args.notNull(javascript, "javascript"); 527 528 if (javascriptsFrozen) 529 { 530 throw new IllegalStateException("A partial update of the page is being rendered, JavaScript can no longer be added"); 531 } 532 533 prependJavaScripts.add(javascript); 534 } 535 536 /** 537 * Adds a component to be updated at the client side with its current markup 538 * 539 * @param component 540 * the component to update 541 * @param markupId 542 * the markup id to use to find the component in the page's markup 543 * @throws IllegalArgumentException 544 * thrown when a Page or an AbstractRepeater is added 545 * @throws IllegalStateException 546 * thrown when components no more can be added for replacement. 547 */ 548 public final void add(final Component component, final String markupId) 549 { 550 Args.notEmpty(markupId, "markupId"); 551 Args.notNull(component, "component"); 552 553 if (component instanceof Page) 554 { 555 if (component != page) 556 { 557 throw new IllegalArgumentException("Cannot add another page"); 558 } 559 } 560 else 561 { 562 Page pageOfComponent = component.findParent(Page.class); 563 if (pageOfComponent == null) 564 { 565 // no longer on page - log the error but don't block the user of the application 566 // (which was the behavior in Wicket <= 7). 567 LOG.warn("Component '{}' not cannot be updated because it was already removed from page", component); 568 return; 569 } 570 else if (pageOfComponent != page) 571 { 572 // on another page 573 throw new IllegalArgumentException("Component " + component.toString() + " cannot be updated because it is on another page."); 574 } 575 576 if (component instanceof AbstractRepeater) 577 { 578 throw new IllegalArgumentException( 579 "Component " + 580 Classes.name(component.getClass()) + 581 " is a repeater and cannot be added to a partial page update directly. " + 582 "Instead add its parent or another markup container higher in the hierarchy."); 583 } 584 } 585 586 if (componentsFrozen) 587 { 588 throw new IllegalStateException("A partial update of the page is being rendered, component " + component.toString() + " can no longer be added"); 589 } 590 591 component.setMarkupId(markupId); 592 markupIdToComponent.put(markupId, component); 593 } 594 595 /** 596 * @return a read-only collection of all components which have been added for replacement so far. 597 */ 598 public final Collection<? extends Component> getComponents() 599 { 600 return Collections.unmodifiableCollection(markupIdToComponent.values()); 601 } 602 603 /** 604 * Detaches the page if at least one of its components was updated. 605 * 606 * @param requestCycle 607 * the current request cycle 608 */ 609 public void detach(IRequestCycle requestCycle) 610 { 611 for (final Component component : markupIdToComponent.values()) { 612 final Page parentPage = component.findParent(Page.class); 613 if (parentPage != null) { 614 parentPage.detach(); 615 break; 616 } 617 } 618 } 619 620 /** 621 * Checks if the target contains an ancestor for the given component 622 * 623 * @param component 624 * the component which ancestors should be checked. 625 * @return <code>true</code> if target contains an ancestor for the given component 626 */ 627 protected boolean containsAncestorFor(Component component) 628 { 629 Component cursor = component.getParent(); 630 while (cursor != null) 631 { 632 if (markupIdToComponent.containsValue(cursor)) 633 { 634 return true; 635 } 636 cursor = cursor.getParent(); 637 } 638 return false; 639 } 640 641 /** 642 * @return {@code true} if the page has been added for replacement 643 */ 644 public boolean containsPage() 645 { 646 return markupIdToComponent.containsValue(page); 647 } 648 649 /** 650 * Gets or creates an IHeaderResponse instance to use for the header contributions. 651 * 652 * @return IHeaderResponse instance to use for the header contributions. 653 */ 654 public IHeaderResponse getHeaderResponse() 655 { 656 if (headerResponse == null) 657 { 658 // we don't need to decorate the header response here because this is called from 659 // within PartialHtmlHeaderContainer, which decorates the response 660 headerResponse = new PartialHeaderResponse(); 661 } 662 return headerResponse; 663 } 664 665 /** 666 * @param response 667 * the response to write to 668 * @param component 669 * to component which will contribute to the header 670 */ 671 protected void writeHeaderContribution(final Response response, final Component component) 672 { 673 headerRendering = true; 674 675 // create the htmlheadercontainer if needed 676 if (header == null) 677 { 678 header = new PartialHtmlHeaderContainer(this); 679 page.addOrReplace(header); 680 } 681 682 RequestCycle requestCycle = component.getRequestCycle(); 683 684 // save old response, set new 685 Response oldResponse = requestCycle.setResponse(headerBuffer); 686 687 try { 688 headerBuffer.reset(); 689 690 IHeaderRenderStrategy strategy = AbstractHeaderRenderStrategy.get(); 691 692 strategy.renderHeader(header, null, component); 693 } finally { 694 // revert to old response 695 requestCycle.setResponse(oldResponse); 696 } 697 698 // note: in almost all cases the header will be empty here, 699 // since all header items will be rendered later on close only 700 writeHeaderContribution(response, headerBuffer.getContents()); 701 headerRendering = false; 702 } 703 704 /** 705 * Sets the Content-Type header to indicate the type of the response. 706 * 707 * @param response 708 * the current we response 709 * @param encoding 710 * the encoding to use 711 */ 712 public abstract void setContentType(WebResponse response, String encoding); 713 714 /** 715 * Header container component for partial page updates. 716 * <p> 717 * This container is temporarily injected into the page to provide the 718 * {@link IHeaderResponse} while components are rendered. It is never 719 * rendered itself. 720 * 721 * @author Matej Knopp 722 */ 723 private static class PartialHtmlHeaderContainer extends HtmlHeaderContainer 724 { 725 private static final long serialVersionUID = 1L; 726 727 /** 728 * Keep transiently, in case the containing page gets serialized before 729 * this container is removed again. This happens when DebugBar determines 730 * the page size by serializing/deserializing it. 731 */ 732 private transient PartialPageUpdate pageUpdate; 733 734 /** 735 * Constructor. 736 * 737 * @param pageUpdate 738 * the partial page update 739 */ 740 public PartialHtmlHeaderContainer(PartialPageUpdate pageUpdate) 741 { 742 super(HtmlHeaderSectionHandler.HEADER_ID); 743 744 this.pageUpdate = pageUpdate; 745 } 746 747 /** 748 * 749 * @see org.apache.wicket.markup.html.internal.HtmlHeaderContainer#newHeaderResponse() 750 */ 751 @Override 752 protected IHeaderResponse newHeaderResponse() 753 { 754 if (pageUpdate == null) { 755 throw new IllegalStateException("disconnected from pageUpdate after serialization"); 756 } 757 758 return pageUpdate.getHeaderResponse(); 759 } 760 } 761 762 /** 763 * Header response for partial updates. 764 * 765 * @author Matej Knopp 766 */ 767 private class PartialHeaderResponse extends HeaderResponse 768 { 769 @Override 770 public void render(HeaderItem item) 771 { 772 while (item instanceof IWrappedHeaderItem) 773 { 774 item = ((IWrappedHeaderItem) item).getWrapped(); 775 } 776 777 if (item instanceof OnLoadHeaderItem) 778 { 779 if (!wasItemRendered(item)) 780 { 781 PartialPageUpdate.this.appendJavaScript(((OnLoadHeaderItem) item).getJavaScript()); 782 markItemRendered(item); 783 } 784 } 785 else if (item instanceof OnEventHeaderItem) 786 { 787 if (!wasItemRendered(item)) 788 { 789 PartialPageUpdate.this.appendJavaScript(((OnEventHeaderItem) item).getCompleteJavaScript()); 790 markItemRendered(item); 791 } 792 } 793 else if (item instanceof OnDomReadyHeaderItem) 794 { 795 if (!wasItemRendered(item)) 796 { 797 PartialPageUpdate.this.domReadyJavaScripts.add(((OnDomReadyHeaderItem)item).getJavaScript()); 798 markItemRendered(item); 799 } 800 } 801 else if (headerRendering) 802 { 803 super.render(item); 804 } 805 else 806 { 807 LOG.debug("Only methods that can be called on IHeaderResponse outside renderHead() are #render(OnLoadHeaderItem) and #render(OnDomReadyHeaderItem)"); 808 } 809 } 810 811 @Override 812 protected Response getRealResponse() 813 { 814 return RequestCycle.get().getResponse(); 815 } 816 } 817 818 /** 819 * Wrapper of a response that buffers its contents. 820 * 821 * @author Igor Vaynberg (ivaynberg) 822 * @author Sven Meier (svenmeier) 823 * 824 * @see ResponseBuffer#getContents() 825 * @see ResponseBuffer#reset() 826 */ 827 protected static final class ResponseBuffer extends WebResponse 828 { 829 private final AppendingStringBuffer buffer = new AppendingStringBuffer(256); 830 831 private final WebResponse originalResponse; 832 833 /** 834 * Constructor. 835 * 836 * @param originalResponse 837 * the original request cycle response 838 */ 839 private ResponseBuffer(WebResponse originalResponse) 840 { 841 this.originalResponse = originalResponse; 842 } 843 844 /** 845 * @see org.apache.wicket.request.Response#encodeURL(CharSequence) 846 */ 847 @Override 848 public String encodeURL(CharSequence url) 849 { 850 return originalResponse.encodeURL(url); 851 } 852 853 /** 854 * @return contents of the response 855 */ 856 public CharSequence getContents() 857 { 858 return buffer; 859 } 860 861 /** 862 * @see org.apache.wicket.request.Response#write(CharSequence) 863 */ 864 @Override 865 public void write(CharSequence cs) 866 { 867 buffer.append(cs); 868 } 869 870 /** 871 * Resets the response to a clean state so it can be reused to save on garbage. 872 */ 873 @Override 874 public void reset() 875 { 876 buffer.clear(); 877 } 878 879 @Override 880 public void write(byte[] array) 881 { 882 throw new UnsupportedOperationException("Cannot write binary data."); 883 } 884 885 @Override 886 public void write(byte[] array, int offset, int length) 887 { 888 throw new UnsupportedOperationException("Cannot write binary data."); 889 } 890 891 @Override 892 public Object getContainerResponse() 893 { 894 return originalResponse.getContainerResponse(); 895 } 896 897 @Override 898 public void addCookie(Cookie cookie) 899 { 900 originalResponse.addCookie(cookie); 901 } 902 903 @Override 904 public void clearCookie(Cookie cookie) 905 { 906 originalResponse.clearCookie(cookie); 907 } 908 909 @Override 910 public boolean isHeaderSupported() 911 { 912 return originalResponse.isHeaderSupported(); 913 } 914 915 @Override 916 public void setHeader(String name, String value) 917 { 918 originalResponse.setHeader(name, value); 919 } 920 921 @Override 922 public void addHeader(String name, String value) 923 { 924 originalResponse.addHeader(name, value); 925 } 926 927 @Override 928 public void setDateHeader(String name, Instant date) 929 { 930 originalResponse.setDateHeader(name, date); 931 } 932 933 @Override 934 public void setContentLength(long length) 935 { 936 originalResponse.setContentLength(length); 937 } 938 939 @Override 940 public void setContentType(String mimeType) 941 { 942 originalResponse.setContentType(mimeType); 943 } 944 945 @Override 946 public void setStatus(int sc) 947 { 948 originalResponse.setStatus(sc); 949 } 950 951 @Override 952 public void sendError(int sc, String msg) 953 { 954 originalResponse.sendError(sc, msg); 955 } 956 957 @Override 958 public String encodeRedirectURL(CharSequence url) 959 { 960 return originalResponse.encodeRedirectURL(url); 961 } 962 963 @Override 964 public void sendRedirect(String url) 965 { 966 originalResponse.sendRedirect(url); 967 } 968 969 @Override 970 public boolean isRedirect() 971 { 972 return originalResponse.isRedirect(); 973 } 974 975 @Override 976 public void flush() 977 { 978 originalResponse.flush(); 979 } 980 } 981}