/*
* 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.settings;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.wicket.response.filter.IResponseFilter;
import org.apache.wicket.util.lang.Args;
import org.apache.wicket.util.time.Duration;
/**
* Class for request related settings
* <p>
* <i>bufferResponse </i> (defaults to true) - True if the application should buffer responses. This
* does require some additional memory, but helps keep exception displays accurate because the whole
* rendering process completes before the page is sent to the user, thus aRequestCycleSettingsing the possibility of
* a partially rendered page.
* <p>
* <i>renderStrategy </i>- Sets in what way the render part of a request is handled. Basically,
* there are two different options:
* <ul>
* <li>Direct, IRequestCycleSettings.RenderStrategy.ONE_PASS_RENDER. Everything is handled in one
* physical request. This is efficient, and is the best option if you want to do sophisticated
* clustering. It does not however, shield you from what is commonly known as the <i>Double submit
* problem </i></li>
* <li>Using a redirect. This follows the pattern <a
* href="http://www.theserverside.com/articles/article.tss?l=RedirectAfterPost" >as described at the
* serverside </a> and that is commonly known as Redirect after post. Wicket takes it one step
* further to do any rendering after a redirect, so that not only form submits are shielded from the
* double submit problem, but also the IRequestListener handlers (that could be e.g. a link that
* deletes a row). With this pattern, you have two options to choose from:
* <ul>
* <li>IRequestCycleSettings.RenderStrategy.REDIRECT_TO_RENDER. This option first handles the
* 'action' part of the request, which is either page construction (bookmarkable pages or the home
* page) or calling a IRequestListener handler, such as Link.onClick. When that part is done, a
* redirect is issued to the render part, which does all the rendering of the page and its
* components. <strong>Be aware </strong> that this may mean, depending on whether you access any
* models in the action part of the request, that attachment and detachment of some models is done
* twice for a request.</li>
* <li>IRequestCycleSettings.RenderStrategy.REDIRECT_TO_BUFFER. This option handles both the action-
* and the render part of the request in one physical request, but instead of streaming the result
* to the browser directly, it is kept in memory, and a redirect is issued to get this buffered
* result (after which it is immediately removed). This option currently is the default render
* strategy, as it shields you from the double submit problem, while being more efficient and less
* error prone regarding to detachable models.</li>
* </ul>
* Note: In rare cases the strategies involving redirect may lose session data! For example: if
* after the first phase of the strategy the server node fails without having the chance to
* replicate the session then the second phase will be executed on another node and the whole
* process will be restarted and thus anything stored in the first phase will be lost with the
* failure of the server node. For similar reasons it is recommended to use sticky sessions when
* using redirect strategies.</li>
* </ul>
*
* <p>
* More documentation is available about each setting in the setter method for the property.
*
* @author Jonathan Locke
* @author Chris Turner
* @author Eelco Hillenius
* @author Juergen Donnerstag
* @author Johan Compagner
* @author Igor Vaynberg (ivaynberg)
* @author Martijn Dashorst
* @author James Carman
*/
public class RequestCycleSettings
{
/**
* Enum type for different render strategies
*/
public enum RenderStrategy {
/**
* All logical parts of a request (the action and render part) are handled within the same
* request.
* <p>
* This strategy is more efficient than the 'REDIRECT_TO_RENDER' strategy, and doesn't have
* some of the potential problems of it, it also does not solve the double submit problem.
* It is however the best option to use when you want to do sophisticated (non-sticky
* session) clustering.
* </p>
*/
ONE_PASS_RENDER,
/**
* All logical parts of a request (the action and render part) are handled within the same
* request, but instead of streaming the render result to the browser directly, the result
* is cached on the server. A client side redirect command is issued to the browser
* specifically to render this request.
*/
REDIRECT_TO_BUFFER,
/**
* The render part of a request (opposed to the 'action part' which is either the
* construction of a bookmarkable page or the execution of a IRequestListener handler) is
* handled by a separate request by issuing a redirect request to the browser. This is
* commonly known as the 'redirect after submit' pattern, though in our case, we use it for
* GET and POST requests instead of just the POST requests.
* <p>
* This pattern solves the 'refresh' problem. While it is a common feature of browsers to
* refresh/ reload a web page, this results in problems in many dynamic web applications.
* For example, when you have a link with an event handler that e.g. deletes a row from a
* list, you usually want to ignore refresh requests after that link is clicked on. By using
* this strategy, the refresh request only results in the re-rendering of the page without
* executing the event handler again.
* </p>
* <p>
* Though it solves the refresh problem, it introduces potential problems, as the request
* that is logically one, are actually two separate request. Not only is this less
* efficient, but this also can mean that within the same request attachment/ detachment of
* models is done twice (in case you use models in the bookmarkable page constructors and
* IRequestListener handlers). If you use this strategy, you should be aware of this
* possibility, and should also be aware that for one logical request, actually two
* instances of RequestCycle are created and processed.
* </p>
* <p>
* Also, even with this strategy set, it is ignored for instances of
* {@link org.apache.wicket.core.request.handler.BookmarkableListenerRequestHandler},
* because otherwise they wouldn't be bookmarkable.
* </p>
*/
REDIRECT_TO_RENDER
}
/** True if the response should be buffered */
private boolean bufferResponse = true;
/**
* Whether Wicket should try to get extensive client info by redirecting to
* {@link org.apache.wicket.markup.html.pages.BrowserInfoPage a page that polls for client
* capabilities}. False by default.
*/
private boolean gatherExtendedBrowserInfo = false;
/**
* The render strategy, defaults to 'REDIRECT_TO_BUFFER'. This property influences the default
* way in how a logical request that consists of an 'action' and a 'render' part is handled, and
* is mainly used to have a means to circumvent the 'refresh' problem.
*/
private RequestCycleSettings.RenderStrategy renderStrategy = RenderStrategy.REDIRECT_TO_BUFFER;
/** List of {@link IResponseFilter}s. */
private List<IResponseFilter> responseFilters;
/**
* In order to do proper form parameter decoding it is important that the response and the
* following request have the same encoding. see
* http://www.crazysquirrel.com/computing/general/form-encoding.jspx for additional information.
*/
private String responseRequestEncoding = "UTF-8";
/**
* The time that a request will by default be waiting for the previous request to be handled
* before giving up. Defaults to one minute.
*/
private Duration timeout = Duration.ONE_MINUTE;
private int exceptionRetryCount = 10;
// ****************************************************************************
// IRequestCycleSettings Implementation
// ****************************************************************************
/**
* Adds a response filter to the list. Filters are evaluated in the order they have been added.
*
* @param responseFilter
* The {@link IResponseFilter} that is added
* @return {@code this} object for chaining
*/
public RequestCycleSettings addResponseFilter(IResponseFilter responseFilter)
{
if (responseFilters == null)
{
responseFilters = new ArrayList<>(3);
}
responseFilters.add(responseFilter);
return this;
}
/**
* Decides whether to buffer the response's headers until the end of the request processing.
* The buffering is needed if the application makes use of
* {@link org.apache.wicket.Component#setResponsePage(org.apache.wicket.request.component.IRequestablePage)} or
* {@link org.apache.wicket.request.flow.ResetResponseException}
*
* @return {@code true} if the application should buffer the response's headers.
*/
public boolean getBufferResponse()
{
return bufferResponse;
}
/**
* Gets whether Wicket should try to get extensive client info by redirecting to
* {@link org.apache.wicket.markup.html.pages.BrowserInfoPage a page that polls for client capabilities}. This method is used by the
* default implementation of {@link org.apache.wicket.Session#getClientInfo()}, so if that method is
* overridden, there is no guarantee this method will be taken into account.
*
* @return Whether to gather extensive client info
*/
public boolean getGatherExtendedBrowserInfo()
{
return gatherExtendedBrowserInfo;
}
/**
* Gets in what way the render part of a request is handled.
*
* @return the render strategy
*/
public RequestCycleSettings.RenderStrategy getRenderStrategy()
{
return renderStrategy;
}
/**
* @return an unmodifiable list of added response filters, null if none
*/
public List<IResponseFilter> getResponseFilters()
{
if (responseFilters == null)
{
return null;
}
else
{
return Collections.unmodifiableList(responseFilters);
}
}
/**
* In order to do proper form parameter encoding it is important that the response and the
* subsequent request stipulate a common character encoding.
*
* possible form encodings and their problems:
*
* <ul>
* <li><a
* href="http://www.crazysquirrel.com/computing/general/form-encoding.jspx">application/x-
* www-form-urlencoded</a></li>
* <li><a href=
* "http://stackoverflow.com/questions/546365/utf-8-text-is-garbled-when-form-is-posted-as-multipart-form-data"
* >multipart/form-data</a></li>
* </ul>
*
* wicket now uses multipart/form-data for it's forms.
*
* @return The request and response encoding
*/
public String getResponseRequestEncoding()
{
return responseRequestEncoding;
}
/**
* Gets the time that a request will by default be waiting for the previous request to be
* handled before giving up.
*
* @return The time out
*/
public Duration getTimeout()
{
return timeout;
}
/**
* Sets a flag whether the application should buffer the response's headers until the end
* of the request processing. The buffering is needed if the application makes use of
* {@link org.apache.wicket.Component#setResponsePage(org.apache.wicket.request.component.IRequestablePage)}
* or {@link org.apache.wicket.request.flow.ResetResponseException}
*
* @param bufferResponse
* {@code true} if the application should buffer response's headers.
* @return {@code this} object for chaining
*/
public RequestCycleSettings setBufferResponse(boolean bufferResponse)
{
this.bufferResponse = bufferResponse;
return this;
}
/**
* Sets whether Wicket should try to get extensive client info by redirecting to
* {@link org.apache.wicket.markup.html.pages.BrowserInfoPage a page that polls for client capabilities}. This method is used by the
* default implementation of {@link org.apache.wicket.Session#getClientInfo()}, so if that method is
* overridden, there is no guarantee this method will be taken into account.
*
* <p>
* <strong>WARNING: </strong> though this facility should work transparently in most cases, it
* is recommended that you trigger the roundtrip to get the browser info somewhere where it
* hurts the least. The roundtrip will be triggered the first time you call
* {@link org.apache.wicket.Session#getClientInfo()} for a session, and after the roundtrip a new request with the
* same info (url, post parameters) is handled. So rather than calling this in the middle of an
* implementation of a form submit method, which would result in the code of that method before
* the call to {@link org.apache.wicket.Session#getClientInfo()} to be executed twice, you best call
* {@link org.apache.wicket.Session#getClientInfo()} e.g. in a page constructor or somewhere else where you didn't
* do a lot of processing first.
* </p>
*
* @param gatherExtendedBrowserInfo
* Whether to gather extensive client info
* @return {@code this} object for chaining
*/
public RequestCycleSettings setGatherExtendedBrowserInfo(boolean gatherExtendedBrowserInfo)
{
this.gatherExtendedBrowserInfo = gatherExtendedBrowserInfo;
return this;
}
/**
* Sets in what way the render part of a request is handled. Basically, there are two different
* options:
* <ul>
* <li>Direct, ApplicationSettings.ONE_PASS_RENDER. Everything is handled in one physical
* request. This is efficient, and is the best option if you want to do sophisticated
* clustering. It does not however, shield you from what is commonly known as the <i>Double
* submit problem </i></li>
* <li>Using a redirect. This follows the pattern <a
* href="http://www.theserverside.com/articles/article.tss?l=RedirectAfterPost" >as described at
* the serverside </a> and that is commonly known as Redirect after post. Wicket takes it one
* step further to do any rendering after a redirect, so that not only form submits are shielded
* from the double submit problem, but also the IRequestListener handlers (that could be e.g. a
* link that deletes a row). With this pattern, you have two options to choose from:
* <ul>
* <li>ApplicationSettings.REDIRECT_TO_RENDER. This option first handles the 'action' part of
* the request, which is either page construction (bookmarkable pages or the home page) or
* calling a IRequestListener handler, such as Link.onClick. When that part is done, a redirect
* is issued to the render part, which does all the rendering of the page and its components.
* <strong>Be aware </strong> that this may mean, depending on whether you access any models in
* the action part of the request, that attachment and detachment of some models is done twice
* for a request.</li>
* <li>ApplicationSettings.REDIRECT_TO_BUFFER. This option handles both the action- and the
* render part of the request in one physical request, but instead of streaming the result to
* the browser directly, it is kept in memory, and a redirect is issue to get this buffered
* result (after which it is immediately removed). This option currently is the default render
* strategy, as it shields you from the double submit problem, while being more efficient and
* less error prone regarding to detachable models.</li>
* </ul>
*
* @param renderStrategy
* the render strategy that should be used by default.
* @return {@code this} object for chaining
*/
public RequestCycleSettings setRenderStrategy(RequestCycleSettings.RenderStrategy renderStrategy)
{
this.renderStrategy = renderStrategy;
return this;
}
/**
* In order to do proper form parameter decoding it is important that the response and the
* following request have the same encoding. see
* http://www.crazysquirrel.com/computing/general/form-encoding.jspx for additional information.
*
* Default encoding: UTF-8
*
* @param encoding
* The request and response encoding to be used.
* @return {@code this} object for chaining
*/
public RequestCycleSettings setResponseRequestEncoding(final String encoding)
{
Args.notNull(encoding, "encoding");
this.responseRequestEncoding = encoding;
return this;
}
/**
* Sets the time that a request will by default be waiting for the previous request to be
* handled before giving up.
*
* @param timeout
* @return {@code this} object for chaining
*/
public RequestCycleSettings setTimeout(Duration timeout)
{
Args.notNull(timeout, "timeout");
this.timeout = timeout;
return this;
}
/**
* Sets how many attempts Wicket will make to render the exception request handler before
* giving up.
* @param retries
* the number of attempts
* @return {@code this} object for chaining
*/
public RequestCycleSettings setExceptionRetryCount(int retries)
{
this.exceptionRetryCount = retries;
return this;
}
/**
* @return How many times will Wicket attempt to render the exception request handler before
* giving up.
*/
public int getExceptionRetryCount()
{
return exceptionRetryCount;
}
}