Monday, March 19, 2007

POST-Redirect-GET pattern

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 <redirect /> line to the navigation case so that it automatically invokes a redirect to the given view after POST. This is highly recommended when you're using commandlinks instead of outputlinks to navigate between pages (which I wouldn't call a good practice; POST should not be used for plain navigation, GET should be used for it). But in case of request based forms you will lost all submitted input values and the eventual FacesMessages. Not very handy if you want to redisplay submitted values after a succesful form submit and/or use FacesMessages to display error/succes message of a form submit.

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.
 * <p>
 * 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 <tt>saveUIInputValues()</tt> and
 * <tt>restoreUIInputValues()</tt> methods to save (little) performance. If you're using JSF 1.1,
 * then you can also remove the <tt>saveViewRoot()</tt> and <tt>restoreViewRoot</tt> 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();
        Map<String, Object> sessionMap = 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<String, List<FacesMessage>> allFacesMessages =
            new LinkedHashMap<String, List<FacesMessage>>();
        facesContext.getExternalContext().getSessionMap()
            .put(ALL_FACES_MESSAGES_ID, allFacesMessages);

        // Get client ID's of all components with facesmessages.
        Iterator<String> clientIdsWithMessages = facesContext.getClientIdsWithMessages();
        while (clientIdsWithMessages.hasNext()) {
            String clientIdWithMessage = clientIdsWithMessages.next();

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

            // Get all messages from client and add them to the client-specific facesmessage list.
            Iterator<FacesMessage> facesMessages = 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.
        Map<String, Object> allUIInputValues = new HashMap<String, Object>();
        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, List<UIComponent> components, Map<String, Object> 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<String, List<FacesMessage>> allFacesMessages = (Map<String, List<FacesMessage>>)
            facesContext.getExternalContext().getSessionMap().remove(ALL_FACES_MESSAGES_ID);

        // Restore them in the given facescontext.
        for (Entry<String, List<FacesMessage>> 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.
        Map<String, Object> allUIInputValues = (Map<String, Object>)
            facesContext.getExternalContext().getSessionMap().remove(ALL_UIINPUT_VALUES_ID);

        // Restore them in the given facescontext.
        for (Entry<String, Object> entry : 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:

<lifecycle>
    <phase-listener>mypackage.PostRedirectGetListener</phase-listener>
</lifecycle>

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

53 comments:

mitch said...

This is really great code. I used it and it helped a lot. Thanks for posting it.

A few comments: this will get called for all jsf pages in your application so you need to make it detect which page it is working on.

For me all the searches in facesContext.getViewRoot().findComponent(paramKey);
were returning null. So I had to just do it for all the parameters rather than ones for specific components.

This was a huge help to me in my project. Thanks.

sandy said...

How to call the Phase listener in from mybean.action() method. On clicking the commandButton how can the user call the redirect method.

BalusC said...

There is no need to call it. Just define the phaselistener in the faces-config.xml. It is always invoked on every request.

sandy said...

I did not understand. My case is in the following way. I have to submit a form from h:form to an absolute url that can read the values or the input data based on the parameter names of the form. On clicking the h:commandButton all the form data should be submitted to this url. I understand that phase listener does some work. But what should be in the action attribute of h:commadButton which will trigger this phaseListener?

BalusC said...

Your problem is a completely different subject. The PRG pattern simply isn't applicable in this. You've posted the same problem in the Sun forum. I've suggested to use a plain vanilla HTML form, or to use Javascript to change the h:form action value so that it points to another URL.

Iceriver said...

That is great post. Thanks.
I see this as a good way to change post to get, so users can record url as bookmarks.
I think this idea (phase listener) can be extended to handle cases to handle mapping, like
to map http://www.abc.com/hello/action/23 to
hello.jsf?action=23
In phase listener, we execute some actions before rendering response page.

Is this a valid thought, balusc?

Jan said...

Hi,
I´ve have the same problem like mitch in its first post. All searches via facesContext.getViewRoot().findComponent(paramKey); return null. I think I know why - because If an navigation-case ist triggered (the action method of the form returns a defined outcome in INVOKE APPLICATION) and therefor the view changes the current view root in RENDER RESPONSE refers to "new" site which maybe does not contain the input components then the view from which the action was performed.

So the solution should be to just go through all request parameters and add them to the url (which is not to good sometimes) or maybe build the url earlier then the end of INVOKE APPLICATION to refer to the original view.

FutureMien said...

This code was really helpful, thanks.

However, I have a question: I am working on a project that uses JBoss Seam as well. I am in the process of implementing code to save the UIViewRoot instance after a post, before redirecting to a get request which functions in a similar manner to your example. However, the web application is raising an exception stating that the UIViewRoot instance is not serializable. I have tried the UIComponentBase#saveState(..) and related methods, but no success yet. Any ideas you may have will be greatly appreciated. Thanks.

NCD said...

BalusC,

Why not just add <redirect /> in your navigation rule in faces-config.xml.

The <redirect /> should accomplish the same thing as your PRG pattern and save a lot of extra work, right?

BalusC said...

FacesMessages and (submitted) input values get lost.

NCD said...

BalusC,

What do you mean by "submitted" input values?

NCD said...

BalusC,

Thanks for your reply to my original post. I would have had problems if I had used the faces-config redirect option blindly.

NCD

iroh said...

BalusC you are the man! This worked phenomenally for what I needed.

Imre said...

Saved me a day.

Thank you.

thomas said...

Hi. First I would like to say that this is a really great code.

But will it work with facelets and if it will, then for which phase should it be set?

Should it be after the facelets have done thier job or before?

Thanks

removeps-groups said...

Very interesting.

What is the meaning of

> But this way you will lost all
> submitted input values and the
> eventual FacesMessages.

I don't know about FacesMessage, but submitted values are not lost. When the page is submitted, it calls set functions on the managed beans. So you you had h:inputText value="#{mybean.firstValue" ... there will be a call to mybean.setFirstValue. If mybean is a session bean, no values will be lost.

Where are the input values and so on saved? It looks like they're saved to facesContext.getExternalContext().sessionMap. What is this thing? Is it the place where all managed JSF session beans are stored?

What does the string returned by resolveActionURL look like? I guess I should try the code, but it will take me a while to set it up.

If you have pages A.jsp, B.jsp, C.jsp, and the user requests "http://localhost/A.faces", what will be the sequence of events? (From the code, it seems that the phase listener code is hit, and since the request is a GET request, the faces context is restored though there is nothing to restore for this initial request).

Afterward pressing the next button on page A, faces-config.xml says to navigate to page B without a redirect. A POST request sent to the server. The phase listener code will save the input values are redirect you a new URL. What is the name of this new URL? When does the code call the set functions on the managed beans used in page A?

BalusC said...

"submitted values are not lost"
You used a session scoped bean for request scoped data instead of a request scoped bean. Learn about the scopes and read the article text and the javadoc/comments of the code.

sri... said...

This works well.
But Im facing an issue with Tomahak components. For example.

I am using h:inputText inside the tag t:dataList in this case. The text boxes inside dataList tag are lost the values, what user typed. Becoz the components are not identifed in the saveUIInputValues method.

Leonardo Pessoa said...

BalusC, that's a great code helped me a lot....
but i am having a problem with it, now i'm using facelets to render my views when i tried to use this listener it not worked, do i have to do something else?

thank you

Mireille's Blog said...

Hi,
In my project I use Facelets too, and the listener did not work :(
That's possible because of render response phase...

ivan.eggel said...

Very nice

mahesh said...

Hello BalusC,

I tried your code its working great for double submits but the users I have are not patient so they click 4 to 5 times and when I tested your code by clicking multiple times its breaking.

Will this code only work for double
submits and not for multiple submits.

BalusC said...

When talking about "double submits" in this article, I meant the double submit which happen when you navigate forward/backward with browser buttons or refresh the submitted page again.

Pressing impatiently on a button is best to be avoided by disabling the button shortly after press using JS, or to freeze the complete window using a transparent iframe.

jane mathew said...

Hi BalusC,

This works fine for me in all cases except where I am populatng data in a h:datatable. I have a file upload in the same jsp. When I submit the page, the values are not retained. Could you please help me.


- Jain mathew

itzli said...

Great!

This a great code, JSF have a lot of inconsistencies. This one is the most important. Thanks to solve it and publicate!

veena said...

Can you please explain again what you mean by the following?

JSF also supports it, you just need to add redirect line to the navigation case so that it automatically invokes a redirect to the given view after POST. But this way you will lost all submitted input values and the eventual FacesMessages. Not very handy if you want to redisplay submitted values after a succesful form submit and/or use FacesMessages to display error/succes message of a form submit.



I just took the PostRedirectGetListener and implemented it, configured it in Faces-config.xml and it works like a charm. But not sure what you mean by "add a redirect "

BalusC said...

Instead of the PostRedirectGetListener you can also add the following line to the navigation case:

<redirect/>

But this has thus the described disadvantages.

veena said...

After implementing this in my application, I found a problem.
I have a navigation case to dialogs and these are getting lost. So the dialog does not pop up. Any suggestions on how to fix this problem?

BalusC said...

I do not understand what exactly you're talking about with 'dialogs'. This seems something 3rd-party-library-specific. Post your issue in a more generic forum, e.g. the JSF forum at SDN.

veena said...

By the way, we are using JSF 1.1.

Thanks,

Veena

veena said...

We are using Trinidad. A dialog page can be opened up as a pop up. Instead of page flow to a jspx, you can have a page flow to a dialog simply by returning a prefixed String dialog:String. In the faces-config you define the navigation case for dialog:String.

BalusC said...

Best what you can do is to let the phaselistener skip the PRG pattern based on some request parameter or attribute which indicates that such a dialog is to be fired.

guima said...

BalusC, Just adding PostRedirectGetListener to application and activating this
phaselistener in faces-config.xml didn't worked for me. So, i tried to do this to a simple application found in the book Core JavaServer Faces called login. Changing the scope of the backing bean to request makes the application seems just like using the tag redirect. What am i doing wrong? Thanks.

Fahad said...

I think for JSF related query Balus is the correct person,
I have probelm with </redirect> for PRG, it worked well on http, but not on https, I mean if we deploy it in secured server redirection will get timeout. please share me if you have any work arround on this.

Michail said...

Hello,
I've tried this listener with JSF 1.2 and JSP and it doesn't work. During debugging I see that the ViewRoot before "render responce" phase is null so there is nothing to save there.
What is wrong there?

saleem said...

Hi,

I am using JSF with RichFaces & i applied the 'POST-Redirect-GET pattern', now, all my ajax requests are submiting the whole page and form. Please provide any solution to this problem.

Jigar said...

hello i have an issue, my footer is polling ajax request,
so if i use your Phase listerner, it is handling all ajax request also, more over i have user PRG in menu navigation,i am setting some data in menu item 's action which is in request scope , but as its PGR it is getting lost in the target view, how to resolve this, i dont want to go for putting data in session scope,if i dnt use PRG then the URL on the browser is not getting updated even thoigh the page is changed..
how to resolve.. ?

Thanks BalusC in Adv.

Jigar said...

http://forums.sun.com/thread.jspa?threadID=5432075

J.Naveen said...
This comment has been removed by the author.
J.Naveen said...

Hi I am getting an exception on exactly redirect page line.

WARNING: phase(RENDER_RESPONSE 6,com.sun.faces.context.FacesContextImpl@bf711e) threw exception: java.lang.IllegalStateException null
org.apache.catalina.connector.ResponseFacade.sendRedirect(ResponseFacade.java:435)
com.sun.faces.context.ExternalContextImpl.redirect(ExternalContextImpl.java:419)
bean.LifeCycleListener.redirect(LifeCycleListener.java:124)
bean.LifeCycleListener.beforePhase(LifeCycleListener.java:67)
com.sun.faces.lifecycle.LifecycleImpl.phase(LifecycleImpl.java:222)
com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:144)
javax.faces.webapp.FacesServlet.service(FacesServlet.java:245)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:286)
org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:845)
org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447)
java.lang.Thread.run(Thread.java:619)

Mathieu said...

Hi,

I'm trying to implement PRG in my JSF 1.2 app. I have some troubles retrieving request attributes.

I have a MyDataBean request scoped which provide a DataModel property to a t:datatable.
When the action handler is called, it retrive the data with the DataModel.getRowData() function. But it seams that the redirect handle another request dans the retrieved data is null at render phase.

I tried to save it in Session the same way you've done for POST data, but it's set to null ( the context seams to keep the reference and write into my saved HttpRequest object).

Is there a way to combine PRG with normal JSF processing ?

Tks.

Carlos E. R. DiĆ³genes said...

Hi BalusC,

Your code is working fine, but in one situation I'm having a problem.

I have a page that render a data list with commandlinks to delete the data.

The first delete works fine and the page is re-rendered. The next time I try to delete a item I get the following error:

com.sun.faces.application.view.StateHolderSaver cannot be cast to [Ljava.lang.Object;

Removing your listener solve the problem. Do you have any idea what could be wrong.

Carlos E. R. DiĆ³genes said...

I removed the code to save and restore the ViewRoot and now it's working. I'm using JSF 2. If you could explain why I will be very thankfull.

Yogeen said...

can you please suggest a solution to double-click problem in JSF.

i.e when a commandlink is double clicked it submits the request twice. can this be solved using PRG ? pls suggest a solution.

Erik said...

This is a really great concept and I like the implementation! I've spent a week trying various approaches to get post-redirect-get to work in a JSF application where all managed beans are managed by Spring. I really didn't want to use the flash scope and lose the automatic property mapping that JSF offers so I tried creating custom scopes and other techniques to try and hang onto my managed beans after the redirect without much success. So far this works great.

I did add support for saving and restoring request attributes so that any initialization of form properties that I do in managed beans gets retained. Your code is pretty easy to add on to as well! Thanks!

Archan Mishra said...

Hi BalusC,
I am using your Phase Listener in my code.
I want to know a bit more about the copyright of code.
Can it be used for both free and commercial purposes.
Thank you once Again

Alfonssocken said...

Just want to say thank you!

@Archan: As it is written in the text: "Copyright - There is no copyright on the code. You can copy, change and distribute it freely. Just mentioning this site should be fair."

smithh032772 said...

BalusC, I arried to this post and solution of yours after experiencing problems with my PrimeFaces 3.0M3, Mojarra 2.1.2 JSF web app, copied and pasted this as you stated in this post, and it solved my problem. I also configured the faces.STATE_SAVING_METHOD, PARTIAL_STATE and FULL_STATE in web.xml per other related posts I found on primefaces.org site, and other posts you posted on stackoverflow.

Thank you very much for all your contribution to the world of JSF; i'm a newbie to java AND JSF, and i'm loving the experience and glued to my desk/seat/computer, primarily, because of all your posts out there.

Thanks again,
Howard

smithh032772 said...

BalusC,

I've been getting the following error message ever since I renamed my NetBeans (v7.0.1) JSF web app (context-root), persistent unit, etc...

My app is Ajaxified, i'm using commandLink/Button actionListener and returning NULL outcome (always) and dynamic ui:include to display JSF pages. I read this post and related posts on stackoverflow, and I don't think I need this code anymore, but I could be wrong. I'm about to remove this listener from faces-config and see what happens. Please let me know your thoughts. Thanks, Howard.


WARNING: JSF1063: WARNING! Setting non-serializable attribute value into HttpSession (key: PostRedirectGetListener.savedViewRoot, value class: javax.faces.component.UIViewRoot).

smithh032772 said...

BalusC,

I attempted to remove the listener (as I said I would in my previous post on this page), and my ajax behavior stopped working, and PrimeFaces commandbuttons were NOT rendered correctly, as they are rendered, when I have this listener of yours in place. So, the listener stays. :)

Howard

M said...

On IBM WAS, using JSF 1.1 the code causes an exception. After the reponse is rendered, the next interaction with the page generates the exception:

[INFO] [Servlet Error]-[Faces Servlet]: java.lang.NullPointerException
[INFO] at com.ibm.ws.webcontainer.srt.SRTServletRequest$SRTServletRequestHelper.access$1600(SRTServletRequest.java:2573)
[INFO] at com.ibm.ws.webcontainer.srt.SRTServletRequest.parseParameters(SRTServletRequest.java:1758)
[INFO] at com.ibm.ws.webcontainer.srt.SRTServletRequest.getParameterNames(SRTServletRequest.java:1449)
[INFO] at javax.servlet.ServletRequestWrapper.getParameterNames(ServletRequestWrapper.java:223)
[INFO] at com.sun.faces.context.RequestParameterMap.entrySet(ExternalContextImpl.java:682)
[INFO] at java.util.AbstractMap.toString(AbstractMap.java:599)
[INFO] at com.ibm.ws.webcontainer.util.SimpleHashtable.toString(SimpleHashtable.java:390)
[INFO] at com.ibm.ws.webcontainer.httpsession.SessionData.toString(SessionData.java:1092)
[INFO] at com.ibm.ws.webcontainer.httpsession.HttpSessionFacade.toString(HttpSessionFacade.java:195)
[INFO] at java.lang.String.valueOf(String.java:1505)
[INFO] at java.lang.StringBuilder.append(StringBuilder.java:194)
[INFO] at com.ibm.ws.webcontainer.filter.FilterInstanceWrapper.doFilter(FilterInstanceWrapper.java:190)
[INFO] at com.ibm.ws.webcontainer.filter.WebAppFilterChain.doFilter(WebAppFilterChain.java:130)
[INFO] at com.ibm.ws.webcontainer.filter.FilterInstanceWrapper.doFilter(FilterInstanceWrapper.java:190)
[INFO] at com.ibm.ws.webcontainer.filter.WebAppFilterChain.doFilter(WebAppFilterChain.java:130)
[INFO] at com.ibm.ws.webcontainer.filter.WebAppFilterChain._doFilter(WebAppFilterChain.java:87)
[INFO] at com.ibm.ws.webcontainer.filter.WebAppFilterManager.doFilter(WebAppFilterManager.java:848)
[INFO] at com.ibm.ws.webcontainer.filter.WebAppFilterManager.doFilter(WebAppFilterManager.java:691)
[INFO] at com.ibm.ws.webcontainer.servlet.ServletWrapper.handleRequest(ServletWrapper.java:588)
[INFO] at com.ibm.ws.wswebcontainer.servlet.ServletWrapper.handleRequest(ServletWrapper.java:525)
[INFO] at com.ibm.ws.webcontainer.servlet.CacheServletWrapper.handleRequest(CacheServletWrapper.java:90)
[INFO] at com.ibm.ws.webcontainer.WebContainer.handleRequest(WebContainer.java:764)
[INFO] at com.ibm.ws.wswebcontainer.WebContainer.handleRequest(WebContainer.java:1478)
[INFO] at com.ibm.ws.webcontainer.channel.WCChannelLink.ready(WCChannelLink.java:133)
[INFO] at com.ibm.ws.http.channel.inbound.impl.HttpInboundLink.handleDiscrimination(HttpInboundLink.java:450)
[INFO] at com.ibm.ws.http.channel.inbound.impl.HttpInboundLink.handleNewRequest(HttpInboundLink.java:508)
[INFO] at com.ibm.ws.http.channel.inbound.impl.HttpInboundLink.processRequest(HttpInboundLink.java:296)
[INFO] at com.ibm.ws.http.channel.inbound.impl.HttpICLReadCallback.complete(HttpICLReadCallback.java:102)
[INFO] at com.ibm.ws.tcp.channel.impl.AioReadCompletionListener.futureCompleted(AioReadCompletionListener.java:165)
[INFO] at com.ibm.io.async.AbstractAsyncFuture.invokeCallback(AbstractAsyncFuture.java:217)
[INFO] at com.ibm.io.async.AsyncChannelFuture.fireCompletionActions(AsyncChannelFuture.java:161)
[INFO] at com.ibm.io.async.AsyncFuture.completed(AsyncFuture.java:136)
[INFO] at com.ibm.io.async.ResultHandler.complete(ResultHandler.java:196)
[INFO] at com.ibm.io.async.ResultHandler.runEventProcessingLoop(ResultHandler.java:751)
[INFO] at com.ibm.io.async.ResultHandler$2.run(ResultHandler.java:881)
[INFO] at com.ibm.ws.util.ThreadPool$Worker.run(ThreadPool.java:1497)

The workaround is to create a POJO to be used for parameter passing, then replace the existing save/restore methods, with save/restore methods that temporarily store the POJO as a session attribute. Of course the managed beans will have to be adjusted to store/retrieve information out of the POJO.

Also, if you are using server side state saving, you may have to clear the view list, view id from the session to prevent "caching".

Don Kleppinger said...

This looks like it will not work if the user has multiple browser tabs/windows open on the same session. If page loads is slow, user could switch tabs and navigate in other tab and could get the wrong saved values.

Diego R. Ferreira said...

Thank you so much! This fits perfectly in my solution.

Regards,