Notice
This article is targeted on JSF 1.2. For JSF 2.0, this can easier be achieved using the new Flash scope.
Doing the PRG in JSF
The POST-Redirect-GET pattern is commonly used in web applications to prevent double submit when refreshing a POST request and navigation problems/annoyances when using browser back/forward button to page between POST requests. Basically it works as follows: after processing the POST request (unnecessarily submitted/displayed form data and/or irritating "Are you sure to resend data?" popups), but right before sending any response to the client, redirect the response to a new GET request. This way refreshing the request won't (re)invoke the initial POST request anymore, but only the GET request.
JSF also supports it, you just need to add
Fortunately the problem of lost input values and FacesMessages is fixable with a PhaseListener. The below PhaseListener example will implement the PRG pattern that way so that for all POST requests all submitted input values and FacesMessages are saved in session for a once and are restored in the redirected view. All you need to do is just copypaste it and define it once in the faces-config of your JSF webapplication. No need to do any other configurations or make any changes in your JSF webapp.
package mypackage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.faces.FacesException;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.servlet.http.HttpServletRequest;
/**
* Implement the POST-Redirect-GET pattern for JSF.
*
* This phaselistener is designed to be used for JSF 1.2 with request scoped beans of which its
* facesmessages and input values should be retained in the new GET request. If you're using session
* scoped beans only, then you can safely remove the saveUIInputValues() and
* restoreUIInputValues() methods to save (little) performance. If you're using JSF 1.1,
* then you can also remove the saveViewRoot() and restoreViewRoot methods,
* because it is not needed with its view state saving system.
*
* @author BalusC
* @link http://balusc.blogspot.com/2007/03/post-redirect-get-pattern.html
*/
public class PostRedirectGetListener implements PhaseListener {
// Init ---------------------------------------------------------------------------------------
private static final String PRG_DONE_ID = "PostRedirectGetListener.postRedirectGetDone";
private static final String SAVED_VIEW_ROOT_ID = "PostRedirectGetListener.savedViewRoot";
private static final String ALL_FACES_MESSAGES_ID = "PostRedirectGetListener.allFacesMessages";
private static final String ALL_UIINPUT_VALUES_ID = "PostRedirectGetListener.allUIInputValues";
// Actions ------------------------------------------------------------------------------------
/**
* @see javax.faces.event.PhaseListener#getPhaseId()
*/
public PhaseId getPhaseId() {
// Only listen during the render response phase.
return PhaseId.RENDER_RESPONSE;
}
/**
* @see javax.faces.event.PhaseListener#beforePhase(javax.faces.event.PhaseEvent)
*/
public void beforePhase(PhaseEvent event) {
// Prepare.
FacesContext facesContext = event.getFacesContext();
ExternalContext externalContext = facesContext.getExternalContext();
HttpServletRequest request = (HttpServletRequest) externalContext.getRequest();
MapsessionMap = externalContext.getSessionMap();
if ("POST".equals(request.getMethod())) {
// Save viewroot, facesmessages and UIInput values from POST request in session so that
// they'll be available on the subsequent GET request.
saveViewRoot(facesContext);
saveFacesMessages(facesContext);
saveUIInputValues(facesContext);
// Redirect POST request to GET request.
redirect(facesContext);
// Set the PRG toggle.
sessionMap.put(PRG_DONE_ID, true);
} else if (sessionMap.containsKey(PRG_DONE_ID)) {
// Restore any viewroot, facesmessages and UIInput values in the GET request.
restoreViewRoot(facesContext);
restoreFacesMessages(facesContext);
restoreUIInputValues(facesContext);
// Remove the PRG toggle.
sessionMap.remove(PRG_DONE_ID);
}
}
/**
* @see javax.faces.event.PhaseListener#afterPhase(javax.faces.event.PhaseEvent)
*/
public void afterPhase(PhaseEvent event) {
// Do nothing.
}
// Helpers ------------------------------------------------------------------------------------
/**
* Save the current viewroot of the given facescontext in session. This is important in JSF 1.2,
* because the viewroot would be lost in the new GET request and will only be created during
* the afterPhase of RENDER_RESPONSE. But as we need to restore the input values in the
* beforePhase of RENDER_RESPONSE, we have to save and restore the viewroot first ourselves.
* @param facesContext The involved facescontext.
*/
private static void saveViewRoot(FacesContext facesContext) {
UIViewRoot savedViewRoot = facesContext.getViewRoot();
facesContext.getExternalContext().getSessionMap()
.put(SAVED_VIEW_ROOT_ID, savedViewRoot);
}
/**
* Save all facesmessages of the given facescontext in session. This is done so because the
* facesmessages are purely request scoped and would be lost in the new GET request otherwise.
* @param facesContext The involved facescontext.
*/
private static void saveFacesMessages(FacesContext facesContext) {
// Prepare the facesmessages holder in the sessionmap. The LinkedHashMap has precedence over
// HashMap, because in a LinkedHashMap the FacesMessages will be kept in order, which can be
// very useful for certain error and focus handlings. Anyway, it's just your design choice.
Map> allFacesMessages =
new LinkedHashMap>();
facesContext.getExternalContext().getSessionMap()
.put(ALL_FACES_MESSAGES_ID, allFacesMessages);
// Get client ID's of all components with facesmessages.
IteratorclientIdsWithMessages = facesContext.getClientIdsWithMessages();
while (clientIdsWithMessages.hasNext()) {
String clientIdWithMessage = clientIdsWithMessages.next();
// Prepare client-specific facesmessages holder in the main facesmessages holder.
ListclientFacesMessages = new ArrayList ();
allFacesMessages.put(clientIdWithMessage, clientFacesMessages);
// Get all messages from client and add them to the client-specific facesmessage list.
IteratorfacesMessages = facesContext.getMessages(clientIdWithMessage);
while (facesMessages.hasNext()) {
clientFacesMessages.add(facesMessages.next());
}
}
}
/**
* Save all input values of the given facescontext in session. This is done specific for request
* scoped beans, because its properties would be lost in the new GET request otherwise.
* @param facesContext The involved facescontext.
*/
private static void saveUIInputValues(FacesContext facesContext) {
// Prepare the input values holder in sessionmap.
MapallUIInputValues = new HashMap ();
facesContext.getExternalContext().getSessionMap()
.put(ALL_UIINPUT_VALUES_ID, allUIInputValues);
// Pass viewroot children to the recursive method which saves all input values.
saveUIInputValues(facesContext, facesContext.getViewRoot().getChildren(), allUIInputValues);
}
/**
* A recursive method to save all input values of the given facescontext in session.
* @param facesContext The involved facescontext.
*/
private static void saveUIInputValues(
FacesContext facesContext, Listcomponents, Map allUIInputValues)
{
// Walk through the components and if it is an instance of UIInput, then save the value.
for (UIComponent component : components) {
if (component instanceof UIInput) {
UIInput input = (UIInput) component;
allUIInputValues.put(input.getClientId(facesContext), input.getValue());
}
// Pass the children of the current component back to this recursive method.
saveUIInputValues(facesContext, component.getChildren(), allUIInputValues);
}
}
/**
* Invoke a redirect to the same URL as the current action URL.
* @param facesContext The involved facescontext.
*/
private static void redirect(FacesContext facesContext) {
// Obtain the action URL of the current view.
String url = facesContext.getApplication().getViewHandler().getActionURL(
facesContext, facesContext.getViewRoot().getViewId());
try {
// Invoke a redirect to the action URL.
facesContext.getExternalContext().redirect(url);
} catch (IOException e) {
// Uhh, something went seriously wrong.
throw new FacesException("Cannot redirect to " + url + " due to IO exception.", e);
}
}
/**
* Restore any viewroot from session in the given facescontext.
* @param facesContext The involved FacesContext.
*/
private static void restoreViewRoot(FacesContext facesContext) {
// Remove the saved viewroot from session.
UIViewRoot savedViewRoot = (UIViewRoot)
facesContext.getExternalContext().getSessionMap().remove(SAVED_VIEW_ROOT_ID);
// Restore it in the given facescontext.
facesContext.setViewRoot(savedViewRoot);
}
/**
* Restore any facesmessages from session in the given FacesContext.
* @param facesContext The involved FacesContext.
*/
@SuppressWarnings("unchecked")
private static void restoreFacesMessages(FacesContext facesContext) {
// Remove all facesmessages from session.
Map> allFacesMessages = (Map >)
facesContext.getExternalContext().getSessionMap().remove(ALL_FACES_MESSAGES_ID);
// Restore them in the given facescontext.
for (Entry> entry : allFacesMessages.entrySet()) {
for (FacesMessage clientFacesMessage : entry.getValue()) {
facesContext.addMessage(entry.getKey(), clientFacesMessage);
}
}
}
/**
* Restore any input values from session in the given FacesContext.
* @param facesContext The involved FacesContext.
*/
@SuppressWarnings("unchecked")
private static void restoreUIInputValues(FacesContext facesContext) {
// Remove all input values from session.
MapallUIInputValues = (Map )
facesContext.getExternalContext().getSessionMap().remove(ALL_UIINPUT_VALUES_ID);
// Restore them in the given facescontext.
for (Entryentry : allUIInputValues.entrySet()) {
UIInput input = (UIInput) facesContext.getViewRoot().findComponent(entry.getKey());
input.setValue(entry.getValue());
}
}
}
Activate this phaselistener by adding the following lines to the faces-config.xml:
mypackage.PostRedirectGetListener
Now when you submit a form by POST using commandLink or commandButton, then it will automatically be redirected to a GET request, hereby keeping the submitted input values and FacesMessages in the new GET request.
Back to top
Copyright - There is no copyright on the code. You can copy, change and distribute it freely. Just mentioning this site should be fair.
(C) March 2007, BalusC
Source:http://balusc.blogspot.com/2007/03/post-redirect-get-pattern.html
Tidak ada komentar:
Posting Komentar