/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.wicket.request.handler.render; import java.util.List; import org.apache.wicket.Application; import org.apache.wicket.Session; import org.apache.wicket.core.request.handler.RenderPageRequestHandler; import org.apache.wicket.core.request.handler.RenderPageRequestHandler.RedirectPolicy; import org.apache.wicket.feedback.FeedbackCollector; import org.apache.wicket.feedback.FeedbackMessage; import org.apache.wicket.protocol.http.BufferedWebResponse; import org.apache.wicket.protocol.http.WebApplication; import org.apache.wicket.request.IRequestHandler; import org.apache.wicket.request.Request; import org.apache.wicket.request.Url; import org.apache.wicket.request.component.IRequestablePage; import org.apache.wicket.request.cycle.RequestCycle; import org.apache.wicket.request.http.WebRequest; import org.apache.wicket.request.http.WebResponse; import org.apache.wicket.util.lang.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * {@link PageRenderer} for web applications. * * @author Matej Knopp */ public class WebPageRenderer extends PageRenderer { private static final Logger logger = LoggerFactory.getLogger(WebPageRenderer.class); /** * Construct. * * @param renderPageRequestHandler */ public WebPageRenderer(RenderPageRequestHandler renderPageRequestHandler) { super(renderPageRequestHandler); } protected boolean isAjax(RequestCycle requestCycle) { boolean isAjax = false; Request request = requestCycle.getRequest(); if (request instanceof WebRequest) { WebRequest webRequest = (WebRequest)request; isAjax = webRequest.isAjax(); } return isAjax; } /** * Store the buffered response at application level. If current session is * temporary, a permanent one is created. * * @param url * @param response */ protected void storeBufferedResponse(Url url, BufferedWebResponse response) { if (isSessionTemporary()) { Session.get().bind(); } WebApplication.get().storeBufferedResponse(getSessionId(), url, response); } /** * Renders page to a {@link BufferedWebResponse}. All URLs in page will be rendered relative to * <code>targetUrl</code> * * @param targetUrl * @param requestCycle * @return BufferedWebResponse containing page body */ protected BufferedWebResponse renderPage(Url targetUrl, RequestCycle requestCycle) { // get the page before checking for a scheduled request handler because // the page may call setResponsePage in its constructor IRequestablePage requestablePage = getPage(); IRequestHandler scheduled = requestCycle.getRequestHandlerScheduledAfterCurrent(); if (scheduled != null) { // no need to render return null; } // keep the original response final WebResponse originalResponse = (WebResponse)requestCycle.getResponse(); // buffered web response for page BufferedWebResponse response = new BufferedWebResponse(originalResponse); // keep the original base URL Url originalBaseUrl = requestCycle.getUrlRenderer().setBaseUrl(targetUrl); try { requestCycle.setResponse(response); requestablePage.renderPage(); if (requestCycle.getRequestHandlerScheduledAfterCurrent() != null) { // This is a special case. // During page render another request handler got scheduled and will want to // overwrite the response, so we need to let it. // Just preserve the meta data headers. Clear the initial actions because they are // already copied into the new response's actions originalResponse.reset(); response.writeMetaData(originalResponse); return null; } else { return response; } } finally { // restore original response and base URL requestCycle.setResponse(originalResponse); requestCycle.getUrlRenderer().setBaseUrl(originalBaseUrl); } } /** * * @param url * @param requestCycle */ protected void redirectTo(Url url, RequestCycle requestCycle) { bindSessionIfNeeded(); WebResponse response = (WebResponse)requestCycle.getResponse(); String relativeUrl = requestCycle.getUrlRenderer().renderUrl(url); response.sendRedirect(relativeUrl); } /** * Bind the session if there are feedback messages pending. * https://issues.apache.org/jira/browse/WICKET-5165 */ private void bindSessionIfNeeded() { // check for session feedback messages only FeedbackCollector collector = new FeedbackCollector(); List<FeedbackMessage> feedbackMessages = collector.collect(); if (feedbackMessages.size() > 0) { Session.get().bind(); } } /* * TODO: simplify the code below. See WICKET-3347 */ @Override public void respond(RequestCycle requestCycle) { Url currentUrl = requestCycle.getUrlRenderer().getBaseUrl(); Url targetUrl = requestCycle.mapUrlFor(getRenderPageRequestHandler()); // // the code below is little hairy but we have to handle 3 redirect policies, // 3 rendering strategies and two kind of requests (ajax and normal) // if (shouldRenderPageAndWriteResponse(requestCycle, currentUrl, targetUrl)) { BufferedWebResponse response = renderPage(currentUrl, requestCycle); if (response != null) { response.writeTo((WebResponse)requestCycle.getResponse()); } } else if (shouldRedirectToTargetUrl(requestCycle, currentUrl, targetUrl)) { redirectTo(targetUrl, requestCycle); // note: if we had session here we would render the page to buffer and then // redirect to URL generated *after* page has been rendered (the statelessness // may change during render). this would save one redirect because now we have // to render to URL generated *before* page is rendered, render the page, get // URL after render and if the URL is different (meaning page is not stateless), // save the buffer and redirect again (which is pretty much what the next step // does) } else { if (isRedirectToBuffer() == false && logger.isDebugEnabled()) { String details = String .format( "redirect strategy: '%s', isAjax: '%s', redirect policy: '%s', " + "current url: '%s', target url: '%s', is new: '%s', is stateless: '%s', is temporary: '%s'", Application.get().getRequestCycleSettings().getRenderStrategy(), isAjax(requestCycle), getRedirectPolicy(), currentUrl, targetUrl, isNewPageInstance(), isPageStateless(), isSessionTemporary()); logger .debug("Falling back to Redirect_To_Buffer render strategy because none of the conditions " + "matched. Details: " + details); } // force creation of possible stateful page to get the final target url getPage(); Url beforeRenderUrl = requestCycle.mapUrlFor(getRenderPageRequestHandler()); // redirect to buffer BufferedWebResponse response = renderPage(beforeRenderUrl, requestCycle); if (response == null) { return; } // the url might have changed after page has been rendered (e.g. the // stateless flag might have changed because stateful components // were added) final Url afterRenderUrl = requestCycle .mapUrlFor(getRenderPageRequestHandler()); if (beforeRenderUrl.getSegments().equals(afterRenderUrl.getSegments()) == false) { // the amount of segments is different - generated relative URLs // will not work, we need to rerender the page. This can happen // with IRequestHandlers that produce different URLs with // different amount of segments for stateless and stateful pages response = renderPage(afterRenderUrl, requestCycle); } if (currentUrl.equals(afterRenderUrl)) { // no need to redirect when both urls are exactly the same response.writeTo((WebResponse)requestCycle.getResponse()); } // if page is still stateless after render else if (isPageStateless() && !enableRedirectForStatelessPage()) { // we don't want the redirect to happen for stateless page // example: // when a normal mounted stateful page is hit at /mount/point // wicket renders the page to buffer and redirects to /mount/point?12 // but for stateless page the redirect is not necessary // also for request listeners on stateful page we want to redirect // after the listener is invoked, but on stateless page the user // must ask for redirect explicitly response.writeTo((WebResponse)requestCycle.getResponse()); } else { storeBufferedResponse(afterRenderUrl, response); redirectTo(afterRenderUrl, requestCycle); } } } protected boolean isPageStateless() { return getPage().isPageStateless(); } protected boolean isNewPageInstance() { return getPageProvider().isNewPageInstance(); } protected boolean shouldPreserveClientUrl(RequestCycle requestCycle) { return ((WebRequest)requestCycle.getRequest()).shouldPreserveClientUrl(); } /** * Should the client be redirected to target url. */ protected boolean shouldRedirectToTargetUrl(RequestCycle cycle, Url currentUrl, Url targetUrl) { return alwaysRedirect(getRedirectPolicy()) // || isRedirectToRender() // || (isAjax(cycle) && targetUrl.equals(currentUrl)) // || (!targetUrl.equals(currentUrl) && (isNewPageInstance() || (isSessionTemporary() && isPageStateless()))); // if target URL is different and session is temporary and page is stateless // this is special case when page is stateless but there is no session so we can't // render it to buffer // alternatively if URLs are different and we have a page class and not an instance we // can redirect to the url which will instantiate the instance of us } /** * Should the page be rendered immediately. */ protected boolean shouldRenderPageAndWriteResponse(RequestCycle cycle, Url currentUrl, Url targetUrl) { // WICKET-5484 never render and write for Ajax requests if (isAjax(cycle)) { return false; } return (compatibleProtocols(currentUrl.getProtocol(), targetUrl.getProtocol())) && (neverRedirect(getRedirectPolicy()) || ((isOnePassRender() && notForcedRedirect(getRedirectPolicy())) || (targetUrl .equals(currentUrl) && notNewAndNotStatelessPage(isNewPageInstance(), isPageStateless()))) || (targetUrl.equals(currentUrl) && isRedirectToRender()) || (shouldPreserveClientUrl(cycle) && notForcedRedirect(getRedirectPolicy()))); } private static boolean notNewAndNotStatelessPage(boolean newPageInstance, boolean pageStateless) { return !newPageInstance && !pageStateless; } private static boolean neverRedirect(RedirectPolicy redirectPolicy) { return redirectPolicy == RedirectPolicy.NEVER_REDIRECT; } private static boolean alwaysRedirect(RedirectPolicy redirectPolicy) { return redirectPolicy == RedirectPolicy.ALWAYS_REDIRECT; } private static boolean notForcedRedirect(RedirectPolicy redirectPolicy) { return !alwaysRedirect(redirectPolicy); } /** * Compares the protocols of two {@link Url}s * * @param p1 * the first protocol * @param p2 * the second protocol * @return {@code false} if the protocols are both non-null and not equal, * {@code true} - otherwise */ protected boolean compatibleProtocols(String p1, String p2) { if (p1 != null && p2 != null) { return Objects.equal(p1, p2); } return true; } }