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