Minggu, 26 Januari 2014

Facade POST by GET






Introduction


By default the JSF-generated forms only provides POST functionality. There is no default option to transform the POST forms (

) into GET forms (). Not a big problem as far, as it is recommended to use POST only to handle form data. But it can sometimes be very useful to handle forms by GET, like for Search Results. So that you can bookmark or copypaste the URL in the address bar and reinvoke exact the same form action in another browser session.



You can't change POST by GET in JSF, but you can facade POST requests like it are GET requests using a PhaseListener which captures POST requests before the render response and redirects them to a GET request with the request parameters in the URL.


Back to top


Passing GET parameters to backing beans


This technique is also described in Communication. Here is how the JSF could look like:




id="paramname1" value="#{myBean.paramname1}" />
id="paramname2" value="#{myBean.paramname2}" />
value="submit" action="#{myBean.action}" />
value="#{myBean.result}" />


Please note: it is important that the ID value of the UIInput component exactly the same is as the name of the bean property. The h:outputText component value is just an example to show the results. It can be anything, for example a h:dataTable is also possible.


Define the request/input parameters as managed properties in the faces-config.xml:




myBean
mypackage.MyBean
request

paramname1
#{param.paramname1}


paramname2
#{param.paramname2}



As the request parameters are transferred by GET here, there is absolutely no need to put the managed bean in the session scope. If you really want to store some data in the session, then rather use the SessionMap from the ExternalContext or just define another managed bean which you put in the session scope and use this for session data only.


Here is how the backing bean MyBean.java look like. You can use the @PostConstruct annotation to process the GET parameters. The method with this annotation will only be invoked when the managed properties are all already set.



package mypackage;

import javax.annotation.PostConstruct;

public class MyBean {

// Init --------------------------------------------------------------------------------------

private String paramname1;
private String paramname2;
private String result;

// Actions -----------------------------------------------------------------------------------

@PostConstruct
public void init() {
// You can process the GET parameters here.
result = paramname1 + ", " + paramname2;
}

public void action() {
// You can do your form submit thing here.
}

// Getters -----------------------------------------------------------------------------------

public String getParamname1() {
return paramname1;
}

public String getParamname2() {
return paramname2;
}

public String getResult() {
return result;
}

// Setters -----------------------------------------------------------------------------------

public void setParamname1(String paramname1) {
this.paramname1 = paramname1;
}

public void setParamname2(String paramname2) {
this.paramname2 = paramname2;
}

}


The #{param} is a predefinied variable referring to the request parameter map. Invoking a GET request using the following URL will set the parameter values automatically in the managed bean instance and therefore also in the input fields, thanks to the managed-property configuration in the faces-config.xml:
http://example.com/mypage.jsf?paramname1=paramvalue1¶mname2=paramvalue2


Back to top


Facade POST requests like it are GET requests


This PhaseListener will facade POST requests like it are GET requests.



package mypackage;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.faces.FacesException;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
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;

/**
* Facade POST requests like it are GET requests.
*


* This phaselistener is designed to be used for JSF 1.2 with request scoped beans. The beans are
* expected to have the request parameters definied as managed properties in the faces-config.xml.
*
* @author BalusC
* @link http://balusc.blogspot.com/2007/03/facade-post-by-get.html
*/
public class PostFacadeGetListener implements PhaseListener {

// Init --------------------------------------------------------------------------------------

private static final String ALL_FACES_MESSAGES_ID = "PostFacadeGetListener.allFacesMessages";

// 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();
HttpServletRequest request = (HttpServletRequest)
facesContext.getExternalContext().getRequest();

if ("POST".equals(request.getMethod())) {

// Save facesmessages from POST request in session so that they'll be available on the
// subsequent GET request.
saveFacesMessages(facesContext);

// Resolve action URL, add query parameters and redirect POST request to GET request.
redirect(facesContext, addQueryParameters(facesContext, resolveActionURL(facesContext)));

} else {

// Restore any facesmessages in the GET request.
restoreFacesMessages(facesContext);
}
}

/**
* @see javax.faces.event.PhaseListener#afterPhase(javax.faces.event.PhaseEvent)
*/

public void afterPhase(PhaseEvent event) {
// Nothing to do here.
}

// Helpers -----------------------------------------------------------------------------------

/**
* Save all facesmessages of the given facescontext in session.
* @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.
Iterator clientIdsWithMessages = facesContext.getClientIdsWithMessages();
while (clientIdsWithMessages.hasNext()) {
String clientIdWithMessage = clientIdsWithMessages.next();

// Prepare client-specific facesmessages holder in the main facesmessages holder.
List clientFacesMessages = new ArrayList();
allFacesMessages.put(clientIdWithMessage, clientFacesMessages);

// Get all messages from client and add them to the client-specific facesmessage list.
Iterator facesMessages = facesContext.getMessages(clientIdWithMessage);
while (facesMessages.hasNext()) {
clientFacesMessages.add(facesMessages.next());
}
}
}

/**
* Resolve the action URL of the current view of the given facescontext.
* @param facesContext The involved facescontext.
*/

private static String resolveActionURL(FacesContext facesContext) {

// Obtain the action URL of the current view.
return facesContext.getApplication().getViewHandler().getActionURL(
facesContext, facesContext.getViewRoot().getViewId());
}

/**
* Add POST parameters of the given facescontext as GET parameters to the given url.
* @param facesContext The facescontext to obtain POST request parameters from.
* @param url The URL to append the GET query parameters to.
*/

private static String addQueryParameters(FacesContext facesContext, String url) {

// Prepare.
StringBuilder builder = new StringBuilder(url);
int i = 0;

// Gather the POST request parameters.
Map requestParameterMap =
facesContext.getExternalContext().getRequestParameterMap();

// Walk through the POST request parameters and determine its source.
for (String parameterKey : requestParameterMap.keySet()) {
UIComponent component = facesContext.getViewRoot().findComponent(parameterKey);

if (component instanceof UIInput) {
// You may change this if-block if you want. This is done so, because the
// requestParameterMap can contain more stuff than only UIInput values, for example
// the UICommand element responsible for the action and the parent UIForm.

// IMPORTANT: keep in mind that the values of HtmlInputSecret components will also
// be passed to the GET here so that they would become visible in the address bar.
// If you want to prevent this, then consider to set some specific request parameter
// which should let this phaselistener skip the PRG completely for that request.

// Append POST request parameters as GET query parameters to the URL.
String parameterName = parameterKey.substring(parameterKey.lastIndexOf(':') + 1);
String parameterValue = requestParameterMap.get(parameterKey);
builder.append((i++ == 0 ? "?" : "&") + parameterName + "=" + parameterValue);
}
}

return builder.toString();
}

/**
* Invoke a redirect to the given URL.
* @param facesContext The involved facescontext.
*/

private static void redirect(FacesContext facesContext, String url) {
try {
// Invoke a redirect to the given 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 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);

// If any, then restore them in the given facescontext.
if (allFacesMessages != null) {
for (String clientId : allFacesMessages.keySet()) {
List allClientFacesMessages = allFacesMessages.get(clientId);
for (FacesMessage clientFacesMessage : allClientFacesMessages) {
facesContext.addMessage(clientId, clientFacesMessage);
}
}
}
}

}



Activate this phaselistener by adding the following lines to the faces-config.xml:




mypackage.PostFacadeGetListener


Now when you submit a form by POST using commandLink or commandButton, then it will automatically be redirected to a GET URL, with the POST parameters visible in the address bar, like if it was a GET request.


Back to top


Hide parameters


If you want to implement the PRG pattern, but you don't want to use visible UIInput components in the page, then just use h:inputHidden to hide the parameter and transfer it from request to request:




id="paramname1" value="#{myBean.paramname1}" />
id="paramname2" value="#{myBean.paramname2}" />
value="submit" action="#{myBean.action}" />
value="#{myBean.result}" />


In this case, the value of the paramname2 is not visible on the page, but just hidden in a element. And of course it is just visible in the GET request string of the URL.


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/facade-post-by-get.html

Tidak ada komentar:

Posting Komentar