5

I have the following commandButton action method handler:

public String reject()
{
  //Do something
  addMessage(null, "rejectAmountInvalid", FacesMessage.SEVERITY_ERROR);

  redirectToPortlet("/xxx/inbox?source=pendingActions#pendingApproval");
}

public static void addMessage(String clientId, String key, Severity level, Object... objArr)
{
    FacesContext context = FacesContext.getCurrentInstance();
    FacesMessage message = null;
    String msg = getTextFromResourceBundle(key);
    if (objArr != null && objArr.length > 0)
        msg = MessageFormat.format(msg, objArr);
    message = new FacesMessage(msg);

    message.setSeverity(level);
    context.addMessage(clientId, message);
}

public static void redirectToPortlet(String urlToRedirect)
{
    FacesContext context = FacesContext.getCurrentInstance();
    ExternalContext externalContext = context.getExternalContext();

    try
    {
        PortletRequest portletRequest = (PortletRequest) externalContext.getRequest();
        ThemeDisplay themeDisplay = (ThemeDisplay) portletRequest.getAttribute("THEME_DISPLAY");
        String portalURL = themeDisplay.getPortalURL();
        String redirect = portalURL + urlToRedirect;
        externalContext.redirect(redirect);
    }
    catch (Throwable e)
    {
        logger.log("Exception in redirectToPortlet to the URL: " + urlToRedirect, VLevel.ERROR, e);
    }
}

When the page is redirected to "/xxx/inbox?source=pendingActions#pendingApproval", the error message I added is lost. Is there a way to preserve the error message in JSF 2.1?

Thanks Sri

Sri
  • 309
  • 1
  • 9
  • 24
  • Little tip, i would rather rethrow exceptions with new messages than logging them at every little spot where they could occure. If function declarations prohibt exception forwarding you can alwas rethrow a RuntimeException. This way you only have to register one global exception handler where you can log all messages. – Sebastian Hoffmann Aug 14 '12 at 16:55

2 Answers2

7

You can use a PhaseListener to save the messages that weren't displayed for the next request.

I've been using for a while one from Lincoln Baxter's blog post Persist and pass FacesMessages over multiple page redirects, you just copy the class to some package and register on your faces-config.xml.

It's not mentioned explicitly in the blog post, but I'm assuming the code is public domain, so I am posting here for a more self-contained answer:

package com.yoursite.jsf;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;

/**
 * Enables messages to be rendered on different pages from which they were set.
 *
 * After each phase where messages may be added, this moves the messages
 * from the page-scoped FacesContext to the session-scoped session map.
 *
 * Before messages are rendered, this moves the messages from the
 * session-scoped session map back to the page-scoped FacesContext.
 *
 * Only global messages, not associated with a particular component, are
 * moved. Component messages cannot be rendered on pages other than the one on
 * which they were added.
 *
 * To enable multi-page messages support, add a <code>lifecycle</code> block to your
 * faces-config.xml file. That block should contain a single
 * <code>phase-listener</code> block containing the fully-qualified classname
 * of this file.
 *
 * @author Jesse Wilson jesse[AT]odel.on.ca
 * @secondaryAuthor Lincoln Baxter III lincoln[AT]ocpsoft.com 
 */
public class MultiPageMessagesSupport implements PhaseListener
{

    private static final long serialVersionUID = 1250469273857785274L;
    private static final String sessionToken = "MULTI_PAGE_MESSAGES_SUPPORT";

    public PhaseId getPhaseId()
    {
        return PhaseId.ANY_PHASE;
    }

    /*
     * Check to see if we are "naturally" in the RENDER_RESPONSE phase. If we
     * have arrived here and the response is already complete, then the page is
     * not going to show up: don't display messages yet.
     */
    // TODO: Blog this (MultiPageMessagesSupport)
    public void beforePhase(final PhaseEvent event)
    {
        FacesContext facesContext = event.getFacesContext();
        this.saveMessages(facesContext);

        if (PhaseId.RENDER_RESPONSE.equals(event.getPhaseId()))
        {
            if (!facesContext.getResponseComplete())
            {
                this.restoreMessages(facesContext);
            }
        }
    }

    /*
     * Save messages into the session after every phase.
     */
    public void afterPhase(final PhaseEvent event)
    {
        if (!PhaseId.RENDER_RESPONSE.equals(event.getPhaseId()))
        {
            FacesContext facesContext = event.getFacesContext();
            this.saveMessages(facesContext);
        }
    }

    @SuppressWarnings("unchecked")
    private int saveMessages(final FacesContext facesContext)
    {
        List<FacesMessage> messages = new ArrayList<FacesMessage>();
        for (Iterator<FacesMessage> iter = facesContext.getMessages(null); iter.hasNext();)
        {
            messages.add(iter.next());
            iter.remove();
        }

        if (messages.size() == 0)
        {
            return 0;
        }

        Map<String, Object> sessionMap = facesContext.getExternalContext().getSessionMap();
        List<FacesMessage> existingMessages = (List<FacesMessage>) sessionMap.get(sessionToken);
        if (existingMessages != null)
        {
            existingMessages.addAll(messages);
        }
        else
        {
            sessionMap.put(sessionToken, messages);
        }
        return messages.size();
    }

    @SuppressWarnings("unchecked")
    private int restoreMessages(final FacesContext facesContext)
    {
        Map<String, Object> sessionMap = facesContext.getExternalContext().getSessionMap();
        List<FacesMessage> messages = (List<FacesMessage>) sessionMap.remove(sessionToken);

        if (messages == null)
        {
            return 0;
        }

        int restoredCount = messages.size();
        for (Object element : messages)
        {
            facesContext.addMessage(null, (FacesMessage) element);
        }
        return restoredCount;
    }
}

And then, on your faces-config.xml:

<phase-listener>com.yoursite.jsf.MultiPageMessagesSupport</phase-listener>
Elias Dorneles
  • 22,556
  • 11
  • 85
  • 107
7

If the redirect is to the same path, you could just use Flash#setKeepMessages().

context.getExternalContext().getFlash().setKeepMessages(true);

This way the messages are persisted in the flash scope which lives effectively as long as a single subsequent GET request (as occurs during a redirect).

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Could you please point me where exactly I should add the above line in the code I mentioned? – Sri Aug 14 '12 at 16:50
  • 1
    Just in the `reject()` method. Please note once again that this won't work if the redirect is to a different path (due to a still unresolved bug in the the current Mojarra version). Use the `PhaseListener` approach as suggested by eljunior then. – BalusC Aug 14 '12 at 16:52
  • By same path, you mean when the page is loaded initially before clicking the Reject button is ""/xxx/inbox?source=pendingActions#pendingApproval", then I should redirect to the same path ""/xxx/inbox?source=pendingActions#pendingApproval". Then this solution would work, am I right? – Sri Aug 14 '12 at 17:00
  • Basically, the redirected resource should be in the same "folder" as the currently requested resource. Please let me know if you have you tried it or not? It starts to sound more like as if you have not tried it at all and this would only cause confusion as to why exactly you're asking this all. – BalusC Aug 14 '12 at 17:01
  • I am trying your solution, but looks like its not in the same folder. So I will try the phaselistener approach. – Sri Aug 14 '12 at 17:06