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}