Adding faces message to redirected page using ExternalContext.redirect()
Use the flash to keep messages over a redirect.
Add these two lines to your code before redirecting:
FacesContext context = FacesContext.getCurrentInstance();
context.getExternalContext().getFlash().setKeepMessages(true);
Note that the there are some issues with Mojarra's flash scope implementation. Keep this in mind if you use it.
This seems to be a timing problem. This listener method is invoked during the preRenderView
event. According to the source code of ELFlash
(Mojarra's Flash
implementation as returned by ExternalContext#getFlash()
) it turns out that it won't set the flash cookie when you're currently sitting in the render response phase and the flash cookie hasn't been set yet for the current request:
Here are the relevant lines from ELFlash
:
if (currentPhase.getOrdinal() < PhaseId.RENDER_RESPONSE.getOrdinal()) {
flashInfo = flashManager.getPreviousRequestFlashInfo();
} else {
flashInfo = flashManager.getNextRequestFlashInfo(this, true);
maybeWriteCookie(context, flashManager);
}
The maybeWriteCookie
would only set the cookie when the flash cookie needs to be passed through for the second time (i.e. when the redirected page in turn redirects to another page).
This is an unfortunate corner case. This ELFlash
logic makes sense, but this isn't what you actually want. Basically you need to add the message during INVOKE_APPLICATION
phase instead. There is however no such event as postInvokeAction
. With the new JSF 2.2 <f:viewAction>
tag it should be possible as it really runs during invoke application phase.
<f:viewAction action="#{bean.onload}" />
As long as you're not on JSF 2.2 yet, you'd need to look for alternate ways. The easiest way would be to create a custom ComponentSystemEvent
.
@NamedEvent(shortName="postInvokeAction")
public class PostInvokeActionEvent extends ComponentSystemEvent {
public PostInvokeActionEvent(UIComponent component) {
super(component);
}
}
Now you need somewhere a hook to publish this event. The most sensible place is a PhaseListener
listening on after phase of INVOKE_APPLICATION
.
public class PostInvokeActionListener implements PhaseListener {
@Override
public PhaseId getPhaseId() {
return PhaseId.INVOKE_APPLICATION;
}
@Override
public void beforePhase(PhaseEvent event) {
// NOOP.
}
@Override
public void afterPhase(PhaseEvent event) {
FacesContext context = FacesContext.getCurrentInstance();
context.getApplication().publishEvent(context, PostInvokeActionEvent.class, context.getViewRoot());
}
}
If you register it as follows in faces-config.xml
<lifecycle>
<phase-listener>com.example.PostInvokeActionListener</phase-listener>
</lifecycle>
then you'll be able to use the new event as follows
<f:event type="postInvokeAction" listener="#{bean.onload}" />
Update this is also available in the JSF utility library OmniFaces, so you don't need to homebrew the one and other. See also the InvokeActionEventListener
showcase example.