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.request.handler.render; 018 019import java.util.List; 020 021import org.apache.wicket.Application; 022import org.apache.wicket.Session; 023import org.apache.wicket.core.request.handler.RenderPageRequestHandler; 024import org.apache.wicket.core.request.handler.RenderPageRequestHandler.RedirectPolicy; 025import org.apache.wicket.feedback.FeedbackCollector; 026import org.apache.wicket.feedback.FeedbackMessage; 027import org.apache.wicket.protocol.http.BufferedWebResponse; 028import org.apache.wicket.protocol.http.WebApplication; 029import org.apache.wicket.request.IRequestHandler; 030import org.apache.wicket.request.Request; 031import org.apache.wicket.request.Url; 032import org.apache.wicket.request.component.IRequestablePage; 033import org.apache.wicket.request.cycle.RequestCycle; 034import org.apache.wicket.request.http.WebRequest; 035import org.apache.wicket.request.http.WebResponse; 036import org.apache.wicket.util.lang.Objects; 037import org.slf4j.Logger; 038import org.slf4j.LoggerFactory; 039 040/** 041 * {@link PageRenderer} for web applications. 042 * 043 * @author Matej Knopp 044 */ 045public class WebPageRenderer extends PageRenderer 046{ 047 private static final Logger logger = LoggerFactory.getLogger(WebPageRenderer.class); 048 049 /** 050 * Construct. 051 * 052 * @param renderPageRequestHandler 053 */ 054 public WebPageRenderer(RenderPageRequestHandler renderPageRequestHandler) 055 { 056 super(renderPageRequestHandler); 057 } 058 059 protected boolean isAjax(RequestCycle requestCycle) 060 { 061 boolean isAjax = false; 062 063 Request request = requestCycle.getRequest(); 064 if (request instanceof WebRequest) 065 { 066 WebRequest webRequest = (WebRequest)request; 067 isAjax = webRequest.isAjax(); 068 } 069 070 return isAjax; 071 } 072 073 /** 074 * Store the buffered response at application level. If current session is 075 * temporary, a permanent one is created. 076 * 077 * @param url 078 * @param response 079 */ 080 protected void storeBufferedResponse(Url url, BufferedWebResponse response) 081 { 082 if (isSessionTemporary()) 083 { 084 Session.get().bind(); 085 } 086 087 WebApplication.get().storeBufferedResponse(getSessionId(), url, response); 088 } 089 090 /** 091 * Renders page to a {@link BufferedWebResponse}. All URLs in page will be rendered relative to 092 * <code>targetUrl</code> 093 * 094 * @param targetUrl 095 * @param requestCycle 096 * @return BufferedWebResponse containing page body 097 */ 098 protected BufferedWebResponse renderPage(Url targetUrl, RequestCycle requestCycle) 099 { 100 // get the page before checking for a scheduled request handler because 101 // the page may call setResponsePage in its constructor 102 IRequestablePage requestablePage = getPage(); 103 104 IRequestHandler scheduled = requestCycle.getRequestHandlerScheduledAfterCurrent(); 105 106 if (scheduled != null) 107 { 108 // no need to render 109 return null; 110 } 111 112 // keep the original response 113 final WebResponse originalResponse = (WebResponse)requestCycle.getResponse(); 114 115 // buffered web response for page 116 BufferedWebResponse response = new BufferedWebResponse(originalResponse); 117 118 // keep the original base URL 119 Url originalBaseUrl = requestCycle.getUrlRenderer().setBaseUrl(targetUrl); 120 121 try 122 { 123 requestCycle.setResponse(response); 124 requestablePage.renderPage(); 125 126 if (requestCycle.getRequestHandlerScheduledAfterCurrent() != null) 127 { 128 // This is a special case. 129 // During page render another request handler got scheduled and will want to 130 // overwrite the response, so we need to let it. 131 // Just preserve the meta data headers. Clear the initial actions because they are 132 // already copied into the new response's actions 133 originalResponse.reset(); 134 response.writeMetaData(originalResponse); 135 return null; 136 } 137 else 138 { 139 return response; 140 } 141 } 142 finally 143 { 144 // restore original response and base URL 145 requestCycle.setResponse(originalResponse); 146 requestCycle.getUrlRenderer().setBaseUrl(originalBaseUrl); 147 } 148 } 149 150 /** 151 * 152 * @param url 153 * @param requestCycle 154 */ 155 protected void redirectTo(Url url, RequestCycle requestCycle) 156 { 157 bindSessionIfNeeded(); 158 159 WebResponse response = (WebResponse)requestCycle.getResponse(); 160 String relativeUrl = requestCycle.getUrlRenderer().renderUrl(url); 161 response.sendRedirect(relativeUrl); 162 } 163 164 /** 165 * Bind the session if there are feedback messages pending. 166 * https://issues.apache.org/jira/browse/WICKET-5165 167 */ 168 private void bindSessionIfNeeded() 169 { 170 // check for session feedback messages only 171 FeedbackCollector collector = new FeedbackCollector(); 172 List<FeedbackMessage> feedbackMessages = collector.collect(); 173 if (feedbackMessages.size() > 0) 174 { 175 Session.get().bind(); 176 } 177 } 178 179 /* 180 * TODO: simplify the code below. See WICKET-3347 181 */ 182 @Override 183 public void respond(RequestCycle requestCycle) 184 { 185 Url currentUrl = requestCycle.getUrlRenderer().getBaseUrl(); 186 Url targetUrl = requestCycle.mapUrlFor(getRenderPageRequestHandler()); 187 188 // 189 // the code below is little hairy but we have to handle 3 redirect policies, 190 // 3 rendering strategies and two kind of requests (ajax and normal) 191 // 192 193 if (shouldRenderPageAndWriteResponse(requestCycle, currentUrl, targetUrl)) 194 { 195 BufferedWebResponse response = renderPage(currentUrl, requestCycle); 196 if (response != null) 197 { 198 response.writeTo((WebResponse)requestCycle.getResponse()); 199 } 200 } 201 else if (shouldRedirectToTargetUrl(requestCycle, currentUrl, targetUrl)) 202 { 203 204 redirectTo(targetUrl, requestCycle); 205 206 // note: if we had session here we would render the page to buffer and then 207 // redirect to URL generated *after* page has been rendered (the statelessness 208 // may change during render). this would save one redirect because now we have 209 // to render to URL generated *before* page is rendered, render the page, get 210 // URL after render and if the URL is different (meaning page is not stateless), 211 // save the buffer and redirect again (which is pretty much what the next step 212 // does) 213 } 214 else 215 { 216 if (isRedirectToBuffer() == false && logger.isDebugEnabled()) 217 { 218 String details = String 219 .format( 220 "redirect strategy: '%s', isAjax: '%s', redirect policy: '%s', " 221 + "current url: '%s', target url: '%s', is new: '%s', is stateless: '%s', is temporary: '%s'", 222 Application.get().getRequestCycleSettings().getRenderStrategy(), 223 isAjax(requestCycle), getRedirectPolicy(), currentUrl, targetUrl, 224 isNewPageInstance(), isPageStateless(), isSessionTemporary()); 225 logger 226 .debug("Falling back to Redirect_To_Buffer render strategy because none of the conditions " 227 + "matched. Details: " + details); 228 } 229 230 // force creation of possible stateful page to get the final target url 231 getPage(); 232 233 Url beforeRenderUrl = requestCycle.mapUrlFor(getRenderPageRequestHandler()); 234 235 // redirect to buffer 236 BufferedWebResponse response = renderPage(beforeRenderUrl, requestCycle); 237 238 if (response == null) 239 { 240 return; 241 } 242 243 // the url might have changed after page has been rendered (e.g. the 244 // stateless flag might have changed because stateful components 245 // were added) 246 final Url afterRenderUrl = requestCycle 247 .mapUrlFor(getRenderPageRequestHandler()); 248 249 if (beforeRenderUrl.getSegments().equals(afterRenderUrl.getSegments()) == false) 250 { 251 // the amount of segments is different - generated relative URLs 252 // will not work, we need to rerender the page. This can happen 253 // with IRequestHandlers that produce different URLs with 254 // different amount of segments for stateless and stateful pages 255 response = renderPage(afterRenderUrl, requestCycle); 256 } 257 258 if (currentUrl.equals(afterRenderUrl)) 259 { 260 // no need to redirect when both urls are exactly the same 261 response.writeTo((WebResponse)requestCycle.getResponse()); 262 } 263 // if page is still stateless after render 264 else if (isPageStateless() && !enableRedirectForStatelessPage()) 265 { 266 // we don't want the redirect to happen for stateless page 267 // example: 268 // when a normal mounted stateful page is hit at /mount/point 269 // wicket renders the page to buffer and redirects to /mount/point?12 270 // but for stateless page the redirect is not necessary 271 // also for request listeners on stateful page we want to redirect 272 // after the listener is invoked, but on stateless page the user 273 // must ask for redirect explicitly 274 response.writeTo((WebResponse)requestCycle.getResponse()); 275 } 276 else 277 { 278 storeBufferedResponse(afterRenderUrl, response); 279 280 redirectTo(afterRenderUrl, requestCycle); 281 } 282 } 283 } 284 285 protected boolean isPageStateless() 286 { 287 return getPage().isPageStateless(); 288 } 289 290 protected boolean isNewPageInstance() 291 { 292 return !getPageProvider().hasPageInstance(); 293 } 294 295 protected boolean shouldPreserveClientUrl(RequestCycle requestCycle) 296 { 297 return ((WebRequest)requestCycle.getRequest()).shouldPreserveClientUrl(); 298 } 299 300 /** 301 * Should the client be redirected to target url. 302 */ 303 protected boolean shouldRedirectToTargetUrl(RequestCycle cycle, Url currentUrl, Url targetUrl) 304 { 305 return alwaysRedirect(getRedirectPolicy()) // 306 || isRedirectToRender() // 307 || (isAjax(cycle) && targetUrl.equals(currentUrl)) // 308 || (!targetUrl.equals(currentUrl) && (isNewPageInstance() || (isSessionTemporary() && isPageStateless()))); 309 // if target URL is different and session is temporary and page is stateless 310 // this is special case when page is stateless but there is no session so we can't 311 // render it to buffer 312 313 // alternatively if URLs are different and we have a page class and not an instance we 314 // can redirect to the url which will instantiate the instance of us 315 } 316 317 /** 318 * Should the page be rendered immediately. 319 */ 320 protected boolean shouldRenderPageAndWriteResponse(RequestCycle cycle, Url currentUrl, 321 Url targetUrl) 322 { 323 // WICKET-5484 never render and write for Ajax requests 324 if (isAjax(cycle)) 325 { 326 return false; 327 } 328 329 return (compatibleProtocols(currentUrl.getProtocol(), targetUrl.getProtocol())) && 330 (neverRedirect(getRedirectPolicy()) 331 || ((isOnePassRender() && notForcedRedirect(getRedirectPolicy())) || (targetUrl 332 .equals(currentUrl) && (!isNewPageInstance() && !isPageStateless()))) || (targetUrl.equals(currentUrl) && isRedirectToRender()) 333 || (shouldPreserveClientUrl(cycle) && notForcedRedirect(getRedirectPolicy()))); 334 } 335 336 private static boolean neverRedirect(RedirectPolicy redirectPolicy) 337 { 338 return redirectPolicy == RedirectPolicy.NEVER_REDIRECT; 339 } 340 341 private static boolean alwaysRedirect(RedirectPolicy redirectPolicy) 342 { 343 return redirectPolicy == RedirectPolicy.ALWAYS_REDIRECT; 344 } 345 346 private static boolean notForcedRedirect(RedirectPolicy redirectPolicy) 347 { 348 return !alwaysRedirect(redirectPolicy); 349 } 350 351 /** 352 * Compares the protocols of two {@link Url}s 353 * 354 * @param p1 355 * the first protocol 356 * @param p2 357 * the second protocol 358 * @return {@code false} if the protocols are both non-null and not equal, 359 * {@code true} - otherwise 360 */ 361 protected boolean compatibleProtocols(String p1, String p2) 362 { 363 if (p1 != null && p2 != null) 364 { 365 return Objects.equal(p1, p2); 366 } 367 368 return true; 369 } 370}